summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/bin/feature_flag_spec.rb191
-rw-r--r--spec/config/object_store_settings_spec.rb102
-rw-r--r--spec/config/settings_spec.rb22
-rw-r--r--spec/controllers/admin/application_settings_controller_spec.rb7
-rw-r--r--spec/controllers/admin/clusters_controller_spec.rb33
-rw-r--r--spec/controllers/admin/jobs_controller_spec.rb32
-rw-r--r--spec/controllers/admin/services_controller_spec.rb22
-rw-r--r--spec/controllers/application_controller_spec.rb22
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb50
-rw-r--r--spec/controllers/concerns/controller_with_feature_category/config_spec.rb53
-rw-r--r--spec/controllers/concerns/controller_with_feature_category_spec.rb66
-rw-r--r--spec/controllers/concerns/metrics_dashboard_spec.rb33
-rw-r--r--spec/controllers/concerns/renders_commits_spec.rb2
-rw-r--r--spec/controllers/concerns/sorting_preference_spec.rb4
-rw-r--r--spec/controllers/dashboard/projects_controller_spec.rb42
-rw-r--r--spec/controllers/dashboard/snippets_controller_spec.rb4
-rw-r--r--spec/controllers/dashboard/todos_controller_spec.rb9
-rw-r--r--spec/controllers/dashboard_controller_spec.rb8
-rw-r--r--spec/controllers/every_controller_spec.rb82
-rw-r--r--spec/controllers/groups/clusters_controller_spec.rb40
-rw-r--r--spec/controllers/groups/imports_controller_spec.rb2
-rw-r--r--spec/controllers/groups/runners_controller_spec.rb127
-rw-r--r--spec/controllers/groups/settings/ci_cd_controller_spec.rb15
-rw-r--r--spec/controllers/groups_controller_spec.rb36
-rw-r--r--spec/controllers/import/bitbucket_controller_spec.rb48
-rw-r--r--spec/controllers/import/bitbucket_server_controller_spec.rb81
-rw-r--r--spec/controllers/import/fogbugz_controller_spec.rb23
-rw-r--r--spec/controllers/import/gitlab_controller_spec.rb23
-rw-r--r--spec/controllers/instance_statistics/cohorts_controller_spec.rb7
-rw-r--r--spec/controllers/instance_statistics/dev_ops_score_controller_spec.rb13
-rw-r--r--spec/controllers/invites_controller_spec.rb37
-rw-r--r--spec/controllers/oauth/applications_controller_spec.rb16
-rw-r--r--spec/controllers/profiles/keys_controller_spec.rb65
-rw-r--r--spec/controllers/projects/clusters_controller_spec.rb42
-rw-r--r--spec/controllers/projects/cycle_analytics_controller_spec.rb7
-rw-r--r--spec/controllers/projects/deployments_controller_spec.rb46
-rw-r--r--spec/controllers/projects/discussions_controller_spec.rb3
-rw-r--r--spec/controllers/projects/environments/prometheus_api_controller_spec.rb206
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb45
-rw-r--r--spec/controllers/projects/graphs_controller_spec.rb9
-rw-r--r--spec/controllers/projects/imports_controller_spec.rb171
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb39
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb103
-rw-r--r--spec/controllers/projects/logs_controller_spec.rb28
-rw-r--r--spec/controllers/projects/merge_requests/diffs_controller_spec.rb12
-rw-r--r--spec/controllers/projects/merge_requests/drafts_controller_spec.rb9
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb4
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb84
-rw-r--r--spec/controllers/projects/pipelines/stages_controller_spec.rb (renamed from spec/controllers/projects/stages_controller_spec.rb)4
-rw-r--r--spec/controllers/projects/pipelines/tests_controller_spec.rb112
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb95
-rw-r--r--spec/controllers/projects/project_members_controller_spec.rb23
-rw-r--r--spec/controllers/projects/refs_controller_spec.rb9
-rw-r--r--spec/controllers/projects/releases_controller_spec.rb11
-rw-r--r--spec/controllers/projects/service_desk_controller_spec.rb111
-rw-r--r--spec/controllers/projects/services_controller_spec.rb19
-rw-r--r--spec/controllers/projects/settings/operations_controller_spec.rb100
-rw-r--r--spec/controllers/projects/snippets/blobs_controller_spec.rb85
-rw-r--r--spec/controllers/projects/snippets_controller_spec.rb28
-rw-r--r--spec/controllers/projects/tree_controller_spec.rb28
-rw-r--r--spec/controllers/projects/wikis_controller_spec.rb2
-rw-r--r--spec/controllers/projects_controller_spec.rb91
-rw-r--r--spec/controllers/registrations/experience_levels_controller_spec.rb47
-rw-r--r--spec/controllers/search_controller_spec.rb5
-rw-r--r--spec/controllers/snippets/blobs_controller_spec.rb61
-rw-r--r--spec/controllers/snippets_controller_spec.rb6
-rw-r--r--spec/controllers/users_controller_spec.rb65
-rw-r--r--spec/db/schema_spec.rb92
-rw-r--r--spec/factories/alert_management/alerts.rb28
-rw-r--r--spec/factories/approvals.rb10
-rw-r--r--spec/factories/background_migration_jobs.rb13
-rw-r--r--spec/factories/ci/builds.rb12
-rw-r--r--spec/factories/ci/pipelines.rb16
-rw-r--r--spec/factories/clusters/applications/helm.rb8
-rw-r--r--spec/factories/clusters/clusters.rb1
-rw-r--r--spec/factories/custom_emoji.rb9
-rw-r--r--spec/factories/deployments.rb2
-rw-r--r--spec/factories/events.rb2
-rw-r--r--spec/factories/go_module_commits.rb82
-rw-r--r--spec/factories/go_module_versions.rb77
-rw-r--r--spec/factories/go_modules.rb13
-rw-r--r--spec/factories/groups.rb8
-rw-r--r--spec/factories/namespaces.rb30
-rw-r--r--spec/factories/notes.rb5
-rw-r--r--spec/factories/packages.rb355
-rw-r--r--spec/factories/product_analytics_event.rb24
-rw-r--r--spec/factories/project_repository_storage_moves.rb8
-rw-r--r--spec/factories/project_statistics.rb1
-rw-r--r--spec/factories/projects.rb26
-rw-r--r--spec/factories/service_desk_settings.rb7
-rw-r--r--spec/factories/services.rb11
-rw-r--r--spec/factories/snippet_statistics.rb25
-rw-r--r--spec/factories/terraform/state.rb6
-rw-r--r--spec/factories/usage_data.rb16
-rw-r--r--spec/features/admin/admin_sees_project_statistics_spec.rb2
-rw-r--r--spec/features/admin/admin_users_spec.rb8
-rw-r--r--spec/features/admin/services/admin_visits_service_templates_spec.rb30
-rw-r--r--spec/features/clusters/cluster_health_dashboard_spec.rb93
-rw-r--r--spec/features/clusters/installing_applications_shared_examples.rb53
-rw-r--r--spec/features/dashboard/projects_spec.rb8
-rw-r--r--spec/features/discussion_comments/commit_spec.rb2
-rw-r--r--spec/features/groups/container_registry_spec.rb2
-rw-r--r--spec/features/groups/empty_states_spec.rb2
-rw-r--r--spec/features/groups/import_export/import_file_spec.rb2
-rw-r--r--spec/features/groups/members/manage_members_spec.rb9
-rw-r--r--spec/features/groups/navbar_spec.rb1
-rw-r--r--spec/features/groups_spec.rb29
-rw-r--r--spec/features/invites_spec.rb260
-rw-r--r--spec/features/issuables/close_reopen_report_toggle_spec.rb22
-rw-r--r--spec/features/issuables/issuable_list_spec.rb10
-rw-r--r--spec/features/issuables/sorting_list_spec.rb4
-rw-r--r--spec/features/issues/bulk_assignment_labels_spec.rb45
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb8
-rw-r--r--spec/features/issues/filtered_search/search_bar_spec.rb2
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb752
-rw-r--r--spec/features/issues/issue_detail_spec.rb2
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb2
-rw-r--r--spec/features/issues/move_spec.rb39
-rw-r--r--spec/features/issues/service_desk_spec.rb163
-rw-r--r--spec/features/issues/update_issues_spec.rb1
-rw-r--r--spec/features/issues/user_creates_branch_and_merge_request_spec.rb8
-rw-r--r--spec/features/issues/user_filters_issues_spec.rb2
-rw-r--r--spec/features/issues/user_interacts_with_awards_spec.rb4
-rw-r--r--spec/features/issues/user_sees_breadcrumb_links_spec.rb2
-rw-r--r--spec/features/issues/user_sees_empty_state_spec.rb4
-rw-r--r--spec/features/issues/user_sorts_issues_spec.rb2
-rw-r--r--spec/features/issues/user_views_issues_spec.rb29
-rw-r--r--spec/features/labels_hierarchy_spec.rb2
-rw-r--r--spec/features/markdown/metrics_spec.rb38
-rw-r--r--spec/features/merge_request/maintainer_edits_fork_spec.rb4
-rw-r--r--spec/features/merge_request/user_approves_spec.rb41
-rw-r--r--spec/features/merge_request/user_closes_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/user_comments_on_diff_spec.rb53
-rw-r--r--spec/features/merge_request/user_creates_image_diff_notes_spec.rb2
-rw-r--r--spec/features/merge_request/user_edits_merge_request_spec.rb69
-rw-r--r--spec/features/merge_request/user_posts_diff_notes_spec.rb4
-rw-r--r--spec/features/merge_request/user_reopens_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_diff_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_merge_widget_spec.rb10
-rw-r--r--spec/features/merge_request/user_sees_pipelines_spec.rb113
-rw-r--r--spec/features/merge_request/user_suggests_changes_on_diff_spec.rb5
-rw-r--r--spec/features/merge_request/user_toggles_whitespace_changes_spec.rb2
-rw-r--r--spec/features/merge_request/user_views_diffs_file_by_file_spec.rb33
-rw-r--r--spec/features/participants_autocomplete_spec.rb23
-rw-r--r--spec/features/profiles/user_edit_preferences_spec.rb13
-rw-r--r--spec/features/profiles/user_edit_profile_spec.rb5
-rw-r--r--spec/features/profiles/user_visits_profile_preferences_page_spec.rb2
-rw-r--r--spec/features/projects/activity/user_sees_design_activity_spec.rb78
-rw-r--r--spec/features/projects/blobs/blob_show_spec.rb2
-rw-r--r--spec/features/projects/blobs/edit_spec.rb16
-rw-r--r--spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb3
-rw-r--r--spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb2
-rw-r--r--spec/features/projects/clusters/gcp_spec.rb26
-rw-r--r--spec/features/projects/clusters_spec.rb162
-rw-r--r--spec/features/projects/commit/builds_spec.rb25
-rw-r--r--spec/features/projects/commits/user_browses_commits_spec.rb27
-rw-r--r--spec/features/projects/confluence/user_views_confluence_page_spec.rb28
-rw-r--r--spec/features/projects/container_registry_spec.rb2
-rw-r--r--spec/features/projects/environments/environment_metrics_spec.rb11
-rw-r--r--spec/features/projects/environments_pod_logs_spec.rb5
-rw-r--r--spec/features/projects/files/edit_file_soft_wrap_spec.rb24
-rw-r--r--spec/features/projects/files/gitignore_dropdown_spec.rb2
-rw-r--r--spec/features/projects/files/user_browses_files_spec.rb20
-rw-r--r--spec/features/projects/files/user_creates_files_spec.rb16
-rw-r--r--spec/features/projects/files/user_edits_files_spec.rb20
-rw-r--r--spec/features/projects/import_export/test_project_export.tar.gzbin3360 -> 3176 bytes
-rw-r--r--spec/features/projects/issues/design_management/user_paginates_designs_spec.rb56
-rw-r--r--spec/features/projects/issues/design_management/user_permissions_upload_spec.rb29
-rw-r--r--spec/features/projects/issues/design_management/user_uploads_designs_spec.rb62
-rw-r--r--spec/features/projects/issues/design_management/user_views_design_spec.rb39
-rw-r--r--spec/features/projects/issues/design_management/user_views_designs_spec.rb79
-rw-r--r--spec/features/projects/issues/design_management/user_views_designs_with_svg_xss_spec.rb1
-rw-r--r--spec/features/projects/jobs_spec.rb4
-rw-r--r--spec/features/projects/labels/issues_sorted_by_priority_spec.rb4
-rw-r--r--spec/features/projects/members/list_spec.rb9
-rw-r--r--spec/features/projects/members/member_leaves_project_spec.rb2
-rw-r--r--spec/features/projects/navbar_spec.rb2
-rw-r--r--spec/features/projects/pipeline_schedules_spec.rb2
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb90
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb17
-rw-r--r--spec/features/projects/services/disable_triggers_spec.rb6
-rw-r--r--spec/features/projects/services/user_activates_alerts_spec.rb51
-rw-r--r--spec/features/projects/services/user_activates_issue_tracker_spec.rb6
-rw-r--r--spec/features/projects/services/user_activates_jira_spec.rb30
-rw-r--r--spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb4
-rw-r--r--spec/features/projects/services/user_activates_slack_slash_command_spec.rb4
-rw-r--r--spec/features/projects/settings/operations_settings_spec.rb5
-rw-r--r--spec/features/projects/settings/registry_settings_spec.rb10
-rw-r--r--spec/features/projects/settings/service_desk_setting_spec.rb33
-rw-r--r--spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb68
-rw-r--r--spec/features/projects/settings/user_manages_project_members_spec.rb9
-rw-r--r--spec/features/projects/show/user_sees_git_instructions_spec.rb22
-rw-r--r--spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb17
-rw-r--r--spec/features/projects/tree/tree_show_spec.rb26
-rw-r--r--spec/features/projects/user_sees_sidebar_spec.rb4
-rw-r--r--spec/features/projects/user_sees_user_popover_spec.rb2
-rw-r--r--spec/features/projects/wiki/user_views_wiki_empty_spec.rb42
-rw-r--r--spec/features/projects/wiki/user_views_wiki_page_spec.rb84
-rw-r--r--spec/features/promotion_spec.rb53
-rw-r--r--spec/features/runners_spec.rb120
-rw-r--r--spec/features/security/project/snippet/public_access_spec.rb9
-rw-r--r--spec/features/signed_commits_spec.rb7
-rw-r--r--spec/features/task_lists_spec.rb2
-rw-r--r--spec/features/triggers_spec.rb2
-rw-r--r--spec/features/users/login_spec.rb6
-rw-r--r--spec/features/users/signup_spec.rb9
-rw-r--r--spec/finders/alert_management/alerts_finder_spec.rb78
-rw-r--r--spec/finders/branches_finder_spec.rb263
-rw-r--r--spec/finders/ci/pipelines_finder_spec.rb4
-rw-r--r--spec/finders/ci/pipelines_for_merge_request_finder_spec.rb90
-rw-r--r--spec/finders/ci/runner_jobs_finder_spec.rb4
-rw-r--r--spec/finders/ci/variables_finder_spec.rb44
-rw-r--r--spec/finders/events_finder_spec.rb22
-rw-r--r--spec/finders/group_projects_finder_spec.rb44
-rw-r--r--spec/finders/merge_requests_finder_spec.rb39
-rw-r--r--spec/finders/notes_finder_spec.rb6
-rw-r--r--spec/finders/packages/conan/package_file_finder_spec.rb64
-rw-r--r--spec/finders/packages/conan/package_finder_spec.rb22
-rw-r--r--spec/finders/packages/go/module_finder_spec.rb71
-rw-r--r--spec/finders/packages/go/version_finder_spec.rb160
-rw-r--r--spec/finders/packages/group_packages_finder_spec.rb156
-rw-r--r--spec/finders/packages/maven/package_finder_spec.rb57
-rw-r--r--spec/finders/packages/npm/package_finder_spec.rb34
-rw-r--r--spec/finders/packages/nuget/package_finder_spec.rb75
-rw-r--r--spec/finders/packages/package_file_finder_spec.rb44
-rw-r--r--spec/finders/packages/package_finder_spec.rb25
-rw-r--r--spec/finders/packages/packages_finder_spec.rb90
-rw-r--r--spec/finders/packages/tags_finder_spec.rb68
-rw-r--r--spec/finders/personal_access_tokens_finder_spec.rb18
-rw-r--r--spec/finders/projects_finder_spec.rb11
-rw-r--r--spec/finders/resource_milestone_event_finder_spec.rb16
-rw-r--r--spec/finders/resource_state_event_finder_spec.rb76
-rw-r--r--spec/finders/snippets_finder_spec.rb22
-rw-r--r--spec/finders/todos_finder_spec.rb53
-rw-r--r--spec/finders/user_recent_events_finder_spec.rb16
-rw-r--r--spec/fixtures/api/graphql/introspection.graphql16
-rw-r--r--spec/fixtures/api/schemas/entities/dag_job.json10
-rw-r--r--spec/fixtures/api/schemas/entities/dag_job_group.json13
-rw-r--r--spec/fixtures/api/schemas/entities/dag_pipeline.json11
-rw-r--r--spec/fixtures/api/schemas/entities/dag_stage.json11
-rw-r--r--spec/fixtures/api/schemas/evidences/build_artifact.json10
-rw-r--r--spec/fixtures/api/schemas/evidences/release.json4
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/composer/index.json29
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/composer/package.json65
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/composer/provider.json25
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/group_package.json33
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/group_packages.json4
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/npm_package.json8
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/npm_package_tags.json7
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/npm_package_version.json46
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/nuget/dependency_group.json22
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/nuget/download_versions.json10
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/nuget/package_metadata.json28
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/nuget/packages_metadata.json54
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/nuget/search.json39
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/nuget/service_index.json19
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/package.json41
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/package_files.json13
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/package_version.json19
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/package_with_build.json10
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/packages.json4
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/pipeline.json27
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/snippets.json10
-rw-r--r--spec/fixtures/clusters/ca_certificate.pem23
-rw-r--r--spec/fixtures/clusters/chain_certificates.pem100
-rw-r--r--spec/fixtures/clusters/intermediate_certificate.pem28
-rw-r--r--spec/fixtures/clusters/root_certificate.pem49
-rw-r--r--spec/fixtures/emails/service_desk.eml28
-rw-r--r--spec/fixtures/emails/service_desk_custom_address.eml27
-rw-r--r--spec/fixtures/emails/service_desk_forwarded.eml30
-rw-r--r--spec/fixtures/emails/service_desk_forwarded_new_issue.eml29
-rw-r--r--spec/fixtures/emails/service_desk_legacy.eml28
-rw-r--r--spec/fixtures/emails/service_desk_sender_and_from.eml27
-rw-r--r--spec/fixtures/emails/valid_reply_with_quick_actions.eml45
-rw-r--r--spec/fixtures/gitlab/import_export/corrupted_project_export.tar.gzbin4352 -> 3846 bytes
-rw-r--r--spec/fixtures/gitlab/import_export/lightweight_project_export.tar.gzbin3837 -> 3647 bytes
-rw-r--r--spec/fixtures/helm/helm_list_v2_cilium_deployed.json.gzbin0 -> 302 bytes
-rw-r--r--spec/fixtures/helm/helm_list_v2_cilium_failed.json.gzbin0 -> 304 bytes
-rw-r--r--spec/fixtures/helm/helm_list_v2_cilium_missing.json.gzbin0 -> 320 bytes
-rw-r--r--spec/fixtures/lib/gitlab/import_export/complex/project.json389
-rw-r--r--spec/fixtures/lib/gitlab/import_export/complex/tree/project/services.ndjson38
-rw-r--r--spec/fixtures/lib/gitlab/import_export/designs/project.json3
-rw-r--r--spec/fixtures/lib/gitlab/import_export/light/project.json44
-rw-r--r--spec/fixtures/lib/gitlab/import_export/light/tree/project/services.ndjson4
-rw-r--r--spec/fixtures/lib/gitlab/import_export/with_invalid_records/project.json1
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml17
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_full_syntax.json12
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_options.json11
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_values.json10
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metric_label_values_variable_full_syntax.json12
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metric_label_values_variable_options.json12
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/text_variable_full_syntax.json2
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/variables.json8
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/blockquote.json24
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/blockquote.md2
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/bullet_list.json72
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/bullet_list.md8
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/code_block.json89
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/code_block.md38
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/code_mark.json24
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/code_mark.md2
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/complex_document.json964
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/complex_document.md223
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/emoji.json66
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/emoji.md4
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/hard_break.json22
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/hard_break.md3
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/heading.json91
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/heading.md12
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/inline_card.json46
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/inline_card.md13
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/invalid_json.json16
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/invalid_no_doc.json13
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/invalid_node_type.json28
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/link_mark.json44
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/link_mark.md6
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/media_group.json59
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/media_group.md10
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/media_single.json42
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/media_single.md4
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/mention.json44
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/mention.md4
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/ordered_list.json170
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/ordered_list.md25
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/panel.json117
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/panel.md12
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/paragraph.json28
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/paragraph.md4
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/rule.json9
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/rule.md2
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/strike_sup_sub_mark.json56
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/strike_sup_sub_mark.md3
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/strong_em_mark.json53
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/strong_em_mark.md2
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/table.json55
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/table.md18
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/underline_text_color_mark.json40
-rw-r--r--spec/fixtures/lib/kramdown/atlassian_document_format/underline_text_color_mark.md2
-rw-r--r--spec/fixtures/pager_duty/webhook_incident_trigger.json239
-rw-r--r--spec/fixtures/product_analytics/event.json16
-rw-r--r--spec/fixtures/sentry/issue_sample_response.json25
-rw-r--r--spec/frontend/__mocks__/@gitlab/ui.js21
-rw-r--r--spec/frontend/__mocks__/document-register-element/index.js1
-rw-r--r--spec/frontend/__mocks__/monaco-editor/index.js2
-rw-r--r--spec/frontend/alert_management/components/alert_management_detail_spec.js31
-rw-r--r--spec/frontend/alert_management/components/alert_management_empty_state_spec.js54
-rw-r--r--spec/frontend/alert_management/components/alert_management_list_wrapper_spec.js57
-rw-r--r--spec/frontend/alert_management/components/alert_management_sidebar_todo_spec.js76
-rw-r--r--spec/frontend/alert_management/components/alert_management_table_spec.js (renamed from spec/frontend/alert_management/components/alert_management_list_spec.js)175
-rw-r--r--spec/frontend/alert_management/components/alert_metrics_spec.js67
-rw-r--r--spec/frontend/alert_management/components/alert_sidebar_status_spec.js107
-rw-r--r--spec/frontend/alert_management/components/sidebar/alert_managment_sidebar_assignees_spec.js (renamed from spec/frontend/alert_management/components/alert_managment_sidebar_assignees_spec.js)29
-rw-r--r--spec/frontend/alert_management/components/sidebar/alert_sidebar_spec.js (renamed from spec/frontend/alert_management/components/alert_sidebar_spec.js)27
-rw-r--r--spec/frontend/alert_management/components/sidebar/alert_sidebar_status_spec.js129
-rw-r--r--spec/frontend/alert_management/components/system_notes/alert_management_system_note_spec.js (renamed from spec/frontend/alert_management/components/alert_management_system_note_spec.js)8
-rw-r--r--spec/frontend/alert_management/mocks/alerts.json4
-rw-r--r--spec/frontend/alert_settings/__snapshots__/alert_settings_form_spec.js.snap48
-rw-r--r--spec/frontend/alert_settings/alert_settings_form_spec.js233
-rw-r--r--spec/frontend/alerts_service_settings/components/alerts_service_form_spec.js14
-rw-r--r--spec/frontend/api_spec.js95
-rw-r--r--spec/frontend/awards_handler_spec.js292
-rw-r--r--spec/frontend/batch_comments/stores/modules/batch_comments/actions_spec.js28
-rw-r--r--spec/frontend/behaviors/copy_as_gfm_spec.js5
-rw-r--r--spec/frontend/behaviors/gl_emoji_spec.js110
-rw-r--r--spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js2
-rw-r--r--spec/frontend/blob/components/__snapshots__/blob_header_filepath_spec.js.snap1
-rw-r--r--spec/frontend/blob/components/blob_content_error_spec.js6
-rw-r--r--spec/frontend/blob/components/blob_header_default_actions_spec.js5
-rw-r--r--spec/frontend/blob/components/blob_header_viewer_switcher_spec.js6
-rw-r--r--spec/frontend/blob/components/mock_data.js14
-rw-r--r--spec/frontend/blob_edit/edit_blob_spec.js31
-rw-r--r--spec/frontend/boards/components/board_form_spec.js5
-rw-r--r--spec/frontend/boards/issue_card_spec.js2
-rw-r--r--spec/frontend/ci_variable_list/components/ci_key_field_spec.js244
-rw-r--r--spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js73
-rw-r--r--spec/frontend/ci_variable_list/services/mock_data.js2
-rw-r--r--spec/frontend/ci_variable_list/store/actions_spec.js68
-rw-r--r--spec/frontend/ci_variable_list/store/mutations_spec.js78
-rw-r--r--spec/frontend/close_reopen_report_toggle_spec.js7
-rw-r--r--spec/frontend/clusters/components/remove_cluster_confirmation_spec.js17
-rw-r--r--spec/frontend/clusters_list/components/ancestor_notice_spec.js51
-rw-r--r--spec/frontend/clusters_list/components/clusters_spec.js59
-rw-r--r--spec/frontend/clusters_list/store/actions_spec.js63
-rw-r--r--spec/frontend/clusters_list/store/mutations_spec.js60
-rw-r--r--spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap110
-rw-r--r--spec/frontend/code_navigation/components/popover_spec.js35
-rw-r--r--spec/frontend/code_navigation/store/actions_spec.js24
-rw-r--r--spec/frontend/code_navigation/utils/index_spec.js2
-rw-r--r--spec/frontend/cycle_analytics/stage_nav_item_spec.js3
-rw-r--r--spec/frontend/design_management/components/design_notes/design_discussion_spec.js2
-rw-r--r--spec/frontend/design_management/pages/design/index_spec.js33
-rw-r--r--spec/frontend/design_management/pages/index_spec.js2
-rw-r--r--spec/frontend/design_management/utils/tracking_spec.js28
-rw-r--r--spec/frontend/design_management_new/components/__snapshots__/design_note_pin_spec.js.snap42
-rw-r--r--spec/frontend/design_management_new/components/__snapshots__/design_presentation_spec.js.snap104
-rw-r--r--spec/frontend/design_management_new/components/__snapshots__/design_scaler_spec.js.snap115
-rw-r--r--spec/frontend/design_management_new/components/__snapshots__/image_spec.js.snap68
-rw-r--r--spec/frontend/design_management_new/components/delete_button_spec.js51
-rw-r--r--spec/frontend/design_management_new/components/design_note_pin_spec.js49
-rw-r--r--spec/frontend/design_management_new/components/design_notes/__snapshots__/design_note_spec.js.snap67
-rw-r--r--spec/frontend/design_management_new/components/design_notes/__snapshots__/design_reply_form_spec.js.snap15
-rw-r--r--spec/frontend/design_management_new/components/design_notes/design_discussion_spec.js322
-rw-r--r--spec/frontend/design_management_new/components/design_notes/design_note_spec.js170
-rw-r--r--spec/frontend/design_management_new/components/design_notes/design_reply_form_spec.js184
-rw-r--r--spec/frontend/design_management_new/components/design_notes/toggle_replies_widget_spec.js98
-rw-r--r--spec/frontend/design_management_new/components/design_overlay_spec.js410
-rw-r--r--spec/frontend/design_management_new/components/design_presentation_spec.js553
-rw-r--r--spec/frontend/design_management_new/components/design_scaler_spec.js67
-rw-r--r--spec/frontend/design_management_new/components/design_sidebar_spec.js236
-rw-r--r--spec/frontend/design_management_new/components/image_spec.js133
-rw-r--r--spec/frontend/design_management_new/components/list/__snapshots__/item_spec.js.snap472
-rw-r--r--spec/frontend/design_management_new/components/list/item_spec.js168
-rw-r--r--spec/frontend/design_management_new/components/toolbar/__snapshots__/index_spec.js.snap63
-rw-r--r--spec/frontend/design_management_new/components/toolbar/__snapshots__/pagination_button_spec.js.snap28
-rw-r--r--spec/frontend/design_management_new/components/toolbar/__snapshots__/pagination_spec.js.snap29
-rw-r--r--spec/frontend/design_management_new/components/toolbar/index_spec.js123
-rw-r--r--spec/frontend/design_management_new/components/toolbar/pagination_button_spec.js61
-rw-r--r--spec/frontend/design_management_new/components/toolbar/pagination_spec.js79
-rw-r--r--spec/frontend/design_management_new/components/upload/__snapshots__/button_spec.js.snap85
-rw-r--r--spec/frontend/design_management_new/components/upload/__snapshots__/design_dropzone_spec.js.snap501
-rw-r--r--spec/frontend/design_management_new/components/upload/__snapshots__/design_version_dropdown_spec.js.snap141
-rw-r--r--spec/frontend/design_management_new/components/upload/button_spec.js59
-rw-r--r--spec/frontend/design_management_new/components/upload/design_dropzone_spec.js151
-rw-r--r--spec/frontend/design_management_new/components/upload/design_version_dropdown_spec.js114
-rw-r--r--spec/frontend/design_management_new/components/upload/mock_data/all_versions.js14
-rw-r--r--spec/frontend/design_management_new/mock_data/all_versions.js8
-rw-r--r--spec/frontend/design_management_new/mock_data/design.js74
-rw-r--r--spec/frontend/design_management_new/mock_data/designs.js17
-rw-r--r--spec/frontend/design_management_new/mock_data/no_designs.js11
-rw-r--r--spec/frontend/design_management_new/mock_data/notes.js46
-rw-r--r--spec/frontend/design_management_new/pages/__snapshots__/index_spec.js.snap317
-rw-r--r--spec/frontend/design_management_new/pages/design/__snapshots__/index_spec.js.snap216
-rw-r--r--spec/frontend/design_management_new/pages/design/index_spec.js294
-rw-r--r--spec/frontend/design_management_new/pages/index_spec.js571
-rw-r--r--spec/frontend/design_management_new/router_spec.js70
-rw-r--r--spec/frontend/design_management_new/utils/cache_update_spec.js44
-rw-r--r--spec/frontend/design_management_new/utils/design_management_utils_spec.js176
-rw-r--r--spec/frontend/design_management_new/utils/error_messages_spec.js62
-rw-r--r--spec/frontend/design_management_new/utils/tracking_spec.js59
-rw-r--r--spec/frontend/diffs/components/app_spec.js55
-rw-r--r--spec/frontend/diffs/components/diff_expansion_cell_spec.js4
-rw-r--r--spec/frontend/diffs/components/diff_file_header_spec.js1
-rw-r--r--spec/frontend/diffs/components/diff_file_row_spec.js11
-rw-r--r--spec/frontend/diffs/components/diff_file_spec.js3
-rw-r--r--spec/frontend/diffs/components/diff_gutter_avatars_spec.js2
-rw-r--r--spec/frontend/diffs/components/diff_line_note_form_spec.js16
-rw-r--r--spec/frontend/diffs/components/diff_table_cell_spec.js15
-rw-r--r--spec/frontend/diffs/components/inline_diff_table_row_spec.js53
-rw-r--r--spec/frontend/diffs/components/no_changes_spec.js8
-rw-r--r--spec/frontend/diffs/components/parallel_diff_table_row_spec.js28
-rw-r--r--spec/frontend/diffs/store/actions_spec.js82
-rw-r--r--spec/frontend/diffs/store/utils_spec.js22
-rw-r--r--spec/frontend/editor/editor_lite_spec.js70
-rw-r--r--spec/frontend/editor/editor_markdown_ext_spec.js204
-rw-r--r--spec/frontend/emoji/emoji_spec.js (renamed from spec/frontend/emoji_spec.js)145
-rw-r--r--spec/frontend/emoji/support/unicode_support_map_spec.js (renamed from spec/frontend/behaviors/gl_emoji/unicode_support_map_spec.js)0
-rw-r--r--spec/frontend/environment.js7
-rw-r--r--spec/frontend/environments/emtpy_state_spec.js16
-rw-r--r--spec/frontend/error_tracking/components/error_details_spec.js12
-rw-r--r--spec/frontend/filtered_search/dropdown_user_spec.js8
-rw-r--r--spec/frontend/filtered_search/filtered_search_manager_spec.js4
-rw-r--r--spec/frontend/filtered_search/stores/recent_searches_store_spec.js9
-rw-r--r--spec/frontend/filtered_search/visual_token_value_spec.js3
-rw-r--r--spec/frontend/fixtures/branches.rb50
-rw-r--r--spec/frontend/fixtures/commit.rb49
-rw-r--r--spec/frontend/fixtures/emojis.rb17
-rw-r--r--spec/frontend/fixtures/metrics_dashboard.rb12
-rw-r--r--spec/frontend/fixtures/services.rb2
-rw-r--r--spec/frontend/fixtures/static/mini_dropdown_graph.html24
-rw-r--r--spec/frontend/fixtures/static/search_autocomplete.html (renamed from spec/frontend/fixtures/static/global_search_input.html)0
-rw-r--r--spec/frontend/fixtures/tags.rb28
-rw-r--r--spec/frontend/gfm_auto_complete_spec.js2
-rw-r--r--spec/frontend/gl_form_spec.js18
-rw-r--r--spec/frontend/helpers/event_hub_factory_spec.js142
-rw-r--r--spec/frontend/helpers/fake_request_animation_frame.js13
-rw-r--r--spec/frontend/helpers/init_vue_mr_page_helper.js (renamed from spec/javascripts/helpers/init_vue_mr_page_helper.js)6
-rw-r--r--spec/frontend/helpers/monitor_helper_spec.js9
-rw-r--r--spec/frontend/helpers/test_constants.js22
-rw-r--r--spec/frontend/helpers/vue_mock_directive.js20
-rw-r--r--spec/frontend/helpers/wait_using_real_timer.js7
-rw-r--r--spec/frontend/ide/commit_icon_spec.js1
-rw-r--r--spec/frontend/ide/components/ide_status_list_spec.js8
-rw-r--r--spec/frontend/ide/components/jobs/__snapshots__/stage_spec.js.snap2
-rw-r--r--spec/frontend/ide/components/repo_editor_spec.js113
-rw-r--r--spec/frontend/ide/helpers.js1
-rw-r--r--spec/frontend/ide/lib/editor_spec.js22
-rw-r--r--spec/frontend/ide/services/index_spec.js6
-rw-r--r--spec/frontend/ide/stores/actions/file_spec.js361
-rw-r--r--spec/frontend/ide/stores/actions/merge_request_spec.js1
-rw-r--r--spec/frontend/ide/stores/actions/tree_spec.js3
-rw-r--r--spec/frontend/ide/utils_spec.js52
-rw-r--r--spec/frontend/image_diff/helpers/comment_indicator_helper_spec.js3
-rw-r--r--spec/frontend/image_diff/helpers/utils_helper_spec.js3
-rw-r--r--spec/frontend/image_diff/image_diff_spec.js3
-rw-r--r--spec/frontend/image_diff/replaced_image_diff_spec.js7
-rw-r--r--spec/frontend/import_projects/store/actions_spec.js27
-rw-r--r--spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap99
-rw-r--r--spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap63
-rw-r--r--spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap89
-rw-r--r--spec/frontend/incidents_settings/components/alerts_form_spec.js49
-rw-r--r--spec/frontend/incidents_settings/components/incidents_settings_service_spec.js55
-rw-r--r--spec/frontend/incidents_settings/components/incidents_settings_tabs_spec.js55
-rw-r--r--spec/frontend/incidents_settings/components/pagerduty_form_spec.js67
-rw-r--r--spec/frontend/integrations/edit/components/active_toggle_spec.js18
-rw-r--r--spec/frontend/integrations/edit/components/dynamic_field_spec.js242
-rw-r--r--spec/frontend/integrations/edit/components/integration_form_spec.js94
-rw-r--r--spec/frontend/integrations/edit/components/jira_issues_fields_spec.js96
-rw-r--r--spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js25
-rw-r--r--spec/frontend/integrations/edit/components/trigger_fields_spec.js51
-rw-r--r--spec/frontend/integrations/edit/mock_data.js18
-rw-r--r--spec/frontend/integrations/edit/store/actions_spec.js19
-rw-r--r--spec/frontend/integrations/edit/store/getters_spec.js71
-rw-r--r--spec/frontend/integrations/edit/store/mutations_spec.js19
-rw-r--r--spec/frontend/integrations/edit/store/state_spec.js26
-rw-r--r--spec/frontend/issuable_suggestions/components/app_spec.js4
-rw-r--r--spec/frontend/issuable_suggestions/components/item_spec.js5
-rw-r--r--spec/frontend/issuable_suggestions/mock_data.js8
-rw-r--r--spec/frontend/issuables_list/components/issuable_list_root_app_spec.js24
-rw-r--r--spec/frontend/issuables_list/components/issuable_spec.js167
-rw-r--r--spec/frontend/issuables_list/components/issuables_list_app_spec.js148
-rw-r--r--spec/frontend/issue_show/components/issuable_header_warnings_spec.js79
-rw-r--r--spec/frontend/issue_show/components/pinned_links_spec.js15
-rw-r--r--spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap277
-rw-r--r--spec/frontend/jira_import/components/jira_import_app_spec.js208
-rw-r--r--spec/frontend/jira_import/components/jira_import_form_spec.js179
-rw-r--r--spec/frontend/jira_import/components/jira_import_progress_spec.js7
-rw-r--r--spec/frontend/jira_import/components/jira_import_setup_spec.js4
-rw-r--r--spec/frontend/jira_import/mock_data.js53
-rw-r--r--spec/frontend/jira_import/utils/jira_import_utils_spec.js59
-rw-r--r--spec/frontend/jobs/components/job_app_spec.js148
-rw-r--r--spec/frontend/jobs/components/job_log_spec.js65
-rw-r--r--spec/frontend/jobs/components/log/collapsible_section_spec.js4
-rw-r--r--spec/frontend/jobs/store/mutations_spec.js25
-rw-r--r--spec/frontend/jobs/store/utils_spec.js4
-rw-r--r--spec/frontend/lib/utils/common_utils_spec.js47
-rw-r--r--spec/frontend/lib/utils/datetime_utility_spec.js26
-rw-r--r--spec/frontend/lib/utils/dom_utils_spec.js50
-rw-r--r--spec/frontend/lib/utils/grammar_spec.js12
-rw-r--r--spec/frontend/lib/utils/text_markdown_spec.js108
-rw-r--r--spec/frontend/lib/utils/text_utility_spec.js50
-rw-r--r--spec/frontend/lib/utils/url_utility_spec.js8
-rw-r--r--spec/frontend/logs/components/environment_logs_spec.js2
-rw-r--r--spec/frontend/logs/components/log_control_buttons_spec.js8
-rw-r--r--spec/frontend/logs/mock_data.js15
-rw-r--r--spec/frontend/logs/stores/actions_spec.js27
-rw-r--r--spec/frontend/logs/stores/mutations_spec.js35
-rw-r--r--spec/frontend/merge_request_tabs_spec.js2
-rw-r--r--spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap55
-rw-r--r--spec/frontend/monitoring/components/__snapshots__/empty_state_spec.js.snap68
-rw-r--r--spec/frontend/monitoring/components/charts/anomaly_spec.js133
-rw-r--r--spec/frontend/monitoring/components/charts/column_spec.js10
-rw-r--r--spec/frontend/monitoring/components/charts/single_stat_spec.js54
-rw-r--r--spec/frontend/monitoring/components/charts/time_series_spec.js95
-rw-r--r--spec/frontend/monitoring/components/create_dashboard_modal_spec.js48
-rw-r--r--spec/frontend/monitoring/components/dashboard_header_spec.js232
-rw-r--r--spec/frontend/monitoring/components/dashboard_panel_spec.js59
-rw-r--r--spec/frontend/monitoring/components/dashboard_spec.js237
-rw-r--r--spec/frontend/monitoring/components/dashboard_template_spec.js4
-rw-r--r--spec/frontend/monitoring/components/dashboard_url_time_spec.js5
-rw-r--r--spec/frontend/monitoring/components/dashboards_dropdown_spec.js184
-rw-r--r--spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js111
-rw-r--r--spec/frontend/monitoring/components/empty_state_spec.js23
-rw-r--r--spec/frontend/monitoring/components/graph_group_spec.js126
-rw-r--r--spec/frontend/monitoring/components/links_section_spec.js2
-rw-r--r--spec/frontend/monitoring/components/refresh_button_spec.js143
-rw-r--r--spec/frontend/monitoring/components/variables/dropdown_field_spec.js (renamed from spec/frontend/monitoring/components/variables/custom_variable_spec.js)33
-rw-r--r--spec/frontend/monitoring/components/variables/text_field_spec.js (renamed from spec/frontend/monitoring/components/variables/text_variable_spec.js)8
-rw-r--r--spec/frontend/monitoring/components/variables_section_spec.js63
-rw-r--r--spec/frontend/monitoring/fixture_data.js40
-rw-r--r--spec/frontend/monitoring/graph_data.js164
-rw-r--r--spec/frontend/monitoring/mock_data.js572
-rw-r--r--spec/frontend/monitoring/pages/dashboard_page_spec.js36
-rw-r--r--spec/frontend/monitoring/router_spec.js81
-rw-r--r--spec/frontend/monitoring/store/actions_spec.js980
-rw-r--r--spec/frontend/monitoring/store/getters_spec.js40
-rw-r--r--spec/frontend/monitoring/store/mutations_spec.js154
-rw-r--r--spec/frontend/monitoring/store/utils_spec.js297
-rw-r--r--spec/frontend/monitoring/store/variable_mapping_spec.js263
-rw-r--r--spec/frontend/monitoring/store_utils.js32
-rw-r--r--spec/frontend/monitoring/utils_spec.js55
-rw-r--r--spec/frontend/namespace_storage_limit_alert_spec.js36
-rw-r--r--spec/frontend/notes/components/multiline_comment_utils_spec.js64
-rw-r--r--spec/frontend/notes/components/note_actions_spec.js64
-rw-r--r--spec/frontend/notes/components/note_form_spec.js18
-rw-r--r--spec/frontend/notes/components/noteable_note_spec.js71
-rw-r--r--spec/frontend/notes/mixins/discussion_navigation_spec.js6
-rw-r--r--spec/frontend/notes/old_notes_spec.js44
-rw-r--r--spec/frontend/notes/stores/actions_spec.js133
-rw-r--r--spec/frontend/notes/stores/mutation_spec.js34
-rw-r--r--spec/frontend/pager_spec.js7
-rw-r--r--spec/frontend/pages/admin/jobs/index/components/stop_jobs_modal_spec.js5
-rw-r--r--spec/frontend/pages/labels/components/promote_label_modal_spec.js5
-rw-r--r--spec/frontend/pages/milestones/shared/components/delete_milestone_modal_spec.js5
-rw-r--r--spec/frontend/pages/milestones/shared/components/promote_milestone_modal_spec.js5
-rw-r--r--spec/frontend/pages/projects/forks/new/components/fork_groups_list_item_spec.js78
-rw-r--r--spec/frontend/pages/projects/forks/new/components/fork_groups_list_spec.js133
-rw-r--r--spec/frontend/pages/projects/graphs/code_coverage_spec.js6
-rw-r--r--spec/frontend/pages/projects/graphs/mock_data.js91
-rw-r--r--spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js121
-rw-r--r--spec/frontend/persistent_user_callout_spec.js66
-rw-r--r--spec/frontend/pipelines/blank_state_spec.js2
-rw-r--r--spec/frontend/pipelines/components/dag/__snapshots__/dag_graph_spec.js.snap44
-rw-r--r--spec/frontend/pipelines/components/dag/dag_annotations_spec.js112
-rw-r--r--spec/frontend/pipelines/components/dag/dag_graph_spec.js4
-rw-r--r--spec/frontend/pipelines/components/dag/dag_spec.js171
-rw-r--r--spec/frontend/pipelines/components/dag/mock_data.js80
-rw-r--r--spec/frontend/pipelines/components/pipelines_filtered_search_spec.js2
-rw-r--r--spec/frontend/pipelines/empty_state_spec.js2
-rw-r--r--spec/frontend/pipelines/graph/job_item_spec.js13
-rw-r--r--spec/frontend/pipelines/graph/linked_pipeline_spec.js28
-rw-r--r--spec/frontend/pipelines/graph/linked_pipelines_mock_data.js21
-rw-r--r--spec/frontend/pipelines/nav_controls_spec.js2
-rw-r--r--spec/frontend/pipelines/pipeline_triggerer_spec.js2
-rw-r--r--spec/frontend/pipelines/pipeline_url_spec.js116
-rw-r--r--spec/frontend/pipelines/pipelines_actions_spec.js2
-rw-r--r--spec/frontend/pipelines/pipelines_artifacts_spec.js2
-rw-r--r--spec/frontend/pipelines/pipelines_spec.js14
-rw-r--r--spec/frontend/pipelines/pipelines_table_row_spec.js2
-rw-r--r--spec/frontend/pipelines/pipelines_table_spec.js2
-rw-r--r--spec/frontend/pipelines/stage_spec.js2
-rw-r--r--spec/frontend/pipelines/test_reports/stores/actions_spec.js109
-rw-r--r--spec/frontend/pipelines/test_reports/stores/getters_spec.js15
-rw-r--r--spec/frontend/pipelines/test_reports/stores/mutations_spec.js34
-rw-r--r--spec/frontend/pipelines/test_reports/test_reports_spec.js71
-rw-r--r--spec/frontend/pipelines/test_reports/test_suite_table_spec.js11
-rw-r--r--spec/frontend/pipelines/test_reports/test_summary_spec.js2
-rw-r--r--spec/frontend/pipelines/time_ago_spec.js2
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js2
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_status_token_spec.js2
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js2
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js2
-rw-r--r--spec/frontend/polyfills/element_spec.js46
-rw-r--r--spec/frontend/projects/commits/store/actions_spec.js4
-rw-r--r--spec/frontend/projects/components/__snapshots__/remove_modal_spec.js.snap126
-rw-r--r--spec/frontend/projects/components/remove_modal_spec.js62
-rw-r--r--spec/frontend/projects/pipelines/charts/components/__snapshots__/pipelines_area_chart_spec.js.snap2
-rw-r--r--spec/frontend/projects/project_new_spec.js3
-rw-r--r--spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js226
-rw-r--r--spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js234
-rw-r--r--spec/frontend/projects/settings_service_desk/services/service_desk_service_spec.js129
-rw-r--r--spec/frontend/ref/components/ref_selector_spec.js532
-rw-r--r--spec/frontend/ref/stores/actions_spec.js180
-rw-r--r--spec/frontend/ref/stores/getters_spec.js36
-rw-r--r--spec/frontend/ref/stores/mutations_spec.js274
-rw-r--r--spec/frontend/registry/explorer/components/delete_button_spec.js73
-rw-r--r--spec/frontend/registry/explorer/components/details_page/details_row_spec.js43
-rw-r--r--spec/frontend/registry/explorer/components/details_page/empty_tags_state_spec.js (renamed from spec/frontend/registry/explorer/components/details_page/empty_tags_state.js)2
-rw-r--r--spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js330
-rw-r--r--spec/frontend/registry/explorer/components/details_page/tags_list_spec.js146
-rw-r--r--spec/frontend/registry/explorer/components/details_page/tags_table_spec.js286
-rw-r--r--spec/frontend/registry/explorer/components/list_item_spec.js156
-rw-r--r--spec/frontend/registry/explorer/components/list_page/__snapshots__/group_empty_state_spec.js.snap5
-rw-r--r--spec/frontend/registry/explorer/components/list_page/__snapshots__/project_empty_state_spec.js.snap95
-rw-r--r--spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js35
-rw-r--r--spec/frontend/registry/explorer/mock_data.js10
-rw-r--r--spec/frontend/registry/explorer/pages/details_spec.js53
-rw-r--r--spec/frontend/registry/explorer/stubs.js17
-rw-r--r--spec/frontend/registry/settings/components/__snapshots__/registry_settings_app_spec.js.snap18
-rw-r--r--spec/frontend/registry/settings/components/registry_settings_app_spec.js11
-rw-r--r--spec/frontend/registry/settings/components/settings_form_spec.js69
-rw-r--r--spec/frontend/registry/shared/components/__snapshots__/expiration_policy_fields_spec.js.snap20
-rw-r--r--spec/frontend/registry/shared/components/expiration_policy_fields_spec.js75
-rw-r--r--spec/frontend/releases/components/app_new_spec.js26
-rw-r--r--spec/frontend/releases/components/release_block_assets_spec.js32
-rw-r--r--spec/frontend/reports/codequality_report/components/codequality_issue_body_spec.js62
-rw-r--r--spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js146
-rw-r--r--spec/frontend/reports/codequality_report/mock_data.js90
-rw-r--r--spec/frontend/reports/codequality_report/store/actions_spec.js151
-rw-r--r--spec/frontend/reports/codequality_report/store/getters_spec.js95
-rw-r--r--spec/frontend/reports/codequality_report/store/mutations_spec.js80
-rw-r--r--spec/frontend/reports/codequality_report/store/utils/codequality_comparison_spec.js139
-rw-r--r--spec/frontend/reports/components/grouped_test_reports_app_spec.js45
-rw-r--r--spec/frontend/reports/components/report_section_spec.js102
-rw-r--r--spec/frontend/reports/components/summary_row_spec.js43
-rw-r--r--spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap55
-rw-r--r--spec/frontend/repository/components/table/index_spec.js15
-rw-r--r--spec/frontend/repository/components/table/row_spec.js16
-rw-r--r--spec/frontend/repository/components/web_ide_link_spec.js51
-rw-r--r--spec/frontend/repository/utils/dom_spec.js3
-rw-r--r--spec/frontend/search_autocomplete_spec.js (renamed from spec/frontend/global_search_input_spec.js)107
-rw-r--r--spec/frontend/self_monitor/components/__snapshots__/self_monitor_form_spec.js.snap4
-rw-r--r--spec/frontend/self_monitor/components/self_monitor_form_spec.js3
-rw-r--r--spec/frontend/sidebar/__snapshots__/confidential_issue_sidebar_spec.js.snap6
-rw-r--r--spec/frontend/sidebar/confidential/edit_form_buttons_spec.js130
-rw-r--r--spec/frontend/sidebar/confidential/edit_form_spec.js2
-rw-r--r--spec/frontend/sidebar/confidential_issue_sidebar_spec.js28
-rw-r--r--spec/frontend/snippets/components/edit_spec.js179
-rw-r--r--spec/frontend/snippets/components/show_spec.js35
-rw-r--r--spec/frontend/snippets/components/snippet_blob_edit_spec.js137
-rw-r--r--spec/frontend/snippets/components/snippet_blob_view_spec.js38
-rw-r--r--spec/frontend/snippets/components/snippet_header_spec.js19
-rw-r--r--spec/frontend/static_site_editor/components/edit_area_spec.js41
-rw-r--r--spec/frontend/static_site_editor/mock_data.js7
-rw-r--r--spec/frontend/static_site_editor/services/parse_source_file_spec.js92
-rw-r--r--spec/frontend/static_site_editor/services/submit_content_changes_spec.js48
-rw-r--r--spec/frontend/vue_mr_widget/components/approvals/approvals_spec.js391
-rw-r--r--spec/frontend/vue_mr_widget/components/approvals/approvals_summary_optional_spec.js57
-rw-r--r--spec/frontend/vue_mr_widget/components/approvals/approvals_summary_spec.js93
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js54
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_expandable_section_spec.js65
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_header_spec.js35
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js300
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js86
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_terraform_plan_spec.js107
-rw-r--r--spec/frontend/vue_mr_widget/components/pipeline_tour_mock_data.js7
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js9
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_checking_spec.js2
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_tour_spec.js143
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js44
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js42
-rw-r--r--spec/frontend/vue_mr_widget/components/states/pipeline_tour_mock_data.js10
-rw-r--r--spec/frontend/vue_mr_widget/components/terraform/mock_data.js31
-rw-r--r--spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js172
-rw-r--r--spec/frontend/vue_mr_widget/components/terraform/terraform_plan_spec.js95
-rw-r--r--spec/frontend/vue_mr_widget/mock_data.js15
-rw-r--r--spec/frontend/vue_mr_widget/mr_widget_options_spec.js6
-rw-r--r--spec/frontend/vue_mr_widget/stores/get_state_key_spec.js8
-rw-r--r--spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap63
-rw-r--r--spec/frontend/vue_shared/components/file_icon_spec.js27
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js60
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js23
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js23
-rw-r--r--spec/frontend/vue_shared/components/gl_modal_vuex_spec.js28
-rw-r--r--spec/frontend/vue_shared/components/issue/issue_assignees_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/issue/issue_milestone_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/issue/issue_warning_spec.js105
-rw-r--r--spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/issue/related_issuable_mock_data.js18
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js25
-rw-r--r--spec/frontend/vue_shared/components/notes/__snapshots__/noteable_warning_spec.js.snap (renamed from spec/frontend/vue_shared/components/issue/__snapshots__/issue_warning_spec.js.snap)40
-rw-r--r--spec/frontend/vue_shared/components/notes/noteable_warning_spec.js196
-rw-r--r--spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js10
-rw-r--r--spec/frontend/vue_shared/components/remove_member_modal_spec.js65
-rw-r--r--spec/frontend/vue_shared/components/resizable_chart/__snapshots__/resizable_chart_container_spec.js.snap (renamed from spec/frontend/vue_shared/components/__snapshots__/resizable_chart_container_spec.js.snap)0
-rw-r--r--spec/frontend/vue_shared/components/resizable_chart/__snapshots__/skeleton_loader_spec.js.snap324
-rw-r--r--spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js (renamed from spec/frontend/vue_shared/components/resizable_chart_container_spec.js)0
-rw-r--r--spec/frontend/vue_shared/components/resizable_chart/skeleton_loader_spec.js55
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js62
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal_spec.js76
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/upload_image_tab_spec.js41
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/modals/add_image_modal_spec.js41
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js43
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/build_custom_renderer_spec.js29
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer_spec.js50
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token_spec.js88
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/renderers/mock_data.js58
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_embedded_ruby_spec.js30
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline_spec.js33
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_html_block_spec.js38
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text_spec.js55
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph_spec.js65
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_list_spec.js55
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_text_spec.js30
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js62
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js14
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js29
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/getters_spec.js21
-rw-r--r--spec/frontend/vue_shared/components/user_popover/user_popover_spec.js46
-rw-r--r--spec/frontend/wikis_spec.js30
-rw-r--r--spec/frontend_integration/ide/__snapshots__/ide_integration_spec.js.snap1
-rw-r--r--spec/graphql/mutations/alert_management/alerts/set_assignees_spec.rb2
-rw-r--r--spec/graphql/mutations/alert_management/alerts/todo/create_spec.rb58
-rw-r--r--spec/graphql/mutations/alert_management/update_alert_status_spec.rb2
-rw-r--r--spec/graphql/mutations/concerns/mutations/resolves_issuable_spec.rb13
-rw-r--r--spec/graphql/mutations/container_expiration_policies/update_spec.rb2
-rw-r--r--spec/graphql/mutations/issues/set_locked_spec.rb46
-rw-r--r--spec/graphql/mutations/issues/update_spec.rb2
-rw-r--r--spec/graphql/mutations/merge_requests/create_spec.rb2
-rw-r--r--spec/graphql/mutations/merge_requests/set_assignees_spec.rb2
-rw-r--r--spec/graphql/mutations/merge_requests/set_labels_spec.rb2
-rw-r--r--spec/graphql/mutations/merge_requests/set_locked_spec.rb2
-rw-r--r--spec/graphql/mutations/merge_requests/set_milestone_spec.rb2
-rw-r--r--spec/graphql/mutations/merge_requests/set_subscription_spec.rb2
-rw-r--r--spec/graphql/mutations/merge_requests/set_wip_spec.rb2
-rw-r--r--spec/graphql/mutations/merge_requests/update_spec.rb68
-rw-r--r--spec/graphql/mutations/todos/mark_all_done_spec.rb5
-rw-r--r--spec/graphql/mutations/todos/mark_done_spec.rb2
-rw-r--r--spec/graphql/mutations/todos/restore_many_spec.rb6
-rw-r--r--spec/graphql/mutations/todos/restore_spec.rb2
-rw-r--r--spec/graphql/resolvers/alert_management/alert_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/alert_management/alert_status_counts_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/base_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/board_lists_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/boards_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/branch_commit_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/ci_configuration/sast_resolver_spec.rb28
-rw-r--r--spec/graphql/resolvers/commit_pipelines_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/concerns/looks_ahead_spec.rb2
-rw-r--r--spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb2
-rw-r--r--spec/graphql/resolvers/concerns/resolves_project_spec.rb2
-rw-r--r--spec/graphql/resolvers/design_management/design_at_version_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/design_management/design_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/design_management/designs_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/design_management/version/design_at_version_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/design_management/version/designs_at_version_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/design_management/version_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/design_management/versions_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/echo_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/environments_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/group_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/issues_resolver_spec.rb38
-rw-r--r--spec/graphql/resolvers/last_commit_resolver_spec.rb28
-rw-r--r--spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/merge_requests_resolver_spec.rb25
-rw-r--r--spec/graphql/resolvers/metadata_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/metrics/dashboard_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/metrics/dashboards/annotation_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/milestone_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/namespace_projects_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/packages_resolver_spec.rb17
-rw-r--r--spec/graphql/resolvers/project_members_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/project_pipeline_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/project_pipelines_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/project_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/projects/grafana_integration_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb4
-rw-r--r--spec/graphql/resolvers/projects/services_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/projects/snippets_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/projects_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/release_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/releases_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/snippets_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/todo_resolver_spec.rb43
-rw-r--r--spec/graphql/resolvers/tree_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/user_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/users/snippets_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/users_resolver_spec.rb2
-rw-r--r--spec/graphql/types/access_level_enum_spec.rb2
-rw-r--r--spec/graphql/types/access_level_type_spec.rb2
-rw-r--r--spec/graphql/types/alert_management/alert_status_count_type_spec.rb2
-rw-r--r--spec/graphql/types/alert_management/alert_type_spec.rb3
-rw-r--r--spec/graphql/types/alert_management/severity_enum_spec.rb2
-rw-r--r--spec/graphql/types/alert_management/status_enum_spec.rb2
-rw-r--r--spec/graphql/types/award_emojis/award_emoji_type_spec.rb2
-rw-r--r--spec/graphql/types/base_enum_spec.rb2
-rw-r--r--spec/graphql/types/base_field_spec.rb2
-rw-r--r--spec/graphql/types/blob_viewers/type_enum_spec.rb2
-rw-r--r--spec/graphql/types/board_list_type_spec.rb2
-rw-r--r--spec/graphql/types/board_type_spec.rb2
-rw-r--r--spec/graphql/types/branch_type_spec.rb2
-rw-r--r--spec/graphql/types/ci/detailed_status_type_spec.rb2
-rw-r--r--spec/graphql/types/ci/pipeline_type_spec.rb2
-rw-r--r--spec/graphql/types/ci_configuration/sast/analyzers_entity_type_spec.rb11
-rw-r--r--spec/graphql/types/ci_configuration/sast/entity_type_spec.rb11
-rw-r--r--spec/graphql/types/ci_configuration/sast/options_entity_spec.rb11
-rw-r--r--spec/graphql/types/ci_configuration/sast/type_spec.rb11
-rw-r--r--spec/graphql/types/commit_action_mode_enum_spec.rb2
-rw-r--r--spec/graphql/types/commit_encoding_enum_spec.rb2
-rw-r--r--spec/graphql/types/commit_type_spec.rb2
-rw-r--r--spec/graphql/types/container_expiration_policy_cadence_enum_spec.rb2
-rw-r--r--spec/graphql/types/container_expiration_policy_keep_enum_spec.rb2
-rw-r--r--spec/graphql/types/container_expiration_policy_older_than_enum_spec.rb2
-rw-r--r--spec/graphql/types/container_expiration_policy_type_spec.rb18
-rw-r--r--spec/graphql/types/design_management/design_at_version_type_spec.rb2
-rw-r--r--spec/graphql/types/design_management/design_collection_type_spec.rb2
-rw-r--r--spec/graphql/types/design_management/design_type_spec.rb2
-rw-r--r--spec/graphql/types/design_management/design_version_event_enum_spec.rb2
-rw-r--r--spec/graphql/types/design_management/version_type_spec.rb2
-rw-r--r--spec/graphql/types/design_management_type_spec.rb2
-rw-r--r--spec/graphql/types/diff_refs_type_spec.rb2
-rw-r--r--spec/graphql/types/environment_type_spec.rb2
-rw-r--r--spec/graphql/types/error_tracking/sentry_detailed_error_type_spec.rb4
-rw-r--r--spec/graphql/types/error_tracking/sentry_error_collection_type_spec.rb2
-rw-r--r--spec/graphql/types/error_tracking/sentry_error_stack_trace_entry_type_spec.rb2
-rw-r--r--spec/graphql/types/error_tracking/sentry_error_stack_trace_type_spec.rb2
-rw-r--r--spec/graphql/types/error_tracking/sentry_error_type_spec.rb2
-rw-r--r--spec/graphql/types/evidence_type_spec.rb2
-rw-r--r--spec/graphql/types/global_id_type_spec.rb215
-rw-r--r--spec/graphql/types/grafana_integration_type_spec.rb2
-rw-r--r--spec/graphql/types/group_member_type_spec.rb2
-rw-r--r--spec/graphql/types/group_type_spec.rb2
-rw-r--r--spec/graphql/types/issuable_sort_enum_spec.rb2
-rw-r--r--spec/graphql/types/issuable_state_enum_spec.rb2
-rw-r--r--spec/graphql/types/issue_connection_type_spec.rb11
-rw-r--r--spec/graphql/types/issue_sort_enum_spec.rb2
-rw-r--r--spec/graphql/types/issue_state_enum_spec.rb2
-rw-r--r--spec/graphql/types/issue_type_spec.rb102
-rw-r--r--spec/graphql/types/jira_import_type_spec.rb2
-rw-r--r--spec/graphql/types/jira_user_type_spec.rb13
-rw-r--r--spec/graphql/types/label_type_spec.rb2
-rw-r--r--spec/graphql/types/merge_request_state_enum_spec.rb2
-rw-r--r--spec/graphql/types/merge_request_type_spec.rb5
-rw-r--r--spec/graphql/types/metadata_type_spec.rb2
-rw-r--r--spec/graphql/types/metrics/dashboard_type_spec.rb2
-rw-r--r--spec/graphql/types/metrics/dashboards/annotation_type_spec.rb2
-rw-r--r--spec/graphql/types/milestone_stats_type_spec.rb15
-rw-r--r--spec/graphql/types/milestone_type_spec.rb19
-rw-r--r--spec/graphql/types/mutation_type_spec.rb28
-rw-r--r--spec/graphql/types/namespace_type_spec.rb4
-rw-r--r--spec/graphql/types/notes/diff_position_type_spec.rb2
-rw-r--r--spec/graphql/types/notes/discussion_type_spec.rb2
-rw-r--r--spec/graphql/types/notes/note_type_spec.rb3
-rw-r--r--spec/graphql/types/notes/noteable_type_spec.rb2
-rw-r--r--spec/graphql/types/package_type_enum_spec.rb9
-rw-r--r--spec/graphql/types/package_type_spec.rb15
-rw-r--r--spec/graphql/types/permission_types/base_permission_type_spec.rb2
-rw-r--r--spec/graphql/types/permission_types/issue_spec.rb2
-rw-r--r--spec/graphql/types/permission_types/merge_request_spec.rb2
-rw-r--r--spec/graphql/types/permission_types/merge_request_type_spec.rb2
-rw-r--r--spec/graphql/types/permission_types/note_spec.rb2
-rw-r--r--spec/graphql/types/permission_types/project_spec.rb2
-rw-r--r--spec/graphql/types/permission_types/snippet_spec.rb2
-rw-r--r--spec/graphql/types/permission_types/user_spec.rb2
-rw-r--r--spec/graphql/types/project_member_type_spec.rb2
-rw-r--r--spec/graphql/types/project_statistics_type_spec.rb6
-rw-r--r--spec/graphql/types/project_type_spec.rb92
-rw-r--r--spec/graphql/types/projects/base_service_type_spec.rb2
-rw-r--r--spec/graphql/types/projects/jira_project_type_spec.rb2
-rw-r--r--spec/graphql/types/projects/jira_service_type_spec.rb2
-rw-r--r--spec/graphql/types/projects/service_type_spec.rb2
-rw-r--r--spec/graphql/types/projects/services_enum_spec.rb2
-rw-r--r--spec/graphql/types/query_type_spec.rb2
-rw-r--r--spec/graphql/types/release_asset_link_type_spec.rb15
-rw-r--r--spec/graphql/types/release_assets_type_spec.rb6
-rw-r--r--spec/graphql/types/release_links_type_spec.rb9
-rw-r--r--spec/graphql/types/release_source_type_spec.rb4
-rw-r--r--spec/graphql/types/release_type_spec.rb12
-rw-r--r--spec/graphql/types/repository_type_spec.rb2
-rw-r--r--spec/graphql/types/resolvable_interface_spec.rb2
-rw-r--r--spec/graphql/types/root_storage_statistics_type_spec.rb4
-rw-r--r--spec/graphql/types/snippet_type_spec.rb2
-rw-r--r--spec/graphql/types/snippets/blob_type_spec.rb2
-rw-r--r--spec/graphql/types/snippets/blob_viewer_type_spec.rb2
-rw-r--r--spec/graphql/types/snippets/file_input_action_enum_spec.rb2
-rw-r--r--spec/graphql/types/snippets/file_input_type_spec.rb2
-rw-r--r--spec/graphql/types/time_type_spec.rb2
-rw-r--r--spec/graphql/types/todo_type_spec.rb2
-rw-r--r--spec/graphql/types/tree/blob_type_spec.rb4
-rw-r--r--spec/graphql/types/tree/submodule_type_spec.rb2
-rw-r--r--spec/graphql/types/tree/tree_entry_type_spec.rb2
-rw-r--r--spec/graphql/types/tree/tree_type_spec.rb2
-rw-r--r--spec/graphql/types/tree/type_enum_spec.rb2
-rw-r--r--spec/graphql/types/untrusted_regexp_spec.rb52
-rw-r--r--spec/graphql/types/user_type_spec.rb2
-rw-r--r--spec/haml_lint/linter/documentation_links_spec.rb82
-rw-r--r--spec/haml_lint/linter/no_plain_nodes_spec.rb2
-rw-r--r--spec/helpers/access_tokens_helper_spec.rb2
-rw-r--r--spec/helpers/analytics/unique_visits_helper_spec.rb56
-rw-r--r--spec/helpers/appearances_helper_spec.rb2
-rw-r--r--spec/helpers/application_helper_spec.rb12
-rw-r--r--spec/helpers/application_settings_helper_spec.rb2
-rw-r--r--spec/helpers/auth_helper_spec.rb2
-rw-r--r--spec/helpers/auto_devops_helper_spec.rb8
-rw-r--r--spec/helpers/avatars_helper_spec.rb2
-rw-r--r--spec/helpers/award_emoji_helper_spec.rb2
-rw-r--r--spec/helpers/blame_helper_spec.rb2
-rw-r--r--spec/helpers/blob_helper_spec.rb10
-rw-r--r--spec/helpers/boards_helper_spec.rb2
-rw-r--r--spec/helpers/broadcast_messages_helper_spec.rb2
-rw-r--r--spec/helpers/button_helper_spec.rb2
-rw-r--r--spec/helpers/calendar_helper_spec.rb2
-rw-r--r--spec/helpers/ci/builds_helper_spec.rb111
-rw-r--r--spec/helpers/ci/pipeline_schedules_helper_spec.rb24
-rw-r--r--spec/helpers/ci/runners_helper_spec.rb (renamed from spec/helpers/runners_helper_spec.rb)2
-rw-r--r--spec/helpers/ci/status_helper_spec.rb (renamed from spec/helpers/ci_status_helper_spec.rb)2
-rw-r--r--spec/helpers/clusters_helper_spec.rb26
-rw-r--r--spec/helpers/commits_helper_spec.rb16
-rw-r--r--spec/helpers/components_helper_spec.rb2
-rw-r--r--spec/helpers/container_expiration_policies_helper_spec.rb2
-rw-r--r--spec/helpers/cookies_helper_spec.rb42
-rw-r--r--spec/helpers/dashboard_helper_spec.rb2
-rw-r--r--spec/helpers/defer_script_tag_helper_spec.rb2
-rw-r--r--spec/helpers/diff_helper_spec.rb16
-rw-r--r--spec/helpers/emails_helper_spec.rb2
-rw-r--r--spec/helpers/emoji_helper_spec.rb2
-rw-r--r--spec/helpers/environment_helper_spec.rb2
-rw-r--r--spec/helpers/environments_helper_spec.rb59
-rw-r--r--spec/helpers/events_helper_spec.rb103
-rw-r--r--spec/helpers/explore_helper_spec.rb2
-rw-r--r--spec/helpers/export_helper_spec.rb2
-rw-r--r--spec/helpers/external_link_helper_spec.rb2
-rw-r--r--spec/helpers/form_helper_spec.rb2
-rw-r--r--spec/helpers/git_helper_spec.rb2
-rw-r--r--spec/helpers/gitlab_routing_helper_spec.rb49
-rw-r--r--spec/helpers/graph_helper_spec.rb2
-rw-r--r--spec/helpers/groups/group_members_helper_spec.rb2
-rw-r--r--spec/helpers/groups_helper_spec.rb2
-rw-r--r--spec/helpers/hooks_helper_spec.rb2
-rw-r--r--spec/helpers/icons_helper_spec.rb32
-rw-r--r--spec/helpers/import_helper_spec.rb2
-rw-r--r--spec/helpers/instance_configuration_helper_spec.rb2
-rw-r--r--spec/helpers/issuables_helper_spec.rb26
-rw-r--r--spec/helpers/issues_helper_spec.rb26
-rw-r--r--spec/helpers/labels_helper_spec.rb2
-rw-r--r--spec/helpers/markup_helper_spec.rb2
-rw-r--r--spec/helpers/members_helper_spec.rb2
-rw-r--r--spec/helpers/merge_requests_helper_spec.rb2
-rw-r--r--spec/helpers/namespaces_helper_spec.rb94
-rw-r--r--spec/helpers/nav_helper_spec.rb2
-rw-r--r--spec/helpers/notes_helper_spec.rb2
-rw-r--r--spec/helpers/notifications_helper_spec.rb2
-rw-r--r--spec/helpers/notify_helper_spec.rb33
-rw-r--r--spec/helpers/onboarding_experiment_helper_spec.rb38
-rw-r--r--spec/helpers/operations_helper_spec.rb160
-rw-r--r--spec/helpers/page_layout_helper_spec.rb2
-rw-r--r--spec/helpers/pagination_helper_spec.rb2
-rw-r--r--spec/helpers/preferences_helper_spec.rb9
-rw-r--r--spec/helpers/profiles_helper_spec.rb2
-rw-r--r--spec/helpers/projects/alert_management_helper_spec.rb33
-rw-r--r--spec/helpers/projects/error_tracking_helper_spec.rb2
-rw-r--r--spec/helpers/projects_helper_spec.rb47
-rw-r--r--spec/helpers/recaptcha_experiment_helper_spec.rb2
-rw-r--r--spec/helpers/releases_helper_spec.rb37
-rw-r--r--spec/helpers/rss_helper_spec.rb2
-rw-r--r--spec/helpers/search_helper_spec.rb95
-rw-r--r--spec/helpers/services_helper_spec.rb27
-rw-r--r--spec/helpers/sessions_helper_spec.rb2
-rw-r--r--spec/helpers/sidekiq_helper_spec.rb2
-rw-r--r--spec/helpers/snippets_helper_spec.rb2
-rw-r--r--spec/helpers/sorting_helper_spec.rb2
-rw-r--r--spec/helpers/sourcegraph_helper_spec.rb2
-rw-r--r--spec/helpers/storage_helper_spec.rb7
-rw-r--r--spec/helpers/submodule_helper_spec.rb2
-rw-r--r--spec/helpers/subscribable_banner_helper_spec.rb2
-rw-r--r--spec/helpers/tab_helper_spec.rb2
-rw-r--r--spec/helpers/time_helper_spec.rb2
-rw-r--r--spec/helpers/timeboxes_helper_spec.rb2
-rw-r--r--spec/helpers/timeboxes_routing_helper_spec.rb2
-rw-r--r--spec/helpers/todos_helper_spec.rb81
-rw-r--r--spec/helpers/tracking_helper_spec.rb2
-rw-r--r--spec/helpers/tree_helper_spec.rb56
-rw-r--r--spec/helpers/user_callouts_helper_spec.rb6
-rw-r--r--spec/helpers/users_helper_spec.rb2
-rw-r--r--spec/helpers/version_check_helper_spec.rb2
-rw-r--r--spec/helpers/visibility_level_helper_spec.rb2
-rw-r--r--spec/helpers/wiki_helper_spec.rb54
-rw-r--r--spec/helpers/x509_helper_spec.rb2
-rw-r--r--spec/initializers/100_patch_omniauth_saml_spec.rb2
-rw-r--r--spec/initializers/6_validations_spec.rb2
-rw-r--r--spec/initializers/action_mailer_hooks_spec.rb2
-rw-r--r--spec/initializers/actionpack_generate_old_csrf_token_spec.rb47
-rw-r--r--spec/initializers/active_record_locking_spec.rb2
-rw-r--r--spec/initializers/asset_proxy_setting_spec.rb2
-rw-r--r--spec/initializers/attr_encrypted_no_db_connection_spec.rb2
-rw-r--r--spec/initializers/attr_encrypted_thread_safe_spec.rb2
-rw-r--r--spec/initializers/database_config_spec.rb2
-rw-r--r--spec/initializers/direct_upload_support_spec.rb2
-rw-r--r--spec/initializers/doorkeeper_spec.rb2
-rw-r--r--spec/initializers/fog_google_https_private_urls_spec.rb2
-rw-r--r--spec/initializers/hangouts_chat_http_override_spec.rb2
-rw-r--r--spec/initializers/lograge_spec.rb32
-rw-r--r--spec/initializers/mail_encoding_patch_spec.rb2
-rw-r--r--spec/initializers/rest-client-hostname_override_spec.rb2
-rw-r--r--spec/initializers/secret_token_spec.rb26
-rw-r--r--spec/initializers/settings_spec.rb2
-rw-r--r--spec/initializers/trusted_proxies_spec.rb2
-rw-r--r--spec/initializers/zz_metrics_spec.rb2
-rw-r--r--spec/javascripts/boards/components/board_spec.js251
-rw-r--r--spec/javascripts/boards/mock_data.js1
-rw-r--r--spec/javascripts/fly_out_nav_browser_spec.js (renamed from spec/javascripts/fly_out_nav_spec.js)4
-rw-r--r--spec/javascripts/helpers/class_spec_helper.js9
-rw-r--r--spec/javascripts/helpers/filtered_search_spec_helper.js1
-rw-r--r--spec/javascripts/helpers/index.js3
-rw-r--r--spec/javascripts/helpers/locale_helper.js11
-rw-r--r--spec/javascripts/helpers/set_timeout_promise_helper.js4
-rw-r--r--spec/javascripts/helpers/text_helper.js18
-rw-r--r--spec/javascripts/helpers/tracking_helper.js5
-rw-r--r--spec/javascripts/helpers/user_mock_data_helper.js14
-rw-r--r--spec/javascripts/helpers/vue_mount_component_helper.js2
-rw-r--r--spec/javascripts/helpers/vue_test_utils_helper.js5
-rw-r--r--spec/javascripts/helpers/vuex_action_helper.js102
-rw-r--r--spec/javascripts/helpers/wait_for_promises.js1
-rw-r--r--spec/javascripts/jobs/mock_data.js2
-rw-r--r--spec/javascripts/matchers.js55
-rw-r--r--spec/javascripts/monitoring/components/dashboard_resize_browser_spec.js6
-rw-r--r--spec/javascripts/vue_shared/components/tooltip_on_truncate_browser_spec.js (renamed from spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js)4
-rw-r--r--spec/lib/after_commit_queue_spec.rb2
-rw-r--r--spec/lib/api/api_spec.rb2
-rw-r--r--spec/lib/api/entities/branch_spec.rb2
-rw-r--r--spec/lib/api/entities/deploy_key_spec.rb2
-rw-r--r--spec/lib/api/entities/deploy_keys_project_spec.rb2
-rw-r--r--spec/lib/api/entities/design_management/design_spec.rb2
-rw-r--r--spec/lib/api/entities/job_request/image_spec.rb2
-rw-r--r--spec/lib/api/entities/job_request/port_spec.rb2
-rw-r--r--spec/lib/api/entities/merge_request_approvals_spec.rb36
-rw-r--r--spec/lib/api/entities/merge_request_basic_spec.rb43
-rw-r--r--spec/lib/api/entities/nuget/dependency_group_spec.rb50
-rw-r--r--spec/lib/api/entities/nuget/dependency_spec.rb28
-rw-r--r--spec/lib/api/entities/nuget/metadatum_spec.rb35
-rw-r--r--spec/lib/api/entities/nuget/package_metadata_catalog_entry_spec.rb43
-rw-r--r--spec/lib/api/entities/nuget/search_result_spec.rb57
-rw-r--r--spec/lib/api/entities/project_import_failed_relation_spec.rb2
-rw-r--r--spec/lib/api/entities/project_import_status_spec.rb2
-rw-r--r--spec/lib/api/entities/project_repository_storage_move_spec.rb2
-rw-r--r--spec/lib/api/entities/release_spec.rb2
-rw-r--r--spec/lib/api/entities/snippet_spec.rb55
-rw-r--r--spec/lib/api/entities/ssh_key_spec.rb2
-rw-r--r--spec/lib/api/entities/user_spec.rb2
-rw-r--r--spec/lib/api/helpers/common_helpers_spec.rb51
-rw-r--r--spec/lib/api/helpers/graphql_helpers_spec.rb2
-rw-r--r--spec/lib/api/helpers/label_helpers_spec.rb2
-rw-r--r--spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb72
-rw-r--r--spec/lib/api/helpers/packages_helpers_spec.rb104
-rw-r--r--spec/lib/api/helpers/packages_manager_clients_helpers_spec.rb154
-rw-r--r--spec/lib/api/helpers/pagination_spec.rb2
-rw-r--r--spec/lib/api/helpers/pagination_strategies_spec.rb2
-rw-r--r--spec/lib/api/helpers/related_resources_helpers_spec.rb2
-rw-r--r--spec/lib/api/helpers/version_spec.rb2
-rw-r--r--spec/lib/api/helpers_spec.rb2
-rw-r--r--spec/lib/api/support/git_access_actor_spec.rb2
-rw-r--r--spec/lib/api/validations/validators/absence_spec.rb2
-rw-r--r--spec/lib/api/validations/validators/array_none_any_spec.rb2
-rw-r--r--spec/lib/api/validations/validators/file_path_spec.rb2
-rw-r--r--spec/lib/api/validations/validators/git_ref_spec.rb2
-rw-r--r--spec/lib/api/validations/validators/git_sha_spec.rb2
-rw-r--r--spec/lib/api/validations/validators/integer_none_any_spec.rb2
-rw-r--r--spec/lib/api/validations/validators/limit_spec.rb2
-rw-r--r--spec/lib/api/validations/validators/untrusted_regexp_spec.rb2
-rw-r--r--spec/lib/backup/files_spec.rb2
-rw-r--r--spec/lib/backup/manager_spec.rb2
-rw-r--r--spec/lib/backup/repository_spec.rb2
-rw-r--r--spec/lib/backup/uploads_spec.rb2
-rw-r--r--spec/lib/banzai/color_parser_spec.rb2
-rw-r--r--spec/lib/banzai/commit_renderer_spec.rb2
-rw-r--r--spec/lib/banzai/cross_project_reference_spec.rb2
-rw-r--r--spec/lib/banzai/filter/absolute_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/abstract_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/asset_proxy_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/audio_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/autolink_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/blockquote_fence_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/broadcast_message_placeholders_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/broadcast_message_sanitization_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/color_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/commit_range_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/commit_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/commit_trailers_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/design_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/emoji_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/external_issue_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/external_link_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/footnote_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/front_matter_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/gollum_tags_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/html_entity_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/image_lazy_load_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/image_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/inline_cluster_metrics_filter_spec.rb25
-rw-r--r--spec/lib/banzai/filter/inline_diff_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/inline_metrics_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb22
-rw-r--r--spec/lib/banzai/filter/issuable_state_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/issue_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/jira_import/adf_to_commonmark_filter_spec.rb28
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/markdown_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/math_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/merge_request_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/mermaid_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/milestone_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/output_safety_spec.rb2
-rw-r--r--spec/lib/banzai/filter/plantuml_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/project_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/reference_filter_spec.rb247
-rw-r--r--spec/lib/banzai/filter/reference_redactor_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/repository_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/sanitization_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/snippet_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/spaced_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/suggestion_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/syntax_highlight_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/table_of_contents_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/table_of_contents_tag_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/upload_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/video_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/wiki_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter_array_spec.rb2
-rw-r--r--spec/lib/banzai/issuable_extractor_spec.rb2
-rw-r--r--spec/lib/banzai/object_renderer_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/broadcast_message_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/description_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/email_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/emoji_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/full_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/gfm_pipeline_spec.rb53
-rw-r--r--spec/lib/banzai/pipeline/jira_import/adf_commonmark_pipeline_spec.rb15
-rw-r--r--spec/lib/banzai/pipeline/post_process_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/wiki_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/querying_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/base_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/commit_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/commit_range_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/design_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/external_issue_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/issue_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/label_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/mentioned_group_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/mentioned_project_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/mentioned_user_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/merge_request_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/milestone_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/project_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/snippet_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/user_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_redactor_spec.rb2
-rw-r--r--spec/lib/banzai/render_context_spec.rb2
-rw-r--r--spec/lib/banzai/renderer_spec.rb2
-rw-r--r--spec/lib/bitbucket/collection_spec.rb2
-rw-r--r--spec/lib/bitbucket/connection_spec.rb2
-rw-r--r--spec/lib/bitbucket/page_spec.rb2
-rw-r--r--spec/lib/bitbucket/paginator_spec.rb2
-rw-r--r--spec/lib/bitbucket/representation/comment_spec.rb2
-rw-r--r--spec/lib/bitbucket/representation/issue_spec.rb2
-rw-r--r--spec/lib/bitbucket/representation/pull_request_comment_spec.rb2
-rw-r--r--spec/lib/bitbucket/representation/pull_request_spec.rb2
-rw-r--r--spec/lib/bitbucket/representation/repo_spec.rb2
-rw-r--r--spec/lib/bitbucket/representation/user_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/client_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/collection_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/connection_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/page_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/paginator_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/representation/activity_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/representation/comment_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/representation/pull_request_comment_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/representation/pull_request_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/representation/repo_spec.rb2
-rw-r--r--spec/lib/constraints/admin_constrainer_spec.rb2
-rw-r--r--spec/lib/constraints/feature_constrainer_spec.rb2
-rw-r--r--spec/lib/constraints/group_url_constrainer_spec.rb2
-rw-r--r--spec/lib/constraints/project_url_constrainer_spec.rb2
-rw-r--r--spec/lib/constraints/user_url_constrainer_spec.rb2
-rw-r--r--spec/lib/container_registry/blob_spec.rb2
-rw-r--r--spec/lib/container_registry/client_spec.rb2
-rw-r--r--spec/lib/container_registry/path_spec.rb2
-rw-r--r--spec/lib/container_registry/registry_spec.rb2
-rw-r--r--spec/lib/container_registry/tag_spec.rb2
-rw-r--r--spec/lib/csv_builder_spec.rb2
-rw-r--r--spec/lib/declarative_policy/overrides_spec.rb82
-rw-r--r--spec/lib/declarative_policy_spec.rb2
-rw-r--r--spec/lib/event_filter_spec.rb45
-rw-r--r--spec/lib/expand_variables_spec.rb2
-rw-r--r--spec/lib/extracts_path_spec.rb2
-rw-r--r--spec/lib/extracts_ref_spec.rb2
-rw-r--r--spec/lib/feature/definition_spec.rb209
-rw-r--r--spec/lib/feature/gitaly_spec.rb2
-rw-r--r--spec/lib/feature_spec.rb99
-rw-r--r--spec/lib/file_size_validator_spec.rb2
-rw-r--r--spec/lib/forever_spec.rb2
-rw-r--r--spec/lib/gitaly/server_spec.rb2
-rw-r--r--spec/lib/gitlab/access/branch_protection_spec.rb2
-rw-r--r--spec/lib/gitlab/alert_management/alert_params_spec.rb6
-rw-r--r--spec/lib/gitlab/alert_management/alert_status_counts_spec.rb2
-rw-r--r--spec/lib/gitlab/alert_management/fingerprint_spec.rb70
-rw-r--r--spec/lib/gitlab/alerting/alert_spec.rb2
-rw-r--r--spec/lib/gitlab/alerting/notification_payload_parser_spec.rb6
-rw-r--r--spec/lib/gitlab/allowable_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/unique_visits_spec.rb62
-rw-r--r--spec/lib/gitlab/anonymous_session_spec.rb2
-rw-r--r--spec/lib/gitlab/app_json_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/app_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/app_text_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/application_context_spec.rb2
-rw-r--r--spec/lib/gitlab/application_rate_limiter_spec.rb2
-rw-r--r--spec/lib/gitlab/asciidoc/include_processor_spec.rb2
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb2
-rw-r--r--spec/lib/gitlab/asset_proxy_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/activity_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/auth_finders_spec.rb75
-rw-r--r--spec/lib/gitlab/auth/blocked_user_tracker_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/current_user_mode_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ip_rate_limiter_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/key_status_checker_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/access_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/adapter_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/auth_hash_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/authentication_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/config_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/dn_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/person_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/user_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/o_auth/provider_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/o_auth/user_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/request_authenticator_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/saml/auth_hash_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/saml/identity_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/saml/origin_validator_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/saml/user_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/unique_ips_limiter_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/user_access_denied_reason_spec.rb2
-rw-r--r--spec/lib/gitlab/auth_spec.rb4
-rw-r--r--spec/lib/gitlab/authorized_keys_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb6
-rw-r--r--spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_hashed_project_repositories_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_namespace_settings_spec.rb23
-rw-r--r--spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_push_rules_id_in_projects_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb18
-rw-r--r--spec/lib/gitlab/background_migration/cleanup_concurrent_schema_change_spec.rb28
-rw-r--r--spec/lib/gitlab/background_migration/digest_column_spec.rb46
-rw-r--r--spec/lib/gitlab/background_migration/encrypt_columns_spec.rb96
-rw-r--r--spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb79
-rw-r--r--spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/fix_projects_without_project_feature_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/fix_projects_without_prometheus_service_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/link_lfs_objects_projects_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/mailers/unconfirm_mailer_spec.rb12
-rw-r--r--spec/lib/gitlab/background_migration/merge_request_assignees_migration_progress_check_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb8
-rw-r--r--spec/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_issue_trackers_sensitive_data_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_null_private_profile_to_false_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_pages_metadata_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb32
-rw-r--r--spec/lib/gitlab/background_migration/migrate_users_bio_to_user_details_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_canonical_emails_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_project_snippet_statistics_spec.rb224
-rw-r--r--spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb3
-rw-r--r--spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb3
-rw-r--r--spec/lib/gitlab/background_migration/populate_user_highest_roles_table_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/remove_restricted_todos_spec.rb14
-rw-r--r--spec/lib/gitlab/background_migration/reset_merge_status_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb64
-rw-r--r--spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/wrongfully_confirmed_email_unconfirmer_spec.rb123
-rw-r--r--spec/lib/gitlab/background_migration_spec.rb21
-rw-r--r--spec/lib/gitlab/backtrace_cleaner_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/coverage/metadata_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/coverage/report_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/coverage/template_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/pipeline/metadata_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/pipeline/status_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/pipeline/template_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/shared/metadata.rb2
-rw-r--r--spec/lib/gitlab/bare_repository_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/bare_repository_import/repository_spec.rb2
-rw-r--r--spec/lib/gitlab/batch_pop_queueing_spec.rb2
-rw-r--r--spec/lib/gitlab/batch_worker_context_spec.rb2
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb26
-rw-r--r--spec/lib/gitlab/bitbucket_import/project_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/bitbucket_server_import/importer_spec.rb51
-rw-r--r--spec/lib/gitlab/blame_spec.rb2
-rw-r--r--spec/lib/gitlab/blob_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/branch_push_merge_commit_analyzer_spec.rb2
-rw-r--r--spec/lib/gitlab/build_access_spec.rb2
-rw-r--r--spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb60
-rw-r--r--spec/lib/gitlab/cache/import/caching_spec.rb2
-rw-r--r--spec/lib/gitlab/cache/request_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/changes_list_spec.rb2
-rw-r--r--spec/lib/gitlab/chat/command_spec.rb2
-rw-r--r--spec/lib/gitlab/chat/output_spec.rb2
-rw-r--r--spec/lib/gitlab/chat/responder/base_spec.rb2
-rw-r--r--spec/lib/gitlab/chat/responder/mattermost_spec.rb2
-rw-r--r--spec/lib/gitlab/chat/responder/slack_spec.rb2
-rw-r--r--spec/lib/gitlab/chat/responder_spec.rb2
-rw-r--r--spec/lib/gitlab/chat_name_token_spec.rb2
-rw-r--r--spec/lib/gitlab/chat_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/branch_check_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/change_access_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/diff_check_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/force_push_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/lfs_check_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/lfs_integrity_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/project_created_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/project_moved_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/push_check_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/push_file_count_check_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/snippet_check_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/tag_check_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/timed_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/ansi2html_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/ansi2json/line_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/ansi2json/parser_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/ansi2json/result_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/ansi2json/style_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/ansi2json_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/artifact_file_reader_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/path_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/context/build_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/context/global_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/credentials/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/credentials/registry_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/image_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/policy/changes_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/policy/refs_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/policy/variables_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/policy_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/port_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/releaser_spec.rb20
-rw-r--r--spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/rules/rule_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/rules_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/step_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/charts_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/edge_stages_injector_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/artifacts_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/bridge_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/cache_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/commands_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/coverage_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/default_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/environment_spec.rb15
-rw-r--r--spec/lib/gitlab/ci/config/entry/files_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/hidden_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/image_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/include_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/inherit/default_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/inherit/variables_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/entry/jobs_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/key_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/kubernetes_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/need_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/needs_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/paths_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/policy_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/port_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/ports_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/prefix_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/processable_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/config/entry/release/assets/link_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/release/assets/links_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/release/assets_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/release_spec.rb215
-rw-r--r--spec/lib/gitlab/ci/config/entry/reports_spec.rb5
-rw-r--r--spec/lib/gitlab/ci/config/entry/retry_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/root_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/rules_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/script_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/service_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/services_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/stage_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/stages_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/trigger_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/variables_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/workflow_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/extendable/entry_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/extendable_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/context_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/file/artifact_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/file/base_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/file/local_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/file/project_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/file/remote_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/file/template_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/processor_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/normalizer_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/cron_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/jwt_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/mask_secret_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/parsers/accessibility/pa11y_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/parsers/terraform/tfplan_spec.rb85
-rw-r--r--spec/lib/gitlab/ci/parsers/test/junit_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/parsers_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/build_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/command_spec.rb27
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb26
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/create_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/duration_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/token_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/preloader_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build/resource_group_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb15
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/coverage_reports_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/terraform_reports_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/test_case_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/test_report_summary_spec.rb90
-rw-r--r--spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/test_reports_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/test_suite_spec.rb37
-rw-r--r--spec/lib/gitlab/ci/reports/test_suite_summary_spec.rb89
-rw-r--r--spec/lib/gitlab/ci/status/bridge/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/action_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/cancelable_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/canceled_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/created_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/erased_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/failed_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/manual_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/pending_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/play_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/preparing_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/retried_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/retryable_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/scheduled_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/skipped_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/stop_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/unschedule_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/canceled_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/composite_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/status/created_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/extended_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/external/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/external/factory_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/status/factory_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/status/failed_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/group/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/group/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/manual_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pending_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/factory_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/status/preparing_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/running_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/scheduled_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/skipped_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/stage/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/stage/factory_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/status/stage/play_manual_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/success_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/success_warning_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb (renamed from spec/lib/gitlab/ci/templates/Jobs/browser_performance_testing_gitlab_ci_yaml_spec.rb)26
-rw-r--r--spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/templates/managed_cluster_applications_gitlab_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/templates_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/trace/chunked_io_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/trace/section_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/trace/stream_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/trace_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/variables/collection/item_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/variables/collection_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb164
-rw-r--r--spec/lib/gitlab/ci_access_spec.rb2
-rw-r--r--spec/lib/gitlab/class_attributes_spec.rb41
-rw-r--r--spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb2
-rw-r--r--spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb2
-rw-r--r--spec/lib/gitlab/cleanup/orphan_lfs_file_references_spec.rb2
-rw-r--r--spec/lib/gitlab/cleanup/project_uploads_spec.rb2
-rw-r--r--spec/lib/gitlab/cleanup/remote_uploads_spec.rb2
-rw-r--r--spec/lib/gitlab/closing_issue_extractor_spec.rb2
-rw-r--r--spec/lib/gitlab/cluster/mixins/puma_cluster_spec.rb2
-rw-r--r--spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb2
-rw-r--r--spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb2
-rw-r--r--spec/lib/gitlab/cluster/rack_timeout_observer_spec.rb2
-rw-r--r--spec/lib/gitlab/code_navigation_path_spec.rb4
-rw-r--r--spec/lib/gitlab/color_schemes_spec.rb2
-rw-r--r--spec/lib/gitlab/conan_token_spec.rb97
-rw-r--r--spec/lib/gitlab/config/entry/attributable_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/boolean_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/configurable_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/simplifiable_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/undefined_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/unspecified_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/validatable_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/validator_spec.rb2
-rw-r--r--spec/lib/gitlab/config/loader/yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/config_checker/external_database_checker_spec.rb55
-rw-r--r--spec/lib/gitlab/config_checker/puma_rugged_checker_spec.rb2
-rw-r--r--spec/lib/gitlab/conflict/file_collection_spec.rb2
-rw-r--r--spec/lib/gitlab/conflict/file_spec.rb2
-rw-r--r--spec/lib/gitlab/content_security_policy/config_loader_spec.rb2
-rw-r--r--spec/lib/gitlab/contributions_calendar_spec.rb2
-rw-r--r--spec/lib/gitlab/cross_project_access/check_collection_spec.rb2
-rw-r--r--spec/lib/gitlab/cross_project_access/check_info_spec.rb2
-rw-r--r--spec/lib/gitlab/cross_project_access/class_methods_spec.rb2
-rw-r--r--spec/lib/gitlab/cross_project_access_spec.rb2
-rw-r--r--spec/lib/gitlab/crypto_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/current_settings_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/code_stage_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/events_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/permissions_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/production_stage_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/review_stage_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/summary/value_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/test_stage_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/updater_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/usage_data_spec.rb2
-rw-r--r--spec/lib/gitlab/daemon_spec.rb2
-rw-r--r--spec/lib/gitlab/danger/changelog_spec.rb42
-rw-r--r--spec/lib/gitlab/danger/commit_linter_spec.rb14
-rw-r--r--spec/lib/gitlab/danger/emoji_checker_spec.rb2
-rw-r--r--spec/lib/gitlab/danger/helper_spec.rb328
-rw-r--r--spec/lib/gitlab/danger/roulette_spec.rb265
-rw-r--r--spec/lib/gitlab/danger/sidekiq_queues_spec.rb82
-rw-r--r--spec/lib/gitlab/danger/teammate_spec.rb125
-rw-r--r--spec/lib/gitlab/data_builder/alert_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/build_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/deployment_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/note_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/pipeline_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/push_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/wiki_page_spec.rb2
-rw-r--r--spec/lib/gitlab/database/background_migration_job_spec.rb125
-rw-r--r--spec/lib/gitlab/database/batch_count_spec.rb2
-rw-r--r--spec/lib/gitlab/database/connection_timer_spec.rb2
-rw-r--r--spec/lib/gitlab/database/count/exact_count_strategy_spec.rb2
-rw-r--r--spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb2
-rw-r--r--spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb2
-rw-r--r--spec/lib/gitlab/database/count_spec.rb2
-rw-r--r--spec/lib/gitlab/database/custom_structure_spec.rb2
-rw-r--r--spec/lib/gitlab/database/dynamic_model_helpers_spec.rb28
-rw-r--r--spec/lib/gitlab/database/grant_spec.rb2
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb288
-rw-r--r--spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb276
-rw-r--r--spec/lib/gitlab/database/multi_threaded_migration_spec.rb2
-rw-r--r--spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb2
-rw-r--r--spec/lib/gitlab/database/partitioning/monthly_strategy_spec.rb153
-rw-r--r--spec/lib/gitlab/database/partitioning/partition_creator_spec.rb96
-rw-r--r--spec/lib/gitlab/database/partitioning/time_partition_spec.rb174
-rw-r--r--spec/lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table_spec.rb175
-rw-r--r--spec/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers_spec.rb2
-rw-r--r--spec/lib/gitlab/database/partitioning_migration_helpers/partitioned_foreign_key_spec.rb2
-rw-r--r--spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb259
-rw-r--r--spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb2
-rw-r--r--spec/lib/gitlab/database/postgresql_adapter/schema_versions_copy_mixin_spec.rb2
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb2
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb2
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb2
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb4
-rw-r--r--spec/lib/gitlab/database/schema_cleaner_spec.rb2
-rw-r--r--spec/lib/gitlab/database/sha_attribute_spec.rb2
-rw-r--r--spec/lib/gitlab/database/with_lock_retries_spec.rb2
-rw-r--r--spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb6
-rw-r--r--spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb2
-rw-r--r--spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb2
-rw-r--r--spec/lib/gitlab/database_importers/self_monitoring/project/delete_service_spec.rb2
-rw-r--r--spec/lib/gitlab/database_spec.rb113
-rw-r--r--spec/lib/gitlab/dependency_linker/base_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/go_mod_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/go_sum_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/devise_failure_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/diff_refs_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/file_collection/commit_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/file_collection/compare_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/file_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/formatters/image_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/formatters/text_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/highlight_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/highlight_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/inline_diff_marker_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/inline_diff_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/line_mapper_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/line_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/lines_unfolder_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/parallel_diff_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/parser_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/position_collection_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/position_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb115
-rw-r--r--spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb140
-rw-r--r--spec/lib/gitlab/diff/position_tracer_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/stats_cache_spec.rb84
-rw-r--r--spec/lib/gitlab/diff/suggestion_diff_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/suggestion_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/suggestions_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/discussions_diff/file_collection_spec.rb2
-rw-r--r--spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/doctor/secrets_spec.rb2
-rw-r--r--spec/lib/gitlab/downtime_check/message_spec.rb2
-rw-r--r--spec/lib/gitlab/downtime_check_spec.rb2
-rw-r--r--spec/lib/gitlab/elasticsearch/logs/lines_spec.rb2
-rw-r--r--spec/lib/gitlab/elasticsearch/logs/pods_spec.rb2
-rw-r--r--spec/lib/gitlab/email/attachment_uploader_spec.rb2
-rw-r--r--spec/lib/gitlab/email/handler/create_issue_handler_spec.rb2
-rw-r--r--spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb2
-rw-r--r--spec/lib/gitlab/email/handler/create_note_handler_spec.rb68
-rw-r--r--spec/lib/gitlab/email/handler/service_desk_handler_spec.rb311
-rw-r--r--spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb2
-rw-r--r--spec/lib/gitlab/email/handler_spec.rb44
-rw-r--r--spec/lib/gitlab/email/hook/additional_headers_interceptor_spec.rb2
-rw-r--r--spec/lib/gitlab/email/hook/delivery_metrics_observer_spec.rb2
-rw-r--r--spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb2
-rw-r--r--spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb2
-rw-r--r--spec/lib/gitlab/email/message/repository_push_spec.rb2
-rw-r--r--spec/lib/gitlab/email/receiver_spec.rb2
-rw-r--r--spec/lib/gitlab/email/reply_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/email/service_desk_receiver_spec.rb37
-rw-r--r--spec/lib/gitlab/email/smime/certificate_spec.rb2
-rw-r--r--spec/lib/gitlab/email/smime/signer_spec.rb2
-rw-r--r--spec/lib/gitlab/emoji_spec.rb125
-rw-r--r--spec/lib/gitlab/encoding_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/error_tracking/stack_trace_highlight_decorator_spec.rb2
-rw-r--r--spec/lib/gitlab/error_tracking_spec.rb86
-rw-r--r--spec/lib/gitlab/etag_caching/middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/etag_caching/router_spec.rb2
-rw-r--r--spec/lib/gitlab/exclusive_lease_helpers/sleeping_lock_spec.rb2
-rw-r--r--spec/lib/gitlab/exclusive_lease_helpers_spec.rb2
-rw-r--r--spec/lib/gitlab/exclusive_lease_spec.rb2
-rw-r--r--spec/lib/gitlab/experimentation_spec.rb2
-rw-r--r--spec/lib/gitlab/external_authorization/access_spec.rb2
-rw-r--r--spec/lib/gitlab/external_authorization/cache_spec.rb2
-rw-r--r--spec/lib/gitlab/external_authorization/client_spec.rb2
-rw-r--r--spec/lib/gitlab/external_authorization/logger_spec.rb2
-rw-r--r--spec/lib/gitlab/external_authorization/response_spec.rb2
-rw-r--r--spec/lib/gitlab/external_authorization_spec.rb2
-rw-r--r--spec/lib/gitlab/fake_application_settings_spec.rb2
-rw-r--r--spec/lib/gitlab/file_detector_spec.rb2
-rw-r--r--spec/lib/gitlab/file_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/file_hook_spec.rb2
-rw-r--r--spec/lib/gitlab/file_markdown_link_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/file_type_detection_spec.rb2
-rw-r--r--spec/lib/gitlab/fogbugz_import/client_spec.rb2
-rw-r--r--spec/lib/gitlab/fogbugz_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/fogbugz_import/project_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/gfm/reference_rewriter_spec.rb2
-rw-r--r--spec/lib/gitlab/gfm/uploads_rewriter_spec.rb2
-rw-r--r--spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/git/attributes_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/git/blame_spec.rb2
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb2
-rw-r--r--spec/lib/gitlab/git/branch_spec.rb2
-rw-r--r--spec/lib/gitlab/git/bundle_file_spec.rb2
-rw-r--r--spec/lib/gitlab/git/changes_spec.rb2
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb32
-rw-r--r--spec/lib/gitlab/git/compare_spec.rb2
-rw-r--r--spec/lib/gitlab/git/conflict/file_spec.rb2
-rw-r--r--spec/lib/gitlab/git/conflict/parser_spec.rb2
-rw-r--r--spec/lib/gitlab/git/cross_repo_comparer_spec.rb2
-rw-r--r--spec/lib/gitlab/git/diff_collection_spec.rb2
-rw-r--r--spec/lib/gitlab/git/diff_spec.rb2
-rw-r--r--spec/lib/gitlab/git/diff_stats_collection_spec.rb6
-rw-r--r--spec/lib/gitlab/git/gitmodules_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/git/hook_env_spec.rb2
-rw-r--r--spec/lib/gitlab/git/keep_around_spec.rb2
-rw-r--r--spec/lib/gitlab/git/lfs_changes_spec.rb2
-rw-r--r--spec/lib/gitlab/git/lfs_pointer_file_spec.rb2
-rw-r--r--spec/lib/gitlab/git/merge_base_spec.rb2
-rw-r--r--spec/lib/gitlab/git/object_pool_spec.rb2
-rw-r--r--spec/lib/gitlab/git/patches/collection_spec.rb2
-rw-r--r--spec/lib/gitlab/git/patches/commit_patches_spec.rb2
-rw-r--r--spec/lib/gitlab/git/patches/patch_spec.rb2
-rw-r--r--spec/lib/gitlab/git/pre_receive_error_spec.rb2
-rw-r--r--spec/lib/gitlab/git/push_spec.rb2
-rw-r--r--spec/lib/gitlab/git/raw_diff_change_spec.rb2
-rw-r--r--spec/lib/gitlab/git/remote_mirror_spec.rb2
-rw-r--r--spec/lib/gitlab/git/remote_repository_spec.rb2
-rw-r--r--spec/lib/gitlab/git/repository_cleaner_spec.rb2
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb43
-rw-r--r--spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb2
-rw-r--r--spec/lib/gitlab/git/tag_spec.rb2
-rw-r--r--spec/lib/gitlab/git/tree_spec.rb2
-rw-r--r--spec/lib/gitlab/git/user_spec.rb2
-rw-r--r--spec/lib/gitlab/git/util_spec.rb2
-rw-r--r--spec/lib/gitlab/git/wiki_spec.rb2
-rw-r--r--spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb2
-rw-r--r--spec/lib/gitlab/git_access_design_spec.rb2
-rw-r--r--spec/lib/gitlab/git_access_project_spec.rb2
-rw-r--r--spec/lib/gitlab/git_access_snippet_spec.rb2
-rw-r--r--spec/lib/gitlab/git_access_spec.rb2
-rw-r--r--spec/lib/gitlab/git_access_wiki_spec.rb2
-rw-r--r--spec/lib/gitlab/git_post_receive_spec.rb2
-rw-r--r--spec/lib/gitlab/git_ref_validator_spec.rb2
-rw-r--r--spec/lib/gitlab/git_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/blob_service_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/call_spec.rb122
-rw-r--r--spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb29
-rw-r--r--spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/diff_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/health_check_service_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/operation_service_spec.rb16
-rw-r--r--spec/lib/gitlab/gitaly_client/praefect_info_service_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/ref_service_spec.rb47
-rw-r--r--spec/lib/gitlab/gitaly_client/remote_service_spec.rb5
-rw-r--r--spec/lib/gitlab/gitaly_client/repository_service_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/storage_settings_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/util_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/wiki_service_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb4
-rw-r--r--spec/lib/gitlab/github_import/bulk_importing_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/client_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/diff_notes_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/issue_and_label_links_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/issue_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/issues_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/label_links_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/labels_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/lfs_object_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/note_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/notes_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/releases_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/repository_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/issuable_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/label_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/markdown_text_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/milestone_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/page_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/parallel_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/parallel_scheduling_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/representation/diff_note_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/representation/expose_attribute_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/representation/issue_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/representation/note_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/representation/pull_request_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/representation/to_hash_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/representation/user_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/representation_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/sequential_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/user_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import_spec.rb2
-rw-r--r--spec/lib/gitlab/gitlab_import/client_spec.rb2
-rw-r--r--spec/lib/gitlab/gitlab_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/gitlab_import/project_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/gl_repository/identifier_spec.rb63
-rw-r--r--spec/lib/gitlab/gl_repository/repo_type_spec.rb2
-rw-r--r--spec/lib/gitlab/gl_repository_spec.rb2
-rw-r--r--spec/lib/gitlab/global_id_spec.rb35
-rw-r--r--spec/lib/gitlab/gon_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/google_code_import/client_spec.rb2
-rw-r--r--spec/lib/gitlab/google_code_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/google_code_import/project_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/gpg/commit_spec.rb2
-rw-r--r--spec/lib/gitlab/gpg_spec.rb4
-rw-r--r--spec/lib/gitlab/grape_logging/formatters/lograge_with_timestamp_spec.rb2
-rw-r--r--spec/lib/gitlab/grape_logging/loggers/cloudflare_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/grape_logging/loggers/exception_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/grape_logging/loggers/perf_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/grape_logging/loggers/queue_duration_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/calls_gitaly/instrumentation_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/copy_field_description_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/docs/renderer_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/find_argument_in_parent_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/generic_tracing_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/loaders/batch_project_statistics_loader_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/loaders/issuable_loader_spec.rb96
-rw-r--r--spec/lib/gitlab/graphql/markdown_field/resolver_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/markdown_field_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/mount_mutation_spec.rb63
-rw-r--r--spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/conditions/not_null_condition_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/conditions/null_condition_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/order_info_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/query_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb20
-rw-r--r--spec/lib/gitlab/graphql/representation/submodule_tree_entry_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/representation/tree_entry_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/timeout_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/graphs/commits_spec.rb2
-rw-r--r--spec/lib/gitlab/group_search_results_spec.rb2
-rw-r--r--spec/lib/gitlab/hashed_storage/migrator_spec.rb2
-rw-r--r--spec/lib/gitlab/health_checks/db_check_spec.rb2
-rw-r--r--spec/lib/gitlab/health_checks/gitaly_check_spec.rb2
-rw-r--r--spec/lib/gitlab/health_checks/master_check_spec.rb2
-rw-r--r--spec/lib/gitlab/health_checks/probes/collection_spec.rb16
-rw-r--r--spec/lib/gitlab/health_checks/puma_check_spec.rb2
-rw-r--r--spec/lib/gitlab/health_checks/redis/cache_check_spec.rb2
-rw-r--r--spec/lib/gitlab/health_checks/redis/queues_check_spec.rb2
-rw-r--r--spec/lib/gitlab/health_checks/redis/redis_check_spec.rb2
-rw-r--r--spec/lib/gitlab/health_checks/redis/shared_state_check_spec.rb2
-rw-r--r--spec/lib/gitlab/health_checks/simple_check_shared.rb2
-rw-r--r--spec/lib/gitlab/health_checks/unicorn_check_spec.rb2
-rw-r--r--spec/lib/gitlab/highlight_spec.rb2
-rw-r--r--spec/lib/gitlab/hook_data/base_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/hook_data/issuable_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/hook_data/issue_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/hook_data/merge_request_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/http_connection_adapter_spec.rb2
-rw-r--r--spec/lib/gitlab/http_io_spec.rb2
-rw-r--r--spec/lib/gitlab/http_spec.rb2
-rw-r--r--spec/lib/gitlab/i18n/metadata_entry_spec.rb2
-rw-r--r--spec/lib/gitlab/i18n/po_linter_spec.rb2
-rw-r--r--spec/lib/gitlab/i18n/translation_entry_spec.rb2
-rw-r--r--spec/lib/gitlab/i18n_spec.rb2
-rw-r--r--spec/lib/gitlab/identifier_spec.rb2
-rw-r--r--spec/lib/gitlab/import/database_helpers_spec.rb2
-rw-r--r--spec/lib/gitlab/import/merge_request_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/import/merge_request_helpers_spec.rb2
-rw-r--r--spec/lib/gitlab/import/metrics_spec.rb70
-rw-r--r--spec/lib/gitlab/import/set_async_jid_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml15
-rw-r--r--spec/lib/gitlab/import_export/attribute_cleaner_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/attribute_configuration_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/attributes_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/attributes_permitter_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/avatar_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/avatar_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/base/object_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/base/relation_factory_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/command_line_util_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/config_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/design_repo_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/design_repo_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/error_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb10
-rw-r--r--spec/lib/gitlab/import_export/file_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/fork_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/group/legacy_tree_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/group/object_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/group/relation_factory_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/group/tree_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/group/tree_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/hash_util_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/import_export_equivalence_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/import_export_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/import_failure_service_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/import_test_coverage_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/json/legacy_reader/file_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/json/legacy_reader/hash_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/json/legacy_writer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/json/ndjson_reader_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb16
-rw-r--r--spec/lib/gitlab/import_export/legacy_relation_tree_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/lfs_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/lfs_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/members_mapper_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/merge_request_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/model_configuration_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/project/export_task_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/project/import_task_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/project/object_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/project/relation_factory_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/project/tree_restorer_spec.rb23
-rw-r--r--spec/lib/gitlab/import_export/project/tree_saver_spec.rb15
-rw-r--r--spec/lib/gitlab/import_export/reader_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/references_configuration_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/relation_tree_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/repo_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/repo_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml32
-rw-r--r--spec/lib/gitlab/import_export/saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/shared_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb26
-rw-r--r--spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/uploads_manager_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/uploads_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/uploads_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/version_checker_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/wiki_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/import_sources_spec.rb2
-rw-r--r--spec/lib/gitlab/incident_management/pager_duty/incident_issue_description_spec.rb97
-rw-r--r--spec/lib/gitlab/incoming_email_spec.rb2
-rw-r--r--spec/lib/gitlab/insecure_key_fingerprint_spec.rb2
-rw-r--r--spec/lib/gitlab/instrumentation/redis_base_spec.rb2
-rw-r--r--spec/lib/gitlab/instrumentation/redis_cluster_validator_spec.rb93
-rw-r--r--spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb71
-rw-r--r--spec/lib/gitlab/instrumentation/redis_spec.rb2
-rw-r--r--spec/lib/gitlab/instrumentation_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/internal_post_receive/response_spec.rb2
-rw-r--r--spec/lib/gitlab/issuable_metadata_spec.rb2
-rw-r--r--spec/lib/gitlab/issuable_sorter_spec.rb2
-rw-r--r--spec/lib/gitlab/issuables_count_for_state_spec.rb2
-rw-r--r--spec/lib/gitlab/jira_import/base_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/jira_import/handle_labels_service_spec.rb2
-rw-r--r--spec/lib/gitlab/jira_import/issue_serializer_spec.rb41
-rw-r--r--spec/lib/gitlab/jira_import/issues_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/jira_import/labels_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/jira_import/metadata_collector_spec.rb2
-rw-r--r--spec/lib/gitlab/jira_import/user_mapper_spec.rb80
-rw-r--r--spec/lib/gitlab/jira_import_spec.rb2
-rw-r--r--spec/lib/gitlab/job_waiter_spec.rb2
-rw-r--r--spec/lib/gitlab/json_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/json_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/json_spec.rb429
-rw-r--r--spec/lib/gitlab/jwt_authenticatable_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/config_map_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/config_maps/aws_node_auth_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/default_namespace_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/generic_secret_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/api_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/base_command_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/certificate_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/init_command_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/install_command_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/parsers/list_v2_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/patch_command_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/pod_spec.rb4
-rw-r--r--spec/lib/gitlab/kubernetes/helm/reset_command_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/kube_client_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/namespace_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/network_policy_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/node_spec.rb68
-rw-r--r--spec/lib/gitlab/kubernetes/role_binding_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/role_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/service_account_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/service_account_token_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/tls_secret_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes_spec.rb2
-rw-r--r--spec/lib/gitlab/language_data_spec.rb2
-rw-r--r--spec/lib/gitlab/language_detection_spec.rb2
-rw-r--r--spec/lib/gitlab/lazy_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/branch_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/client_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/comment_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/issuable_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/label_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/project_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/lets_encrypt/challenge_spec.rb2
-rw-r--r--spec/lib/gitlab/lets_encrypt/client_spec.rb2
-rw-r--r--spec/lib/gitlab/lets_encrypt/order_spec.rb2
-rw-r--r--spec/lib/gitlab/lets_encrypt_spec.rb2
-rw-r--r--spec/lib/gitlab/lfs_token_spec.rb2
-rw-r--r--spec/lib/gitlab/log_timestamp_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/logging/cloudflare_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/lograge/custom_options_spec.rb18
-rw-r--r--spec/lib/gitlab/loop_helpers_spec.rb2
-rw-r--r--spec/lib/gitlab/mail_room/mail_room_spec.rb2
-rw-r--r--spec/lib/gitlab/manifest_import/manifest_spec.rb2
-rw-r--r--spec/lib/gitlab/manifest_import/project_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb2
-rw-r--r--spec/lib/gitlab/markdown_cache/field_data_spec.rb2
-rw-r--r--spec/lib/gitlab/markdown_cache/redis/extension_spec.rb27
-rw-r--r--spec/lib/gitlab/markdown_cache/redis/store_spec.rb24
-rw-r--r--spec/lib/gitlab/markup_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/background_transaction_spec.rb13
-rw-r--r--spec/lib/gitlab/metrics/dashboard/defaults_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/dashboard/finder_spec.rb16
-rw-r--r--spec/lib/gitlab/metrics/dashboard/processor_spec.rb31
-rw-r--r--spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb42
-rw-r--r--spec/lib/gitlab/metrics/dashboard/stages/grafana_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb6
-rw-r--r--spec/lib/gitlab/metrics/dashboard/stages/url_validator_spec.rb101
-rw-r--r--spec/lib/gitlab/metrics/dashboard/stages/variable_endpoint_inserter_spec.rb77
-rw-r--r--spec/lib/gitlab/metrics/dashboard/url_spec.rb31
-rw-r--r--spec/lib/gitlab/metrics/delta_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/elasticsearch_rack_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/exporter/sidekiq_exporter_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/instrumentation_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/method_call_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/methods_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/prometheus_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/rack_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/redis_rack_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/samplers/database_sampler_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb31
-rw-r--r--spec/lib/gitlab/metrics/subscribers/action_view_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/subscribers/active_record_spec.rb233
-rw-r--r--spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/system_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/transaction_spec.rb13
-rw-r--r--spec/lib/gitlab/metrics/web_transaction_spec.rb34
-rw-r--r--spec/lib/gitlab/metrics_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/basic_health_check_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/go_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/handle_ip_spoof_attack_error_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/multipart_spec.rb80
-rw-r--r--spec/lib/gitlab/middleware/rails_queue_duration_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/read_only_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/release_env_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/request_context_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/same_site_cookies_spec.rb2
-rw-r--r--spec/lib/gitlab/monitor/demo_projects_spec.rb2
-rw-r--r--spec/lib/gitlab/multi_collection_paginator_spec.rb2
-rw-r--r--spec/lib/gitlab/multi_destination_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/namespaced_session_store_spec.rb2
-rw-r--r--spec/lib/gitlab/no_cache_headers_spec.rb2
-rw-r--r--spec/lib/gitlab/noteable_metadata_spec.rb2
-rw-r--r--spec/lib/gitlab/null_request_store_spec.rb2
-rw-r--r--spec/lib/gitlab/object_hierarchy_spec.rb2
-rw-r--r--spec/lib/gitlab/octokit/middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/omniauth_initializer_spec.rb2
-rw-r--r--spec/lib/gitlab/omniauth_logging/json_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/optimistic_locking_spec.rb2
-rw-r--r--spec/lib/gitlab/other_markup_spec.rb2
-rw-r--r--spec/lib/gitlab/otp_key_rotator_spec.rb2
-rw-r--r--spec/lib/gitlab/pages_spec.rb2
-rw-r--r--spec/lib/gitlab/pagination/keyset/page_spec.rb2
-rw-r--r--spec/lib/gitlab/pagination/keyset/pager_spec.rb2
-rw-r--r--spec/lib/gitlab/pagination/keyset/request_context_spec.rb2
-rw-r--r--spec/lib/gitlab/pagination/keyset_spec.rb2
-rw-r--r--spec/lib/gitlab/pagination/offset_pagination_spec.rb2
-rw-r--r--spec/lib/gitlab/patch/action_dispatch_journey_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/patch/draw_route_spec.rb2
-rw-r--r--spec/lib/gitlab/patch/prependable_spec.rb2
-rw-r--r--spec/lib/gitlab/path_regex_spec.rb2
-rw-r--r--spec/lib/gitlab/performance_bar/with_top_level_warnings_spec.rb2
-rw-r--r--spec/lib/gitlab/performance_bar_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/cache/map_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/client_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/response_spec.rb4
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/user_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/users_response_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/issues/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/project_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/representation/task_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/representation/user_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/user_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/worker_state_spec.rb2
-rw-r--r--spec/lib/gitlab/polling_interval_spec.rb2
-rw-r--r--spec/lib/gitlab/popen/runner_spec.rb2
-rw-r--r--spec/lib/gitlab/popen_spec.rb2
-rw-r--r--spec/lib/gitlab/private_commit_email_spec.rb2
-rw-r--r--spec/lib/gitlab/process_memory_cache/helper_spec.rb2
-rw-r--r--spec/lib/gitlab/profiler_spec.rb3
-rw-r--r--spec/lib/gitlab/project_authorizations_spec.rb2
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb2
-rw-r--r--spec/lib/gitlab/project_template_spec.rb4
-rw-r--r--spec/lib/gitlab/project_transfer_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/adapter_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/internal_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/metric_group_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/validate_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/query_variables_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus_client_spec.rb40
-rw-r--r--spec/lib/gitlab/puma_logging/json_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/push_options_spec.rb2
-rw-r--r--spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb2
-rw-r--r--spec/lib/gitlab/query_limiting/middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/query_limiting/transaction_spec.rb2
-rw-r--r--spec/lib/gitlab/query_limiting_spec.rb2
-rw-r--r--spec/lib/gitlab/quick_actions/command_definition_spec.rb2
-rw-r--r--spec/lib/gitlab/quick_actions/dsl_spec.rb2
-rw-r--r--spec/lib/gitlab/quick_actions/extractor_spec.rb2
-rw-r--r--spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb2
-rw-r--r--spec/lib/gitlab/quick_actions/substitution_definition_spec.rb2
-rw-r--r--spec/lib/gitlab/rate_limit_helpers_spec.rb2
-rw-r--r--spec/lib/gitlab/reactive_cache_set_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/redis/boolean_spec.rb2
-rw-r--r--spec/lib/gitlab/redis/cache_spec.rb2
-rw-r--r--spec/lib/gitlab/redis/queues_spec.rb2
-rw-r--r--spec/lib/gitlab/redis/shared_state_spec.rb2
-rw-r--r--spec/lib/gitlab/redis/wrapper_spec.rb2
-rw-r--r--spec/lib/gitlab/reference_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb2
-rw-r--r--spec/lib/gitlab/regex_spec.rb35
-rw-r--r--spec/lib/gitlab/repo_path_spec.rb2
-rw-r--r--spec/lib/gitlab/repository_cache_adapter_spec.rb2
-rw-r--r--spec/lib/gitlab/repository_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/repository_hash_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/repository_set_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/repository_size_checker_spec.rb2
-rw-r--r--spec/lib/gitlab/repository_size_error_message_spec.rb2
-rw-r--r--spec/lib/gitlab/repository_url_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/request_context_spec.rb2
-rw-r--r--spec/lib/gitlab/request_forgery_protection_spec.rb2
-rw-r--r--spec/lib/gitlab/request_profiler/profile_spec.rb2
-rw-r--r--spec/lib/gitlab/request_profiler_spec.rb2
-rw-r--r--spec/lib/gitlab/route_map_spec.rb2
-rw-r--r--spec/lib/gitlab/routing_spec.rb2
-rw-r--r--spec/lib/gitlab/rugged_instrumentation_spec.rb2
-rw-r--r--spec/lib/gitlab/runtime_spec.rb44
-rw-r--r--spec/lib/gitlab/safe_request_store_spec.rb2
-rw-r--r--spec/lib/gitlab/sanitizers/exif_spec.rb2
-rw-r--r--spec/lib/gitlab/sanitizers/svg_spec.rb2
-rw-r--r--spec/lib/gitlab/search/found_blob_spec.rb2
-rw-r--r--spec/lib/gitlab/search/found_wiki_page_spec.rb2
-rw-r--r--spec/lib/gitlab/search/query_spec.rb2
-rw-r--r--spec/lib/gitlab/search_context/builder_spec.rb2
-rw-r--r--spec/lib/gitlab/search_context/controller_concern_spec.rb2
-rw-r--r--spec/lib/gitlab/search_results_spec.rb2
-rw-r--r--spec/lib/gitlab/serializer/ci/variables_spec.rb2
-rw-r--r--spec/lib/gitlab/serializer/pagination_spec.rb2
-rw-r--r--spec/lib/gitlab/serverless/service_spec.rb2
-rw-r--r--spec/lib/gitlab/service_desk_email_spec.rb59
-rw-r--r--spec/lib/gitlab/service_desk_spec.rb56
-rw-r--r--spec/lib/gitlab/session_spec.rb2
-rw-r--r--spec/lib/gitlab/shard_health_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/shell_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/collection_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/file_sample_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/line_profiler_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/line_sample_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/location_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/query_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/transaction_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_cluster/cli_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_cluster_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_config/worker_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_config_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_daemon/monitor_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_logging/deduplication_logger_spec.rb7
-rw-r--r--spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing_spec.rb16
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/extra_done_log_metadata_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/worker_context/client_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_queue_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_signals_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_status_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_versioning/manager_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_versioning_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/application_help_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/command_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/deploy_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/issue_close_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/issue_comment_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/issue_move_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/issue_new_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/issue_search_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/issue_show_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/access_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/error_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_close_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/run_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/run_spec.rb2
-rw-r--r--spec/lib/gitlab/slug/environment_spec.rb2
-rw-r--r--spec/lib/gitlab/snippet_search_results_spec.rb2
-rw-r--r--spec/lib/gitlab/sourcegraph_spec.rb2
-rw-r--r--spec/lib/gitlab/sql/cte_spec.rb2
-rw-r--r--spec/lib/gitlab/sql/glob_spec.rb2
-rw-r--r--spec/lib/gitlab/sql/pattern_spec.rb2
-rw-r--r--spec/lib/gitlab/sql/recursive_cte_spec.rb2
-rw-r--r--spec/lib/gitlab/sql/union_spec.rb2
-rw-r--r--spec/lib/gitlab/ssh_public_key_spec.rb2
-rw-r--r--spec/lib/gitlab/static_site_editor/config_spec.rb33
-rw-r--r--spec/lib/gitlab/string_placeholder_replacer_spec.rb2
-rw-r--r--spec/lib/gitlab/string_range_marker_spec.rb2
-rw-r--r--spec/lib/gitlab/string_regex_marker_spec.rb2
-rw-r--r--spec/lib/gitlab/submodule_links_spec.rb2
-rw-r--r--spec/lib/gitlab/suggestions/commit_message_spec.rb2
-rw-r--r--spec/lib/gitlab/suggestions/file_suggestion_spec.rb339
-rw-r--r--spec/lib/gitlab/suggestions/suggestion_set_spec.rb7
-rw-r--r--spec/lib/gitlab/tab_width_spec.rb2
-rw-r--r--spec/lib/gitlab/tcp_checker_spec.rb2
-rw-r--r--spec/lib/gitlab/template/finders/global_template_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/template/finders/repo_template_finders_spec.rb2
-rw-r--r--spec/lib/gitlab/template/gitignore_template_spec.rb2
-rw-r--r--spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb2
-rw-r--r--spec/lib/gitlab/template/issue_template_spec.rb2
-rw-r--r--spec/lib/gitlab/template/merge_request_template_spec.rb2
-rw-r--r--spec/lib/gitlab/themes_spec.rb2
-rw-r--r--spec/lib/gitlab/throttle_spec.rb2
-rw-r--r--spec/lib/gitlab/time_tracking_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/tracking/incident_management_spec.rb2
-rw-r--r--spec/lib/gitlab/tracking_spec.rb2
-rw-r--r--spec/lib/gitlab/tree_summary_spec.rb27
-rw-r--r--spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb2
-rw-r--r--spec/lib/gitlab/untrusted_regexp_spec.rb2
-rw-r--r--spec/lib/gitlab/updated_notes_paginator_spec.rb57
-rw-r--r--spec/lib/gitlab/uploads_transfer_spec.rb2
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb2
-rw-r--r--spec/lib/gitlab/url_blockers/domain_whitelist_entry_spec.rb2
-rw-r--r--spec/lib/gitlab/url_blockers/ip_whitelist_entry_spec.rb2
-rw-r--r--spec/lib/gitlab/url_blockers/url_whitelist_spec.rb2
-rw-r--r--spec/lib/gitlab/url_builder_spec.rb39
-rw-r--r--spec/lib/gitlab/url_sanitizer_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data/topology_spec.rb562
-rw-r--r--spec/lib/gitlab/usage_data_concerns/topology_spec.rb220
-rw-r--r--spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/designs_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/merge_request_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/note_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/productivity_analytics_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/search_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/track_unique_actions_spec.rb82
-rw-r--r--spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb453
-rw-r--r--spec/lib/gitlab/user_access_snippet_spec.rb2
-rw-r--r--spec/lib/gitlab/user_access_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/deep_size_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/inline_hash_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/json_size_estimator_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/lazy_attributes_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/log_limited_array_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/markdown_spec.rb63
-rw-r--r--spec/lib/gitlab/utils/measuring_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/merge_hash_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/override_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/safe_inline_hash_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/sanitize_node_link_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/strong_memoize_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/usage_data_spec.rb22
-rw-r--r--spec/lib/gitlab/utils_spec.rb38
-rw-r--r--spec/lib/gitlab/verify/job_artifacts_spec.rb2
-rw-r--r--spec/lib/gitlab/verify/lfs_objects_spec.rb2
-rw-r--r--spec/lib/gitlab/verify/uploads_spec.rb2
-rw-r--r--spec/lib/gitlab/version_info_spec.rb2
-rw-r--r--spec/lib/gitlab/view/presenter/base_spec.rb2
-rw-r--r--spec/lib/gitlab/view/presenter/delegated_spec.rb2
-rw-r--r--spec/lib/gitlab/view/presenter/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/view/presenter/simple_spec.rb2
-rw-r--r--spec/lib/gitlab/visibility_level_checker_spec.rb2
-rw-r--r--spec/lib/gitlab/visibility_level_spec.rb2
-rw-r--r--spec/lib/gitlab/web_ide/config/entry/global_spec.rb2
-rw-r--r--spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb2
-rw-r--r--spec/lib/gitlab/web_ide/config_spec.rb2
-rw-r--r--spec/lib/gitlab/wiki_file_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/with_request_store_spec.rb2
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb2
-rw-r--r--spec/lib/gitlab/x509/commit_spec.rb2
-rw-r--r--spec/lib/gitlab/x509/signature_spec.rb2
-rw-r--r--spec/lib/gitlab/x509/tag_spec.rb2
-rw-r--r--spec/lib/gitlab/zoom_link_extractor_spec.rb2
-rw-r--r--spec/lib/gitlab_danger_spec.rb2
-rw-r--r--spec/lib/gitlab_spec.rb2
-rw-r--r--spec/lib/google_api/auth_spec.rb2
-rw-r--r--spec/lib/google_api/cloud_platform/client_spec.rb2
-rw-r--r--spec/lib/grafana/client_spec.rb2
-rw-r--r--spec/lib/grafana/time_window_spec.rb6
-rw-r--r--spec/lib/grafana/validator_spec.rb2
-rw-r--r--spec/lib/json_web_token/hmac_token_spec.rb2
-rw-r--r--spec/lib/json_web_token/rsa_token_spec.rb2
-rw-r--r--spec/lib/json_web_token/token_spec.rb2
-rw-r--r--spec/lib/kramdown/parser/atlassian_document_format_spec.rb77
-rw-r--r--spec/lib/learn_gitlab_spec.rb61
-rw-r--r--spec/lib/marginalia_spec.rb2
-rw-r--r--spec/lib/mattermost/client_spec.rb2
-rw-r--r--spec/lib/mattermost/command_spec.rb2
-rw-r--r--spec/lib/mattermost/session_spec.rb2
-rw-r--r--spec/lib/mattermost/team_spec.rb2
-rw-r--r--spec/lib/microsoft_teams/activity_spec.rb2
-rw-r--r--spec/lib/microsoft_teams/notifier_spec.rb2
-rw-r--r--spec/lib/object_storage/direct_upload_spec.rb45
-rw-r--r--spec/lib/omni_auth/strategies/jwt_spec.rb2
-rw-r--r--spec/lib/pager_duty/webhook_payload_parser_spec.rb80
-rw-r--r--spec/lib/peek/views/bullet_detailed_spec.rb2
-rw-r--r--spec/lib/peek/views/detailed_view_spec.rb2
-rw-r--r--spec/lib/peek/views/redis_detailed_spec.rb2
-rw-r--r--spec/lib/peek/views/rugged_spec.rb2
-rw-r--r--spec/lib/product_analytics/event_params_spec.rb54
-rw-r--r--spec/lib/prometheus/cleanup_multiproc_dir_service_spec.rb2
-rw-r--r--spec/lib/prometheus/pid_provider_spec.rb2
-rw-r--r--spec/lib/quality/test_level_spec.rb4
-rw-r--r--spec/lib/rspec_flaky/config_spec.rb2
-rw-r--r--spec/lib/rspec_flaky/example_spec.rb2
-rw-r--r--spec/lib/rspec_flaky/flaky_example_spec.rb2
-rw-r--r--spec/lib/rspec_flaky/flaky_examples_collection_spec.rb2
-rw-r--r--spec/lib/rspec_flaky/listener_spec.rb2
-rw-r--r--spec/lib/rspec_flaky/report_spec.rb2
-rw-r--r--spec/lib/safe_zip/entry_spec.rb2
-rw-r--r--spec/lib/safe_zip/extract_params_spec.rb2
-rw-r--r--spec/lib/safe_zip/extract_spec.rb2
-rw-r--r--spec/lib/sentry/api_urls_spec.rb2
-rw-r--r--spec/lib/sentry/client/event_spec.rb2
-rw-r--r--spec/lib/sentry/client/issue_link_spec.rb2
-rw-r--r--spec/lib/sentry/client/issue_spec.rb3
-rw-r--r--spec/lib/sentry/client/projects_spec.rb2
-rw-r--r--spec/lib/sentry/client/repo_spec.rb2
-rw-r--r--spec/lib/sentry/client_spec.rb2
-rw-r--r--spec/lib/sentry/pagination_parser_spec.rb5
-rw-r--r--spec/lib/serializers/json_spec.rb2
-rw-r--r--spec/lib/system_check/app/authorized_keys_permission_check_spec.rb2
-rw-r--r--spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb2
-rw-r--r--spec/lib/system_check/app/hashed_storage_all_projects_check_spec.rb2
-rw-r--r--spec/lib/system_check/app/hashed_storage_enabled_check_spec.rb2
-rw-r--r--spec/lib/system_check/base_check_spec.rb2
-rw-r--r--spec/lib/system_check/orphans/namespace_check_spec.rb2
-rw-r--r--spec/lib/system_check/orphans/repository_check_spec.rb2
-rw-r--r--spec/lib/system_check/simple_executor_spec.rb2
-rw-r--r--spec/lib/system_check_spec.rb2
-rw-r--r--spec/lib/uploaded_file_spec.rb2
-rw-r--r--spec/mailers/abuse_report_mailer_spec.rb2
-rw-r--r--spec/mailers/devise_mailer_spec.rb38
-rw-r--r--spec/mailers/email_rejection_mailer_spec.rb2
-rw-r--r--spec/mailers/emails/auto_devops_spec.rb2
-rw-r--r--spec/mailers/emails/groups_spec.rb2
-rw-r--r--spec/mailers/emails/issues_spec.rb2
-rw-r--r--spec/mailers/emails/merge_requests_spec.rb18
-rw-r--r--spec/mailers/emails/pages_domains_spec.rb2
-rw-r--r--spec/mailers/emails/pipelines_spec.rb2
-rw-r--r--spec/mailers/emails/profile_spec.rb2
-rw-r--r--spec/mailers/emails/projects_spec.rb2
-rw-r--r--spec/mailers/emails/releases_spec.rb2
-rw-r--r--spec/mailers/emails/service_desk_spec.rb188
-rw-r--r--spec/mailers/notify_spec.rb83
-rw-r--r--spec/mailers/repository_check_mailer_spec.rb2
-rw-r--r--spec/migrations/20190924152703_migrate_issue_trackers_data_spec.rb2
-rw-r--r--spec/migrations/20191015154408_drop_merge_requests_require_code_owner_approval_from_projects_spec.rb2
-rw-r--r--spec/migrations/20191125114345_add_admin_mode_protected_path_spec.rb2
-rw-r--r--spec/migrations/20191204114127_delete_legacy_triggers_spec.rb2
-rw-r--r--spec/migrations/20200107172020_add_timestamp_softwarelicensespolicy_spec.rb2
-rw-r--r--spec/migrations/20200122123016_backfill_project_settings_spec.rb2
-rw-r--r--spec/migrations/20200123155929_remove_invalid_jira_data_spec.rb2
-rw-r--r--spec/migrations/20200127090233_remove_invalid_issue_tracker_data_spec.rb2
-rw-r--r--spec/migrations/20200130145430_reschedule_migrate_issue_trackers_data_spec.rb2
-rw-r--r--spec/migrations/20200313203550_remove_orphaned_chat_names_spec.rb2
-rw-r--r--spec/migrations/20200406102120_backfill_deployment_clusters_from_deployments_spec.rb2
-rw-r--r--spec/migrations/20200511145545_change_variable_interpolation_format_in_common_metrics_spec.rb13
-rw-r--r--spec/migrations/20200526115436_dedup_mr_metrics_spec.rb68
-rw-r--r--spec/migrations/20200526231421_update_index_approval_rule_name_for_code_owners_rule_type_spec.rb175
-rw-r--r--spec/migrations/20200703125016_backfill_namespace_settings_spec.rb30
-rw-r--r--spec/migrations/20200706035141_adjust_unique_index_alert_management_alerts_spec.rb57
-rw-r--r--spec/migrations/active_record/schema_spec.rb4
-rw-r--r--spec/migrations/add_default_and_free_plans_spec.rb2
-rw-r--r--spec/migrations/add_default_value_stream_to_groups_with_group_stages_spec.rb44
-rw-r--r--spec/migrations/add_deploy_token_type_to_deploy_tokens_spec.rb2
-rw-r--r--spec/migrations/add_incident_settings_to_all_existing_projects_spec.rb4
-rw-r--r--spec/migrations/add_temporary_partial_index_on_project_id_to_services_spec.rb2
-rw-r--r--spec/migrations/add_unique_constraint_to_approvals_user_id_and_merge_request_id_spec.rb2
-rw-r--r--spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb2
-rw-r--r--spec/migrations/backfill_imported_snippet_repositories_spec.rb2
-rw-r--r--spec/migrations/backfill_operations_feature_flags_active_spec.rb2
-rw-r--r--spec/migrations/backfill_operations_feature_flags_iid_spec.rb2
-rw-r--r--spec/migrations/backfill_releases_table_updated_at_and_add_not_null_constraints_to_timestamps_spec.rb2
-rw-r--r--spec/migrations/backfill_snippet_repositories_spec.rb2
-rw-r--r--spec/migrations/backfill_status_page_published_incidents_spec.rb2
-rw-r--r--spec/migrations/backport_enterprise_schema_spec.rb2
-rw-r--r--spec/migrations/cap_designs_filename_length_to_new_limit_spec.rb2
-rw-r--r--spec/migrations/change_outbound_local_requests_whitelist_default_spec.rb2
-rw-r--r--spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb2
-rw-r--r--spec/migrations/clean_grafana_url_spec.rb2
-rw-r--r--spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb2
-rw-r--r--spec/migrations/cleanup_empty_commit_user_mentions_spec.rb2
-rw-r--r--spec/migrations/cleanup_legacy_artifact_migration_spec.rb2
-rw-r--r--spec/migrations/cleanup_optimistic_locking_nulls_pt2_fixed_spec.rb2
-rw-r--r--spec/migrations/cleanup_optimistic_locking_nulls_spec.rb2
-rw-r--r--spec/migrations/cleanup_projects_with_missing_namespace_spec.rb2
-rw-r--r--spec/migrations/confirm_project_bot_users_spec.rb84
-rw-r--r--spec/migrations/create_environment_for_self_monitoring_project_spec.rb2
-rw-r--r--spec/migrations/delete_internal_ids_where_feature_flags_usage_spec.rb2
-rw-r--r--spec/migrations/delete_template_project_services_spec.rb2
-rw-r--r--spec/migrations/delete_template_services_duplicated_by_type_spec.rb2
-rw-r--r--spec/migrations/delete_user_callout_alerts_moved_spec.rb30
-rw-r--r--spec/migrations/drop_activate_prometheus_services_background_jobs_spec.rb2
-rw-r--r--spec/migrations/drop_background_migration_jobs_spec.rb2
-rw-r--r--spec/migrations/drop_project_ci_cd_settings_merge_trains_enabled_spec.rb2
-rw-r--r--spec/migrations/encrypt_feature_flags_clients_tokens_spec.rb2
-rw-r--r--spec/migrations/encrypt_plaintext_attributes_on_application_settings_spec.rb2
-rw-r--r--spec/migrations/enqueue_reset_merge_status_second_run_spec.rb2
-rw-r--r--spec/migrations/enqueue_reset_merge_status_spec.rb2
-rw-r--r--spec/migrations/fill_file_store_ci_job_artifacts_spec.rb2
-rw-r--r--spec/migrations/fill_file_store_lfs_objects_spec.rb2
-rw-r--r--spec/migrations/fill_productivity_analytics_start_date_spec.rb2
-rw-r--r--spec/migrations/fill_store_uploads_spec.rb2
-rw-r--r--spec/migrations/fix_max_pages_size_spec.rb2
-rw-r--r--spec/migrations/fix_null_type_labels_spec.rb2
-rw-r--r--spec/migrations/fix_pool_repository_source_project_id_spec.rb2
-rw-r--r--spec/migrations/fix_projects_without_project_feature_spec.rb2
-rw-r--r--spec/migrations/fix_projects_without_prometheus_services_spec.rb2
-rw-r--r--spec/migrations/fix_wrong_pages_access_level_spec.rb2
-rw-r--r--spec/migrations/generate_lets_encrypt_private_key_spec.rb2
-rw-r--r--spec/migrations/generate_missing_routes_for_bots_spec.rb80
-rw-r--r--spec/migrations/insert_project_hooks_plan_limits_spec.rb2
-rw-r--r--spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb2
-rw-r--r--spec/migrations/migrate_bot_type_to_user_type_spec.rb2
-rw-r--r--spec/migrations/migrate_code_owner_approval_status_to_protected_branches_in_batches_spec.rb2
-rw-r--r--spec/migrations/migrate_commit_notes_mentions_to_db_spec.rb2
-rw-r--r--spec/migrations/migrate_create_commit_signature_worker_sidekiq_queue_spec.rb2
-rw-r--r--spec/migrations/migrate_discussion_id_on_promoted_epics_spec.rb2
-rw-r--r--spec/migrations/migrate_k8s_service_integration_spec.rb2
-rw-r--r--spec/migrations/migrate_legacy_managed_clusters_to_unmanaged_spec.rb2
-rw-r--r--spec/migrations/migrate_managed_clusters_with_no_token_to_unmanaged_spec.rb2
-rw-r--r--spec/migrations/migrate_merge_request_mentions_to_db_spec.rb2
-rw-r--r--spec/migrations/migrate_ops_feature_flags_scopes_target_user_ids_spec.rb2
-rw-r--r--spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb2
-rw-r--r--spec/migrations/migrate_store_security_reports_sidekiq_queue_spec.rb2
-rw-r--r--spec/migrations/migrate_sync_security_reports_to_report_approval_rules_sidekiq_queue_spec.rb2
-rw-r--r--spec/migrations/move_limits_from_plans_spec.rb2
-rw-r--r--spec/migrations/nullify_users_role_spec.rb2
-rw-r--r--spec/migrations/populate_project_statistics_packages_size_spec.rb2
-rw-r--r--spec/migrations/populate_rule_type_on_approval_merge_request_rules_spec.rb2
-rw-r--r--spec/migrations/remove_additional_application_settings_rows_spec.rb2
-rw-r--r--spec/migrations/remove_deprecated_jenkins_service_records_spec.rb6
-rw-r--r--spec/migrations/remove_duplicate_labels_from_project_spec.rb238
-rw-r--r--spec/migrations/remove_empty_github_service_templates_spec.rb2
-rw-r--r--spec/migrations/remove_gitlab_issue_tracker_service_records_spec.rb19
-rw-r--r--spec/migrations/remove_orphaned_invited_members_spec.rb2
-rw-r--r--spec/migrations/remove_packages_deprecated_dependencies_spec.rb2
-rw-r--r--spec/migrations/remove_security_dashboard_feature_flag_spec.rb2
-rw-r--r--spec/migrations/rename_security_dashboard_feature_flag_to_instance_security_dashboard_spec.rb2
-rw-r--r--spec/migrations/save_instance_administrators_group_id_spec.rb2
-rw-r--r--spec/migrations/schedule_backfill_push_rules_id_in_projects_spec.rb2
-rw-r--r--spec/migrations/schedule_calculate_wiki_sizes_spec.rb60
-rw-r--r--spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb2
-rw-r--r--spec/migrations/schedule_link_lfs_objects_projects_spec.rb2
-rw-r--r--spec/migrations/schedule_merge_request_assignees_migration_progress_check_spec.rb2
-rw-r--r--spec/migrations/schedule_migrate_security_scans_spec.rb2
-rw-r--r--spec/migrations/schedule_pages_metadata_migration_spec.rb2
-rw-r--r--spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb2
-rw-r--r--spec/migrations/schedule_populate_project_snippet_statistics_spec.rb61
-rw-r--r--spec/migrations/schedule_populate_user_highest_roles_table_spec.rb2
-rw-r--r--spec/migrations/schedule_recalculate_project_authorizations_second_run_spec.rb2
-rw-r--r--spec/migrations/schedule_recalculate_project_authorizations_spec.rb2
-rw-r--r--spec/migrations/schedule_recalculate_project_authorizations_third_run_spec.rb2
-rw-r--r--spec/migrations/schedule_sync_issuables_state_id_spec.rb2
-rw-r--r--spec/migrations/schedule_sync_issuables_state_id_where_nil_spec.rb2
-rw-r--r--spec/migrations/schedule_update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb2
-rw-r--r--spec/migrations/seed_repository_storages_weighted_spec.rb2
-rw-r--r--spec/migrations/services_remove_temporary_index_on_project_id_spec.rb2
-rw-r--r--spec/migrations/set_issue_id_for_all_versions_spec.rb2
-rw-r--r--spec/migrations/sync_issuables_state_id_spec.rb2
-rw-r--r--spec/migrations/truncate_user_fullname_spec.rb2
-rw-r--r--spec/migrations/unconfirm_wrongfully_verified_emails_spec.rb21
-rw-r--r--spec/migrations/update_application_setting_npm_package_requests_forwarding_default_spec.rb2
-rw-r--r--spec/migrations/update_fingerprint_sha256_within_keys_spec.rb2
-rw-r--r--spec/migrations/update_minimum_password_length_spec.rb2
-rw-r--r--spec/migrations/update_routes_for_lost_and_found_group_and_orphaned_projects_spec.rb2
-rw-r--r--spec/migrations/update_timestamp_softwarelicensespolicy_spec.rb2
-rw-r--r--spec/models/ability_spec.rb2
-rw-r--r--spec/models/abuse_report_spec.rb2
-rw-r--r--spec/models/alert_management/alert_assignee_spec.rb2
-rw-r--r--spec/models/alert_management/alert_spec.rb119
-rw-r--r--spec/models/alert_management/alert_user_mention_spec.rb2
-rw-r--r--spec/models/alerting/project_alerting_setting_spec.rb2
-rw-r--r--spec/models/analytics/cycle_analytics/project_stage_spec.rb2
-rw-r--r--spec/models/appearance_spec.rb2
-rw-r--r--spec/models/application_record_spec.rb9
-rw-r--r--spec/models/application_setting/term_spec.rb2
-rw-r--r--spec/models/application_setting_spec.rb2
-rw-r--r--spec/models/approval_spec.rb16
-rw-r--r--spec/models/award_emoji_spec.rb2
-rw-r--r--spec/models/aws/role_spec.rb2
-rw-r--r--spec/models/badge_spec.rb2
-rw-r--r--spec/models/badges/group_badge_spec.rb2
-rw-r--r--spec/models/badges/project_badge_spec.rb2
-rw-r--r--spec/models/blob_spec.rb2
-rw-r--r--spec/models/blob_viewer/base_spec.rb2
-rw-r--r--spec/models/blob_viewer/changelog_spec.rb2
-rw-r--r--spec/models/blob_viewer/composer_json_spec.rb2
-rw-r--r--spec/models/blob_viewer/gemspec_spec.rb2
-rw-r--r--spec/models/blob_viewer/gitlab_ci_yml_spec.rb2
-rw-r--r--spec/models/blob_viewer/go_mod_spec.rb2
-rw-r--r--spec/models/blob_viewer/license_spec.rb2
-rw-r--r--spec/models/blob_viewer/metrics_dashboard_yml_spec.rb2
-rw-r--r--spec/models/blob_viewer/package_json_spec.rb2
-rw-r--r--spec/models/blob_viewer/podspec_json_spec.rb2
-rw-r--r--spec/models/blob_viewer/podspec_spec.rb2
-rw-r--r--spec/models/blob_viewer/readme_spec.rb2
-rw-r--r--spec/models/blob_viewer/route_map_spec.rb2
-rw-r--r--spec/models/blob_viewer/server_side_spec.rb2
-rw-r--r--spec/models/board_group_recent_visit_spec.rb2
-rw-r--r--spec/models/board_project_recent_visit_spec.rb2
-rw-r--r--spec/models/board_spec.rb2
-rw-r--r--spec/models/broadcast_message_spec.rb2
-rw-r--r--spec/models/chat_name_spec.rb2
-rw-r--r--spec/models/chat_team_spec.rb2
-rw-r--r--spec/models/ci/artifact_blob_spec.rb2
-rw-r--r--spec/models/ci/bridge_spec.rb6
-rw-r--r--spec/models/ci/build_dependencies_spec.rb2
-rw-r--r--spec/models/ci/build_metadata_spec.rb31
-rw-r--r--spec/models/ci/build_need_spec.rb20
-rw-r--r--spec/models/ci/build_report_result_spec.rb2
-rw-r--r--spec/models/ci/build_runner_session_spec.rb2
-rw-r--r--spec/models/ci/build_spec.rb97
-rw-r--r--spec/models/ci/build_trace_chunk_spec.rb2
-rw-r--r--spec/models/ci/build_trace_chunks/database_spec.rb2
-rw-r--r--spec/models/ci/build_trace_chunks/fog_spec.rb2
-rw-r--r--spec/models/ci/build_trace_chunks/redis_spec.rb2
-rw-r--r--spec/models/ci/build_trace_section_name_spec.rb2
-rw-r--r--spec/models/ci/build_trace_section_spec.rb2
-rw-r--r--spec/models/ci/build_trace_spec.rb32
-rw-r--r--spec/models/ci/daily_build_group_report_result_spec.rb2
-rw-r--r--spec/models/ci/freeze_period_status_spec.rb2
-rw-r--r--spec/models/ci/group_spec.rb2
-rw-r--r--spec/models/ci/group_variable_spec.rb2
-rw-r--r--spec/models/ci/instance_variable_spec.rb17
-rw-r--r--spec/models/ci/job_artifact_spec.rb125
-rw-r--r--spec/models/ci/job_variable_spec.rb2
-rw-r--r--spec/models/ci/legacy_stage_spec.rb2
-rw-r--r--spec/models/ci/persistent_ref_spec.rb2
-rw-r--r--spec/models/ci/pipeline_config_spec.rb2
-rw-r--r--spec/models/ci/pipeline_message_spec.rb53
-rw-r--r--spec/models/ci/pipeline_schedule_spec.rb2
-rw-r--r--spec/models/ci/pipeline_schedule_variable_spec.rb2
-rw-r--r--spec/models/ci/pipeline_spec.rb255
-rw-r--r--spec/models/ci/pipeline_variable_spec.rb2
-rw-r--r--spec/models/ci/processable_spec.rb2
-rw-r--r--spec/models/ci/ref_spec.rb31
-rw-r--r--spec/models/ci/resource_group_spec.rb2
-rw-r--r--spec/models/ci/resource_spec.rb2
-rw-r--r--spec/models/ci/runner_spec.rb42
-rw-r--r--spec/models/ci/sources/pipeline_spec.rb2
-rw-r--r--spec/models/ci/stage_spec.rb4
-rw-r--r--spec/models/ci/trigger_request_spec.rb2
-rw-r--r--spec/models/ci/trigger_spec.rb2
-rw-r--r--spec/models/ci/variable_spec.rb2
-rw-r--r--spec/models/clusters/applications/cert_manager_spec.rb2
-rw-r--r--spec/models/clusters/applications/cilium_spec.rb17
-rw-r--r--spec/models/clusters/applications/crossplane_spec.rb2
-rw-r--r--spec/models/clusters/applications/elastic_stack_spec.rb16
-rw-r--r--spec/models/clusters/applications/fluentd_spec.rb2
-rw-r--r--spec/models/clusters/applications/helm_spec.rb2
-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.rb2
-rw-r--r--spec/models/clusters/applications/prometheus_spec.rb2
-rw-r--r--spec/models/clusters/applications/runner_spec.rb2
-rw-r--r--spec/models/clusters/cluster_spec.rb104
-rw-r--r--spec/models/clusters/clusters_hierarchy_spec.rb2
-rw-r--r--spec/models/clusters/group_spec.rb2
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb48
-rw-r--r--spec/models/clusters/project_spec.rb2
-rw-r--r--spec/models/clusters/providers/aws_spec.rb2
-rw-r--r--spec/models/clusters/providers/gcp_spec.rb2
-rw-r--r--spec/models/commit_collection_spec.rb14
-rw-r--r--spec/models/commit_range_spec.rb2
-rw-r--r--spec/models/commit_spec.rb13
-rw-r--r--spec/models/commit_status_spec.rb2
-rw-r--r--spec/models/commit_with_pipeline_spec.rb2
-rw-r--r--spec/models/compare_spec.rb2
-rw-r--r--spec/models/concerns/access_requestable_spec.rb2
-rw-r--r--spec/models/concerns/approvable_base_spec.rb34
-rw-r--r--spec/models/concerns/atomic_internal_id_spec.rb2
-rw-r--r--spec/models/concerns/avatarable_spec.rb2
-rw-r--r--spec/models/concerns/awardable_spec.rb2
-rw-r--r--spec/models/concerns/batch_destroy_dependent_associations_spec.rb2
-rw-r--r--spec/models/concerns/blob_language_from_git_attributes_spec.rb2
-rw-r--r--spec/models/concerns/blocks_json_serialization_spec.rb2
-rw-r--r--spec/models/concerns/bulk_insert_safe_spec.rb179
-rw-r--r--spec/models/concerns/bulk_insertable_associations_spec.rb2
-rw-r--r--spec/models/concerns/cache_markdown_field_spec.rb2
-rw-r--r--spec/models/concerns/cacheable_attributes_spec.rb2
-rw-r--r--spec/models/concerns/case_sensitivity_spec.rb2
-rw-r--r--spec/models/concerns/checksummable_spec.rb2
-rw-r--r--spec/models/concerns/chronic_duration_attribute_spec.rb8
-rw-r--r--spec/models/concerns/ci/has_ref_spec.rb2
-rw-r--r--spec/models/concerns/ci/has_status_spec.rb (renamed from spec/models/concerns/has_status_spec.rb)2
-rw-r--r--spec/models/concerns/ci/has_variable_spec.rb2
-rw-r--r--spec/models/concerns/ci/maskable_spec.rb2
-rw-r--r--spec/models/concerns/delete_with_limit_spec.rb2
-rw-r--r--spec/models/concerns/deployment_platform_spec.rb237
-rw-r--r--spec/models/concerns/deprecated_assignee_spec.rb2
-rw-r--r--spec/models/concerns/discussion_on_diff_spec.rb2
-rw-r--r--spec/models/concerns/each_batch_spec.rb2
-rw-r--r--spec/models/concerns/editable_spec.rb2
-rw-r--r--spec/models/concerns/expirable_spec.rb2
-rw-r--r--spec/models/concerns/faster_cache_keys_spec.rb2
-rw-r--r--spec/models/concerns/featurable_spec.rb2
-rw-r--r--spec/models/concerns/feature_gate_spec.rb2
-rw-r--r--spec/models/concerns/from_union_spec.rb2
-rw-r--r--spec/models/concerns/group_descendant_spec.rb2
-rw-r--r--spec/models/concerns/has_environment_scope_spec.rb2
-rw-r--r--spec/models/concerns/has_user_type_spec.rb2
-rw-r--r--spec/models/concerns/ignorable_columns_spec.rb2
-rw-r--r--spec/models/concerns/issuable_spec.rb23
-rw-r--r--spec/models/concerns/limitable_spec.rb2
-rw-r--r--spec/models/concerns/loaded_in_group_list_spec.rb2
-rw-r--r--spec/models/concerns/manual_inverse_association_spec.rb2
-rw-r--r--spec/models/concerns/mentionable_spec.rb14
-rw-r--r--spec/models/concerns/milestoneable_spec.rb2
-rw-r--r--spec/models/concerns/milestoneish_spec.rb2
-rw-r--r--spec/models/concerns/noteable_spec.rb42
-rw-r--r--spec/models/concerns/optionally_search_spec.rb2
-rw-r--r--spec/models/concerns/participable_spec.rb2
-rw-r--r--spec/models/concerns/partitioned_table_spec.rb35
-rw-r--r--spec/models/concerns/presentable_spec.rb2
-rw-r--r--spec/models/concerns/project_api_compatibility_spec.rb2
-rw-r--r--spec/models/concerns/project_features_compatibility_spec.rb2
-rw-r--r--spec/models/concerns/prometheus_adapter_spec.rb2
-rw-r--r--spec/models/concerns/protected_ref_access_spec.rb2
-rw-r--r--spec/models/concerns/reactive_caching_spec.rb36
-rw-r--r--spec/models/concerns/redactable_spec.rb2
-rw-r--r--spec/models/concerns/redis_cacheable_spec.rb2
-rw-r--r--spec/models/concerns/resolvable_discussion_spec.rb2
-rw-r--r--spec/models/concerns/resolvable_note_spec.rb2
-rw-r--r--spec/models/concerns/routable_spec.rb4
-rw-r--r--spec/models/concerns/safe_url_spec.rb2
-rw-r--r--spec/models/concerns/schedulable_spec.rb2
-rw-r--r--spec/models/concerns/sha256_attribute_spec.rb2
-rw-r--r--spec/models/concerns/sha_attribute_spec.rb2
-rw-r--r--spec/models/concerns/sortable_spec.rb2
-rw-r--r--spec/models/concerns/spammable_spec.rb2
-rw-r--r--spec/models/concerns/stepable_spec.rb2
-rw-r--r--spec/models/concerns/strip_attribute_spec.rb2
-rw-r--r--spec/models/concerns/subscribable_spec.rb2
-rw-r--r--spec/models/concerns/token_authenticatable_spec.rb10
-rw-r--r--spec/models/concerns/token_authenticatable_strategies/base_spec.rb2
-rw-r--r--spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb2
-rw-r--r--spec/models/concerns/uniquify_spec.rb2
-rw-r--r--spec/models/concerns/usage_statistics_spec.rb2
-rw-r--r--spec/models/concerns/where_composite_spec.rb2
-rw-r--r--spec/models/concerns/x509_serial_number_attribute_spec.rb2
-rw-r--r--spec/models/container_registry/event_spec.rb2
-rw-r--r--spec/models/container_repository_spec.rb2
-rw-r--r--spec/models/custom_emoji_spec.rb31
-rw-r--r--spec/models/cycle_analytics/code_spec.rb2
-rw-r--r--spec/models/cycle_analytics/issue_spec.rb2
-rw-r--r--spec/models/cycle_analytics/plan_spec.rb2
-rw-r--r--spec/models/cycle_analytics/production_spec.rb2
-rw-r--r--spec/models/cycle_analytics/project_level_spec.rb2
-rw-r--r--spec/models/cycle_analytics/review_spec.rb2
-rw-r--r--spec/models/cycle_analytics/staging_spec.rb2
-rw-r--r--spec/models/cycle_analytics/test_spec.rb2
-rw-r--r--spec/models/deploy_key_spec.rb2
-rw-r--r--spec/models/deploy_keys_project_spec.rb17
-rw-r--r--spec/models/deploy_token_spec.rb2
-rw-r--r--spec/models/deployment_cluster_spec.rb2
-rw-r--r--spec/models/deployment_merge_request_spec.rb2
-rw-r--r--spec/models/deployment_metrics_spec.rb2
-rw-r--r--spec/models/deployment_spec.rb2
-rw-r--r--spec/models/description_version_spec.rb2
-rw-r--r--spec/models/design_management/action_spec.rb2
-rw-r--r--spec/models/design_management/design_action_spec.rb2
-rw-r--r--spec/models/design_management/design_at_version_spec.rb2
-rw-r--r--spec/models/design_management/design_collection_spec.rb2
-rw-r--r--spec/models/design_management/design_spec.rb2
-rw-r--r--spec/models/design_management/repository_spec.rb2
-rw-r--r--spec/models/design_management/version_spec.rb2
-rw-r--r--spec/models/design_user_mention_spec.rb2
-rw-r--r--spec/models/dev_ops_score/metric_spec.rb2
-rw-r--r--spec/models/diff_discussion_spec.rb2
-rw-r--r--spec/models/diff_note_position_spec.rb2
-rw-r--r--spec/models/diff_note_spec.rb2
-rw-r--r--spec/models/diff_viewer/base_spec.rb2
-rw-r--r--spec/models/diff_viewer/server_side_spec.rb2
-rw-r--r--spec/models/discussion_spec.rb2
-rw-r--r--spec/models/draft_note_spec.rb2
-rw-r--r--spec/models/email_spec.rb2
-rw-r--r--spec/models/environment_spec.rb17
-rw-r--r--spec/models/environment_status_spec.rb2
-rw-r--r--spec/models/error_tracking/project_error_tracking_setting_spec.rb2
-rw-r--r--spec/models/event_collection_spec.rb51
-rw-r--r--spec/models/event_spec.rb20
-rw-r--r--spec/models/external_issue_spec.rb2
-rw-r--r--spec/models/external_pull_request_spec.rb2
-rw-r--r--spec/models/fork_network_member_spec.rb2
-rw-r--r--spec/models/fork_network_spec.rb2
-rw-r--r--spec/models/generic_commit_status_spec.rb2
-rw-r--r--spec/models/gpg_key_spec.rb2
-rw-r--r--spec/models/gpg_key_subkey_spec.rb2
-rw-r--r--spec/models/grafana_integration_spec.rb2
-rw-r--r--spec/models/group_custom_attribute_spec.rb2
-rw-r--r--spec/models/group_deploy_key_spec.rb2
-rw-r--r--spec/models/group_group_link_spec.rb2
-rw-r--r--spec/models/group_import_state_spec.rb2
-rw-r--r--spec/models/group_label_spec.rb2
-rw-r--r--spec/models/group_spec.rb229
-rw-r--r--spec/models/guest_spec.rb2
-rw-r--r--spec/models/hooks/active_hook_filter_spec.rb2
-rw-r--r--spec/models/hooks/project_hook_spec.rb2
-rw-r--r--spec/models/hooks/service_hook_spec.rb2
-rw-r--r--spec/models/hooks/system_hook_spec.rb2
-rw-r--r--spec/models/hooks/web_hook_log_spec.rb2
-rw-r--r--spec/models/hooks/web_hook_spec.rb2
-rw-r--r--spec/models/identity_spec.rb2
-rw-r--r--spec/models/import_export_upload_spec.rb2
-rw-r--r--spec/models/import_failure_spec.rb2
-rw-r--r--spec/models/incident_management/project_incident_management_setting_spec.rb40
-rw-r--r--spec/models/instance_configuration_spec.rb2
-rw-r--r--spec/models/integration_spec.rb11
-rw-r--r--spec/models/internal_id_spec.rb2
-rw-r--r--spec/models/issue/metrics_spec.rb2
-rw-r--r--spec/models/issue_assignee_spec.rb35
-rw-r--r--spec/models/issue_collection_spec.rb2
-rw-r--r--spec/models/issue_spec.rb51
-rw-r--r--spec/models/iteration_spec.rb29
-rw-r--r--spec/models/jira_import_state_spec.rb2
-rw-r--r--spec/models/key_spec.rb2
-rw-r--r--spec/models/label_link_spec.rb2
-rw-r--r--spec/models/label_note_spec.rb2
-rw-r--r--spec/models/label_priority_spec.rb2
-rw-r--r--spec/models/label_spec.rb2
-rw-r--r--spec/models/legacy_diff_discussion_spec.rb2
-rw-r--r--spec/models/lfs_download_object_spec.rb2
-rw-r--r--spec/models/lfs_file_lock_spec.rb2
-rw-r--r--spec/models/lfs_object_spec.rb2
-rw-r--r--spec/models/lfs_objects_project_spec.rb2
-rw-r--r--spec/models/license_template_spec.rb2
-rw-r--r--spec/models/list_spec.rb2
-rw-r--r--spec/models/list_user_preference_spec.rb2
-rw-r--r--spec/models/member_spec.rb24
-rw-r--r--spec/models/members/group_member_spec.rb58
-rw-r--r--spec/models/members/project_member_spec.rb2
-rw-r--r--spec/models/merge_request/metrics_spec.rb2
-rw-r--r--spec/models/merge_request_assignee_spec.rb24
-rw-r--r--spec/models/merge_request_context_commit_diff_file_spec.rb2
-rw-r--r--spec/models/merge_request_context_commit_spec.rb2
-rw-r--r--spec/models/merge_request_diff_commit_spec.rb2
-rw-r--r--spec/models/merge_request_diff_file_spec.rb2
-rw-r--r--spec/models/merge_request_diff_spec.rb115
-rw-r--r--spec/models/merge_request_spec.rb122
-rw-r--r--spec/models/metrics/dashboard/annotation_spec.rb2
-rw-r--r--spec/models/metrics/users_starred_dashboard_spec.rb2
-rw-r--r--spec/models/milestone_note_spec.rb6
-rw-r--r--spec/models/milestone_release_spec.rb2
-rw-r--r--spec/models/milestone_spec.rb2
-rw-r--r--spec/models/namespace/root_storage_size_spec.rb67
-rw-r--r--spec/models/namespace/root_storage_statistics_spec.rb47
-rw-r--r--spec/models/namespace/traversal_hierarchy_spec.rb63
-rw-r--r--spec/models/namespace_setting_spec.rb7
-rw-r--r--spec/models/namespace_spec.rb51
-rw-r--r--spec/models/network/graph_spec.rb2
-rw-r--r--spec/models/note_diff_file_spec.rb2
-rw-r--r--spec/models/note_spec.rb2
-rw-r--r--spec/models/notification_recipient_spec.rb2
-rw-r--r--spec/models/oauth_access_grant_spec.rb2
-rw-r--r--spec/models/oauth_access_token_spec.rb2
-rw-r--r--spec/models/packages/composer/metadatum_spec.rb14
-rw-r--r--spec/models/packages/conan/file_metadatum_spec.rb106
-rw-r--r--spec/models/packages/conan/metadatum_spec.rb90
-rw-r--r--spec/models/packages/dependency_link_spec.rb56
-rw-r--r--spec/models/packages/dependency_spec.rb113
-rw-r--r--spec/models/packages/go/module_spec.rb59
-rw-r--r--spec/models/packages/go/module_version_spec.rb114
-rw-r--r--spec/models/packages/maven/metadatum_spec.rb40
-rw-r--r--spec/models/packages/nuget/dependency_link_metadatum_spec.rb32
-rw-r--r--spec/models/packages/nuget/metadatum_spec.rb44
-rw-r--r--spec/models/packages/package_file_spec.rb69
-rw-r--r--spec/models/packages/package_spec.rb485
-rw-r--r--spec/models/packages/pypi/metadatum_spec.rb22
-rw-r--r--spec/models/packages/sem_ver_spec.rb42
-rw-r--r--spec/models/packages/tag_spec.rb62
-rw-r--r--spec/models/pages/lookup_path_spec.rb2
-rw-r--r--spec/models/pages/virtual_domain_spec.rb2
-rw-r--r--spec/models/pages_domain_acme_order_spec.rb2
-rw-r--r--spec/models/pages_domain_spec.rb2
-rw-r--r--spec/models/performance_monitoring/prometheus_dashboard_spec.rb16
-rw-r--r--spec/models/performance_monitoring/prometheus_metric_spec.rb2
-rw-r--r--spec/models/performance_monitoring/prometheus_panel_group_spec.rb2
-rw-r--r--spec/models/performance_monitoring/prometheus_panel_spec.rb2
-rw-r--r--spec/models/personal_access_token_spec.rb19
-rw-r--r--spec/models/personal_snippet_spec.rb2
-rw-r--r--spec/models/plan_limits_spec.rb210
-rw-r--r--spec/models/plan_spec.rb14
-rw-r--r--spec/models/pool_repository_spec.rb2
-rw-r--r--spec/models/postgresql/replication_slot_spec.rb2
-rw-r--r--spec/models/product_analytics_event_spec.rb24
-rw-r--r--spec/models/programming_language_spec.rb2
-rw-r--r--spec/models/project_authorization_spec.rb2
-rw-r--r--spec/models/project_auto_devops_spec.rb2
-rw-r--r--spec/models/project_ci_cd_setting_spec.rb2
-rw-r--r--spec/models/project_custom_attribute_spec.rb2
-rw-r--r--spec/models/project_daily_statistic_spec.rb2
-rw-r--r--spec/models/project_export_job_spec.rb2
-rw-r--r--spec/models/project_feature_spec.rb2
-rw-r--r--spec/models/project_group_link_spec.rb2
-rw-r--r--spec/models/project_import_data_spec.rb2
-rw-r--r--spec/models/project_import_state_spec.rb2
-rw-r--r--spec/models/project_label_spec.rb2
-rw-r--r--spec/models/project_metrics_setting_spec.rb2
-rw-r--r--spec/models/project_repository_spec.rb2
-rw-r--r--spec/models/project_services/alerts_service_spec.rb2
-rw-r--r--spec/models/project_services/asana_service_spec.rb2
-rw-r--r--spec/models/project_services/assembla_service_spec.rb2
-rw-r--r--spec/models/project_services/bamboo_service_spec.rb2
-rw-r--r--spec/models/project_services/bugzilla_service_spec.rb47
-rw-r--r--spec/models/project_services/buildkite_service_spec.rb2
-rw-r--r--spec/models/project_services/campfire_service_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/alert_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/base_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/deployment_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/issue_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/merge_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/note_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/pipeline_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/push_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/wiki_page_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_notification_service_spec.rb2
-rw-r--r--spec/models/project_services/confluence_service_spec.rb90
-rw-r--r--spec/models/project_services/custom_issue_tracker_service_spec.rb63
-rw-r--r--spec/models/project_services/data_fields_spec.rb2
-rw-r--r--spec/models/project_services/discord_service_spec.rb2
-rw-r--r--spec/models/project_services/drone_ci_service_spec.rb2
-rw-r--r--spec/models/project_services/emails_on_push_service_spec.rb2
-rw-r--r--spec/models/project_services/external_wiki_service_spec.rb2
-rw-r--r--spec/models/project_services/flowdock_service_spec.rb2
-rw-r--r--spec/models/project_services/gitlab_issue_tracker_service_spec.rb47
-rw-r--r--spec/models/project_services/hangouts_chat_service_spec.rb2
-rw-r--r--spec/models/project_services/hipchat_service_spec.rb2
-rw-r--r--spec/models/project_services/irker_service_spec.rb2
-rw-r--r--spec/models/project_services/issue_tracker_data_spec.rb2
-rw-r--r--spec/models/project_services/issue_tracker_service_spec.rb2
-rw-r--r--spec/models/project_services/jira_service_spec.rb94
-rw-r--r--spec/models/project_services/jira_tracker_data_spec.rb4
-rw-r--r--spec/models/project_services/mattermost_service_spec.rb2
-rw-r--r--spec/models/project_services/mattermost_slash_commands_service_spec.rb2
-rw-r--r--spec/models/project_services/microsoft_teams_service_spec.rb2
-rw-r--r--spec/models/project_services/open_project_service_spec.rb2
-rw-r--r--spec/models/project_services/open_project_tracker_data_spec.rb2
-rw-r--r--spec/models/project_services/packagist_service_spec.rb2
-rw-r--r--spec/models/project_services/pipelines_email_service_spec.rb2
-rw-r--r--spec/models/project_services/pivotaltracker_service_spec.rb2
-rw-r--r--spec/models/project_services/prometheus_service_spec.rb18
-rw-r--r--spec/models/project_services/pushover_service_spec.rb2
-rw-r--r--spec/models/project_services/redmine_service_spec.rb47
-rw-r--r--spec/models/project_services/slack_service_spec.rb2
-rw-r--r--spec/models/project_services/slack_slash_commands_service_spec.rb2
-rw-r--r--spec/models/project_services/teamcity_service_spec.rb2
-rw-r--r--spec/models/project_services/unify_circuit_service_spec.rb2
-rw-r--r--spec/models/project_services/webex_teams_service_spec.rb2
-rw-r--r--spec/models/project_services/youtrack_service_spec.rb47
-rw-r--r--spec/models/project_setting_spec.rb2
-rw-r--r--spec/models/project_snippet_spec.rb2
-rw-r--r--spec/models/project_spec.rb242
-rw-r--r--spec/models/project_statistics_spec.rb87
-rw-r--r--spec/models/project_team_spec.rb2
-rw-r--r--spec/models/project_wiki_spec.rb2
-rw-r--r--spec/models/prometheus_alert_event_spec.rb2
-rw-r--r--spec/models/prometheus_alert_spec.rb6
-rw-r--r--spec/models/prometheus_metric_spec.rb3
-rw-r--r--spec/models/protectable_dropdown_spec.rb2
-rw-r--r--spec/models/protected_branch/merge_access_level_spec.rb2
-rw-r--r--spec/models/protected_branch/push_access_level_spec.rb2
-rw-r--r--spec/models/protected_branch_spec.rb2
-rw-r--r--spec/models/protected_tag_spec.rb2
-rw-r--r--spec/models/push_event_payload_spec.rb2
-rw-r--r--spec/models/push_event_spec.rb2
-rw-r--r--spec/models/readme_blob_spec.rb2
-rw-r--r--spec/models/redirect_route_spec.rb2
-rw-r--r--spec/models/releases/evidence_spec.rb2
-rw-r--r--spec/models/releases/link_spec.rb2
-rw-r--r--spec/models/releases/source_spec.rb2
-rw-r--r--spec/models/remote_mirror_spec.rb2
-rw-r--r--spec/models/repository_language_spec.rb2
-rw-r--r--spec/models/repository_spec.rb55
-rw-r--r--spec/models/resource_milestone_event_spec.rb2
-rw-r--r--spec/models/resource_state_event_spec.rb2
-rw-r--r--spec/models/review_spec.rb2
-rw-r--r--spec/models/route_spec.rb2
-rw-r--r--spec/models/sent_notification_spec.rb2
-rw-r--r--spec/models/sentry_issue_spec.rb2
-rw-r--r--spec/models/serverless/domain_cluster_spec.rb2
-rw-r--r--spec/models/serverless/domain_spec.rb2
-rw-r--r--spec/models/serverless/function_spec.rb2
-rw-r--r--spec/models/service_desk_setting_spec.rb37
-rw-r--r--spec/models/service_spec.rb44
-rw-r--r--spec/models/shard_spec.rb2
-rw-r--r--spec/models/snippet_blob_spec.rb2
-rw-r--r--spec/models/snippet_input_action_collection_spec.rb2
-rw-r--r--spec/models/snippet_input_action_spec.rb16
-rw-r--r--spec/models/snippet_repository_spec.rb2
-rw-r--r--spec/models/snippet_spec.rb39
-rw-r--r--spec/models/snippet_statistics_spec.rb149
-rw-r--r--spec/models/spam_log_spec.rb2
-rw-r--r--spec/models/ssh_host_key_spec.rb2
-rw-r--r--spec/models/state_note_spec.rb54
-rw-r--r--spec/models/subscription_spec.rb2
-rw-r--r--spec/models/suggestion_spec.rb106
-rw-r--r--spec/models/system_note_metadata_spec.rb2
-rw-r--r--spec/models/term_agreement_spec.rb2
-rw-r--r--spec/models/terraform/state_spec.rb2
-rw-r--r--spec/models/todo_spec.rb2
-rw-r--r--spec/models/tree_spec.rb2
-rw-r--r--spec/models/trending_project_spec.rb2
-rw-r--r--spec/models/upload_spec.rb2
-rw-r--r--spec/models/uploads/fog_spec.rb2
-rw-r--r--spec/models/uploads/local_spec.rb2
-rw-r--r--spec/models/user_agent_detail_spec.rb2
-rw-r--r--spec/models/user_callout_spec.rb2
-rw-r--r--spec/models/user_canonical_email_spec.rb2
-rw-r--r--spec/models/user_custom_attribute_spec.rb2
-rw-r--r--spec/models/user_detail_spec.rb33
-rw-r--r--spec/models/user_highest_role_spec.rb2
-rw-r--r--spec/models/user_interacted_project_spec.rb2
-rw-r--r--spec/models/user_mentions/commit_user_mention_spec.rb2
-rw-r--r--spec/models/user_mentions/issue_user_mention_spec.rb2
-rw-r--r--spec/models/user_mentions/merge_request_user_mention_spec.rb2
-rw-r--r--spec/models/user_mentions/snippet_user_mention_spec.rb2
-rw-r--r--spec/models/user_preference_spec.rb2
-rw-r--r--spec/models/user_spec.rb125
-rw-r--r--spec/models/user_status_spec.rb2
-rw-r--r--spec/models/users_statistics_spec.rb2
-rw-r--r--spec/models/web_ide_terminal_spec.rb2
-rw-r--r--spec/models/wiki_page/meta_spec.rb2
-rw-r--r--spec/models/wiki_page/slug_spec.rb2
-rw-r--r--spec/models/wiki_page_spec.rb20
-rw-r--r--spec/models/zoom_meeting_spec.rb2
-rw-r--r--spec/policies/alert_management/alert_policy_spec.rb2
-rw-r--r--spec/policies/application_setting/term_policy_spec.rb2
-rw-r--r--spec/policies/award_emoji_policy_spec.rb2
-rw-r--r--spec/policies/base_policy_spec.rb2
-rw-r--r--spec/policies/blob_policy_spec.rb2
-rw-r--r--spec/policies/board_policy_spec.rb2
-rw-r--r--spec/policies/ci/build_policy_spec.rb2
-rw-r--r--spec/policies/ci/pipeline_policy_spec.rb2
-rw-r--r--spec/policies/ci/pipeline_schedule_policy_spec.rb2
-rw-r--r--spec/policies/ci/trigger_policy_spec.rb2
-rw-r--r--spec/policies/clusters/cluster_policy_spec.rb2
-rw-r--r--spec/policies/clusters/instance_policy_spec.rb2
-rw-r--r--spec/policies/commit_policy_spec.rb2
-rw-r--r--spec/policies/concerns/policy_actor_spec.rb2
-rw-r--r--spec/policies/deploy_key_policy_spec.rb2
-rw-r--r--spec/policies/deploy_keys_project_policy_spec.rb2
-rw-r--r--spec/policies/deploy_token_policy_spec.rb2
-rw-r--r--spec/policies/design_management/design_policy_spec.rb2
-rw-r--r--spec/policies/environment_policy_spec.rb2
-rw-r--r--spec/policies/global_policy_spec.rb20
-rw-r--r--spec/policies/group_member_policy_spec.rb2
-rw-r--r--spec/policies/group_policy_spec.rb65
-rw-r--r--spec/policies/identity_provider_policy_spec.rb2
-rw-r--r--spec/policies/issuable_policy_spec.rb2
-rw-r--r--spec/policies/issue_policy_spec.rb2
-rw-r--r--spec/policies/merge_request_policy_spec.rb3
-rw-r--r--spec/policies/metrics/dashboard/annotation_policy_spec.rb2
-rw-r--r--spec/policies/namespace/root_storage_statistics_policy_spec.rb2
-rw-r--r--spec/policies/namespace_policy_spec.rb2
-rw-r--r--spec/policies/note_policy_spec.rb2
-rw-r--r--spec/policies/packages/package_policy_spec.rb27
-rw-r--r--spec/policies/personal_snippet_policy_spec.rb2
-rw-r--r--spec/policies/project_member_policy_spec.rb33
-rw-r--r--spec/policies/project_policy_spec.rb114
-rw-r--r--spec/policies/project_snippet_policy_spec.rb2
-rw-r--r--spec/policies/project_statistics_policy_spec.rb2
-rw-r--r--spec/policies/protected_branch_policy_spec.rb2
-rw-r--r--spec/policies/releases/source_policy_spec.rb88
-rw-r--r--spec/policies/resource_label_event_policy_spec.rb2
-rw-r--r--spec/policies/todo_policy_spec.rb2
-rw-r--r--spec/policies/user_policy_spec.rb2
-rw-r--r--spec/policies/wiki_page_policy_spec.rb2
-rw-r--r--spec/presenters/alert_management/alert_presenter_spec.rb48
-rw-r--r--spec/presenters/alert_management/prometheus_alert_presenter_spec.rb68
-rw-r--r--spec/presenters/award_emoji_presenter_spec.rb2
-rw-r--r--spec/presenters/blob_presenter_spec.rb2
-rw-r--r--spec/presenters/blobs/unfold_presenter_spec.rb2
-rw-r--r--spec/presenters/ci/bridge_presenter_spec.rb2
-rw-r--r--spec/presenters/ci/build_presenter_spec.rb2
-rw-r--r--spec/presenters/ci/build_runner_presenter_spec.rb2
-rw-r--r--spec/presenters/ci/group_variable_presenter_spec.rb2
-rw-r--r--spec/presenters/ci/legacy_stage_presenter_spec.rb2
-rw-r--r--spec/presenters/ci/pipeline_presenter_spec.rb2
-rw-r--r--spec/presenters/ci/trigger_presenter_spec.rb2
-rw-r--r--spec/presenters/ci/variable_presenter_spec.rb2
-rw-r--r--spec/presenters/clusterable_presenter_spec.rb2
-rw-r--r--spec/presenters/clusters/cluster_presenter_spec.rb124
-rw-r--r--spec/presenters/commit_presenter_spec.rb2
-rw-r--r--spec/presenters/commit_status_presenter_spec.rb2
-rw-r--r--spec/presenters/dev_ops_score/metric_presenter_spec.rb2
-rw-r--r--spec/presenters/event_presenter_spec.rb2
-rw-r--r--spec/presenters/gitlab/blame_presenter_spec.rb2
-rw-r--r--spec/presenters/group_clusterable_presenter_spec.rb8
-rw-r--r--spec/presenters/group_member_presenter_spec.rb2
-rw-r--r--spec/presenters/instance_clusterable_presenter_spec.rb8
-rw-r--r--spec/presenters/issue_presenter_spec.rb2
-rw-r--r--spec/presenters/label_presenter_spec.rb2
-rw-r--r--spec/presenters/merge_request_presenter_spec.rb20
-rw-r--r--spec/presenters/milestone_presenter_spec.rb2
-rw-r--r--spec/presenters/packages/composer/packages_presenter_spec.rb78
-rw-r--r--spec/presenters/packages/conan/package_presenter_spec.rb181
-rw-r--r--spec/presenters/packages/detail/package_presenter_spec.rb98
-rw-r--r--spec/presenters/packages/npm/package_presenter_spec.rb65
-rw-r--r--spec/presenters/packages/nuget/package_metadata_presenter_spec.rb52
-rw-r--r--spec/presenters/packages/nuget/packages_metadata_presenter_spec.rb66
-rw-r--r--spec/presenters/packages/nuget/packages_versions_presenter_spec.rb14
-rw-r--r--spec/presenters/packages/nuget/search_results_presenter_spec.rb59
-rw-r--r--spec/presenters/packages/nuget/service_index_presenter_spec.rb28
-rw-r--r--spec/presenters/packages/pypi/package_presenter_spec.rb49
-rw-r--r--spec/presenters/pages_domain_presenter_spec.rb2
-rw-r--r--spec/presenters/project_clusterable_presenter_spec.rb8
-rw-r--r--spec/presenters/project_hook_presenter_spec.rb2
-rw-r--r--spec/presenters/project_member_presenter_spec.rb2
-rw-r--r--spec/presenters/project_presenter_spec.rb2
-rw-r--r--spec/presenters/projects/import_export/project_export_presenter_spec.rb2
-rw-r--r--spec/presenters/projects/prometheus/alert_presenter_spec.rb100
-rw-r--r--spec/presenters/projects/settings/deploy_keys_presenter_spec.rb2
-rw-r--r--spec/presenters/release_presenter_spec.rb34
-rw-r--r--spec/presenters/sentry_error_presenter_spec.rb2
-rw-r--r--spec/presenters/service_hook_presenter_spec.rb2
-rw-r--r--spec/presenters/snippet_blob_presenter_spec.rb40
-rw-r--r--spec/presenters/snippet_presenter_spec.rb2
-rw-r--r--spec/presenters/tree_entry_presenter_spec.rb2
-rw-r--r--spec/presenters/web_hook_log_presenter_spec.rb2
-rw-r--r--spec/rack_servers/puma_spec.rb2
-rw-r--r--spec/rack_servers/unicorn_spec.rb2
-rw-r--r--spec/requests/api/access_requests_spec.rb2
-rw-r--r--spec/requests/api/admin/ci/variables_spec.rb2
-rw-r--r--spec/requests/api/admin/instance_clusters_spec.rb461
-rw-r--r--spec/requests/api/admin/sidekiq_spec.rb2
-rw-r--r--spec/requests/api/api_guard/admin_mode_middleware_spec.rb2
-rw-r--r--spec/requests/api/api_spec.rb10
-rw-r--r--spec/requests/api/appearance_spec.rb2
-rw-r--r--spec/requests/api/applications_spec.rb11
-rw-r--r--spec/requests/api/avatar_spec.rb2
-rw-r--r--spec/requests/api/award_emoji_spec.rb2
-rw-r--r--spec/requests/api/badges_spec.rb2
-rw-r--r--spec/requests/api/boards_spec.rb2
-rw-r--r--spec/requests/api/branches_spec.rb116
-rw-r--r--spec/requests/api/broadcast_messages_spec.rb2
-rw-r--r--spec/requests/api/ci/pipeline_schedules_spec.rb (renamed from spec/requests/api/pipeline_schedules_spec.rb)4
-rw-r--r--spec/requests/api/ci/pipelines_spec.rb (renamed from spec/requests/api/pipelines_spec.rb)6
-rw-r--r--spec/requests/api/ci/runner_spec.rb (renamed from spec/requests/api/runner_spec.rb)287
-rw-r--r--spec/requests/api/ci/runners_spec.rb (renamed from spec/requests/api/runners_spec.rb)22
-rw-r--r--spec/requests/api/commit_statuses_spec.rb2
-rw-r--r--spec/requests/api/commits_spec.rb2
-rw-r--r--spec/requests/api/composer_packages_spec.rb302
-rw-r--r--spec/requests/api/conan_packages_spec.rb840
-rw-r--r--spec/requests/api/container_registry_event_spec.rb2
-rw-r--r--spec/requests/api/deploy_keys_spec.rb2
-rw-r--r--spec/requests/api/deploy_tokens_spec.rb2
-rw-r--r--spec/requests/api/deployments_spec.rb2
-rw-r--r--spec/requests/api/discussions_spec.rb2
-rw-r--r--spec/requests/api/doorkeeper_access_spec.rb2
-rw-r--r--spec/requests/api/environments_spec.rb2
-rw-r--r--spec/requests/api/error_tracking_spec.rb2
-rw-r--r--spec/requests/api/events_spec.rb2
-rw-r--r--spec/requests/api/features_spec.rb2
-rw-r--r--spec/requests/api/files_spec.rb24
-rw-r--r--spec/requests/api/freeze_periods_spec.rb2
-rw-r--r--spec/requests/api/go_proxy_spec.rb465
-rw-r--r--spec/requests/api/graphql/boards/board_lists_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/boards/boards_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/current_user/todos_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/current_user_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/gitlab_schema_spec.rb2
-rw-r--r--spec/requests/api/graphql/group/labels_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/group/milestones_spec.rb119
-rw-r--r--spec/requests/api/graphql/group_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/metadata_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb2
-rw-r--r--spec/requests/api/graphql/metrics/dashboard_query_spec.rb8
-rw-r--r--spec/requests/api/graphql/multiplexed_queries_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/alert_management/alerts/create_alert_issue_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/alert_management/alerts/set_assignees_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/alert_management/alerts/todo/create_spec.rb55
-rw-r--r--spec/requests/api/graphql/mutations/alert_management/alerts/update_alert_status_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/add_spec.rb6
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb6
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb6
-rw-r--r--spec/requests/api/graphql/mutations/branches/create_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/commits/create_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/container_expiration_policy/update_spec.rb61
-rw-r--r--spec/requests/api/graphql/mutations/design_management/delete_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/design_management/upload_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_locked_spec.rb55
-rw-r--r--spec/requests/api/graphql/mutations/jira_import/import_users_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/jira_import/start_spec.rb5
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/create_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb24
-rw-r--r--spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/note_spec.rb6
-rw-r--r--spec/requests/api/graphql/mutations/notes/destroy_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/notes/update/note_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/snippets/create_spec.rb52
-rw-r--r--spec/requests/api/graphql/mutations/snippets/destroy_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/snippets/update_spec.rb48
-rw-r--r--spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/todos/mark_done_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/todos/restore_spec.rb2
-rw-r--r--spec/requests/api/graphql/namespace/projects_spec.rb2
-rw-r--r--spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/alert_management/alert/assignees_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/alert_management/alert/metrics_dashboard_url_spec.rb62
-rw-r--r--spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb34
-rw-r--r--spec/requests/api/graphql/project/alert_management/alert_status_counts_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/alert_management/alerts_spec.rb11
-rw-r--r--spec/requests/api/graphql/project/base_service_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/container_expiration_policy_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/grafana_integration_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issue/design_collection/version_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issue/designs/designs_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issue/designs/notes_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issue/notes_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issue_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issues_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/jira_import_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/jira_projects_spec.rb30
-rw-r--r--spec/requests/api/graphql/project/jira_service_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/labels_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/merge_request/diff_notes_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/merge_request_spec.rb55
-rw-r--r--spec/requests/api/graphql/project/merge_requests_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/packages_spec.rb69
-rw-r--r--spec/requests/api/graphql/project/pipeline_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/project_statistics_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/release_spec.rb422
-rw-r--r--spec/requests/api/graphql/project/releases_spec.rb284
-rw-r--r--spec/requests/api/graphql/project/repository_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/tree/tree_spec.rb2
-rw-r--r--spec/requests/api/graphql/project_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/query_spec.rb2
-rw-r--r--spec/requests/api/graphql/read_only_spec.rb2
-rw-r--r--spec/requests/api/graphql/tasks/task_completion_status_spec.rb2
-rw-r--r--spec/requests/api/graphql/user/group_member_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/user/project_member_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/user_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/user_spec.rb2
-rw-r--r--spec/requests/api/graphql/users_spec.rb2
-rw-r--r--spec/requests/api/graphql_spec.rb2
-rw-r--r--spec/requests/api/group_boards_spec.rb2
-rw-r--r--spec/requests/api/group_clusters_spec.rb50
-rw-r--r--spec/requests/api/group_container_repositories_spec.rb2
-rw-r--r--spec/requests/api/group_export_spec.rb10
-rw-r--r--spec/requests/api/group_import_spec.rb3
-rw-r--r--spec/requests/api/group_labels_spec.rb2
-rw-r--r--spec/requests/api/group_milestones_spec.rb2
-rw-r--r--spec/requests/api/group_packages_spec.rb147
-rw-r--r--spec/requests/api/group_variables_spec.rb2
-rw-r--r--spec/requests/api/groups_spec.rb57
-rw-r--r--spec/requests/api/helpers_spec.rb2
-rw-r--r--spec/requests/api/import_bitbucket_server_spec.rb218
-rw-r--r--spec/requests/api/import_github_spec.rb6
-rw-r--r--spec/requests/api/internal/base_spec.rb29
-rw-r--r--spec/requests/api/internal/pages_spec.rb2
-rw-r--r--spec/requests/api/issues/get_group_issues_spec.rb2
-rw-r--r--spec/requests/api/issues/get_project_issues_spec.rb2
-rw-r--r--spec/requests/api/issues/issues_spec.rb51
-rw-r--r--spec/requests/api/issues/post_projects_issues_spec.rb2
-rw-r--r--spec/requests/api/issues/put_projects_issues_spec.rb2
-rw-r--r--spec/requests/api/jobs_spec.rb8
-rw-r--r--spec/requests/api/keys_spec.rb2
-rw-r--r--spec/requests/api/labels_spec.rb2
-rw-r--r--spec/requests/api/lint_spec.rb2
-rw-r--r--spec/requests/api/markdown_spec.rb2
-rw-r--r--spec/requests/api/maven_packages_spec.rb569
-rw-r--r--spec/requests/api/members_spec.rb63
-rw-r--r--spec/requests/api/merge_request_approvals_spec.rb84
-rw-r--r--spec/requests/api/merge_request_diffs_spec.rb2
-rw-r--r--spec/requests/api/merge_requests_spec.rb71
-rw-r--r--spec/requests/api/metrics/dashboard/annotations_spec.rb2
-rw-r--r--spec/requests/api/metrics/user_starred_dashboards_spec.rb2
-rw-r--r--spec/requests/api/namespaces_spec.rb2
-rw-r--r--spec/requests/api/notes_spec.rb2
-rw-r--r--spec/requests/api/notification_settings_spec.rb2
-rw-r--r--spec/requests/api/npm_packages_spec.rb550
-rw-r--r--spec/requests/api/nuget_packages_spec.rb482
-rw-r--r--spec/requests/api/oauth_tokens_spec.rb2
-rw-r--r--spec/requests/api/package_files_spec.rb81
-rw-r--r--spec/requests/api/pages/internal_access_spec.rb2
-rw-r--r--spec/requests/api/pages/pages_spec.rb2
-rw-r--r--spec/requests/api/pages/private_access_spec.rb2
-rw-r--r--spec/requests/api/pages/public_access_spec.rb2
-rw-r--r--spec/requests/api/pages_domains_spec.rb2
-rw-r--r--spec/requests/api/project_clusters_spec.rb55
-rw-r--r--spec/requests/api/project_container_repositories_spec.rb2
-rw-r--r--spec/requests/api/project_events_spec.rb2
-rw-r--r--spec/requests/api/project_export_spec.rb6
-rw-r--r--spec/requests/api/project_hooks_spec.rb2
-rw-r--r--spec/requests/api/project_import_spec.rb2
-rw-r--r--spec/requests/api/project_milestones_spec.rb2
-rw-r--r--spec/requests/api/project_packages_spec.rb272
-rw-r--r--spec/requests/api/project_repository_storage_moves_spec.rb2
-rw-r--r--spec/requests/api/project_snapshots_spec.rb2
-rw-r--r--spec/requests/api/project_snippets_spec.rb37
-rw-r--r--spec/requests/api/project_statistics_spec.rb2
-rw-r--r--spec/requests/api/project_templates_spec.rb2
-rw-r--r--spec/requests/api/projects_spec.rb117
-rw-r--r--spec/requests/api/protected_branches_spec.rb2
-rw-r--r--spec/requests/api/protected_tags_spec.rb2
-rw-r--r--spec/requests/api/pypi_packages_spec.rb259
-rw-r--r--spec/requests/api/release/links_spec.rb2
-rw-r--r--spec/requests/api/releases_spec.rb2
-rw-r--r--spec/requests/api/remote_mirrors_spec.rb2
-rw-r--r--spec/requests/api/repositories_spec.rb18
-rw-r--r--spec/requests/api/resource_label_events_spec.rb2
-rw-r--r--spec/requests/api/resource_milestone_events_spec.rb2
-rw-r--r--spec/requests/api/resource_state_events_spec.rb105
-rw-r--r--spec/requests/api/search_spec.rb2
-rw-r--r--spec/requests/api/services_spec.rb2
-rw-r--r--spec/requests/api/settings_spec.rb12
-rw-r--r--spec/requests/api/sidekiq_metrics_spec.rb2
-rw-r--r--spec/requests/api/snippets_spec.rb156
-rw-r--r--spec/requests/api/statistics_spec.rb2
-rw-r--r--spec/requests/api/submodules_spec.rb2
-rw-r--r--spec/requests/api/suggestions_spec.rb2
-rw-r--r--spec/requests/api/system_hooks_spec.rb2
-rw-r--r--spec/requests/api/tags_spec.rb2
-rw-r--r--spec/requests/api/task_completion_status_spec.rb2
-rw-r--r--spec/requests/api/templates_spec.rb2
-rw-r--r--spec/requests/api/terraform/state_spec.rb63
-rw-r--r--spec/requests/api/todos_spec.rb2
-rw-r--r--spec/requests/api/triggers_spec.rb2
-rw-r--r--spec/requests/api/user_counts_spec.rb2
-rw-r--r--spec/requests/api/users_spec.rb10
-rw-r--r--spec/requests/api/variables_spec.rb151
-rw-r--r--spec/requests/api/version_spec.rb2
-rw-r--r--spec/requests/api/wikis_spec.rb306
-rw-r--r--spec/requests/boards/lists_controller_spec.rb2
-rw-r--r--spec/requests/git_http_spec.rb2
-rw-r--r--spec/requests/groups/milestones_controller_spec.rb2
-rw-r--r--spec/requests/groups/registry/repositories_controller_spec.rb2
-rw-r--r--spec/requests/health_controller_spec.rb36
-rw-r--r--spec/requests/import/gitlab_groups_controller_spec.rb4
-rw-r--r--spec/requests/import/gitlab_projects_controller_spec.rb2
-rw-r--r--spec/requests/jwt_controller_spec.rb2
-rw-r--r--spec/requests/lfs_http_spec.rb2
-rw-r--r--spec/requests/lfs_locks_api_spec.rb2
-rw-r--r--spec/requests/oauth_tokens_spec.rb2
-rw-r--r--spec/requests/openid_connect_spec.rb27
-rw-r--r--spec/requests/product_analytics/collector_app_attack_spec.rb41
-rw-r--r--spec/requests/product_analytics/collector_app_spec.rb73
-rw-r--r--spec/requests/profiles/notifications_controller_spec.rb2
-rw-r--r--spec/requests/projects/cycle_analytics_events_spec.rb2
-rw-r--r--spec/requests/projects/incident_management/pagerduty_incidents_spec.rb36
-rw-r--r--spec/requests/projects/merge_requests/creations_spec.rb2
-rw-r--r--spec/requests/projects/merge_requests_discussions_spec.rb2
-rw-r--r--spec/requests/projects/metrics_dashboard_spec.rb85
-rw-r--r--spec/requests/projects/uploads_spec.rb2
-rw-r--r--spec/requests/rack_attack_global_spec.rb2
-rw-r--r--spec/requests/request_profiler_spec.rb2
-rw-r--r--spec/requests/self_monitoring_project_spec.rb2
-rw-r--r--spec/requests/sessions_spec.rb2
-rw-r--r--spec/requests/user_activity_spec.rb2
-rw-r--r--spec/requests/user_avatar_spec.rb2
-rw-r--r--spec/requests/user_spoofs_ip_spec.rb2
-rw-r--r--spec/routing/admin/serverless/domains_controller_routing_spec.rb2
-rw-r--r--spec/routing/admin_routing_spec.rb18
-rw-r--r--spec/routing/environments_spec.rb2
-rw-r--r--spec/routing/git_http_routing_spec.rb2
-rw-r--r--spec/routing/group_routing_spec.rb24
-rw-r--r--spec/routing/import_routing_spec.rb63
-rw-r--r--spec/routing/instance_statistics_routing_spec.rb2
-rw-r--r--spec/routing/notifications_routing_spec.rb2
-rw-r--r--spec/routing/openid_connect_spec.rb4
-rw-r--r--spec/routing/project_routing_spec.rb185
-rw-r--r--spec/routing/routing_spec.rb61
-rw-r--r--spec/routing/uploads_routing_spec.rb2
-rw-r--r--spec/rubocop/code_reuse_helpers_spec.rb38
-rw-r--r--spec/rubocop/cop/active_record_association_reload_spec.rb4
-rw-r--r--spec/rubocop/cop/api/grape_api_instance_spec.rb29
-rw-r--r--spec/rubocop/cop/api/grape_array_missing_coerce_spec.rb62
-rw-r--r--spec/rubocop/cop/avoid_break_from_strong_memoize_spec.rb2
-rw-r--r--spec/rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers_spec.rb4
-rw-r--r--spec/rubocop/cop/avoid_return_from_blocks_spec.rb2
-rw-r--r--spec/rubocop/cop/avoid_route_redirect_leading_slash_spec.rb4
-rw-r--r--spec/rubocop/cop/ban_catch_throw_spec.rb4
-rw-r--r--spec/rubocop/cop/code_reuse/active_record_spec.rb16
-rw-r--r--spec/rubocop/cop/code_reuse/finder_spec.rb4
-rw-r--r--spec/rubocop/cop/code_reuse/presenter_spec.rb4
-rw-r--r--spec/rubocop/cop/code_reuse/serializer_spec.rb4
-rw-r--r--spec/rubocop/cop/code_reuse/service_class_spec.rb4
-rw-r--r--spec/rubocop/cop/code_reuse/worker_spec.rb6
-rw-r--r--spec/rubocop/cop/default_scope_spec.rb4
-rw-r--r--spec/rubocop/cop/destroy_all_spec.rb4
-rw-r--r--spec/rubocop/cop/filename_length_spec.rb2
-rw-r--r--spec/rubocop/cop/gitlab/bulk_insert_spec.rb4
-rw-r--r--spec/rubocop/cop/gitlab/change_timezone_spec.rb4
-rw-r--r--spec/rubocop/cop/gitlab/const_get_inherit_false_spec.rb4
-rw-r--r--spec/rubocop/cop/gitlab/duplicate_spec_location_spec.rb4
-rw-r--r--spec/rubocop/cop/gitlab/finder_with_find_by_spec.rb4
-rw-r--r--spec/rubocop/cop/gitlab/httparty_spec.rb4
-rw-r--r--spec/rubocop/cop/gitlab/json_spec.rb4
-rw-r--r--spec/rubocop/cop/gitlab/module_with_instance_variables_spec.rb4
-rw-r--r--spec/rubocop/cop/gitlab/predicate_memoization_spec.rb4
-rw-r--r--spec/rubocop/cop/gitlab/rails_logger_spec.rb4
-rw-r--r--spec/rubocop/cop/gitlab/union_spec.rb4
-rw-r--r--spec/rubocop/cop/graphql/authorize_types_spec.rb114
-rw-r--r--spec/rubocop/cop/graphql/descriptions_spec.rb4
-rw-r--r--spec/rubocop/cop/group_public_or_visible_to_user_spec.rb4
-rw-r--r--spec/rubocop/cop/ignored_columns_spec.rb4
-rw-r--r--spec/rubocop/cop/include_sidekiq_worker_spec.rb4
-rw-r--r--spec/rubocop/cop/inject_enterprise_edition_module_spec.rb4
-rw-r--r--spec/rubocop/cop/line_break_around_conditional_block_spec.rb4
-rw-r--r--spec/rubocop/cop/migration/add_column_with_default_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/add_columns_to_wide_tables_spec.rb5
-rw-r--r--spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/add_concurrent_index_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/add_index_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/add_limit_to_text_columns_spec.rb6
-rw-r--r--spec/rubocop/cop/migration/add_reference_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/add_timestamps_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/datetime_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/drop_table_spec.rb74
-rw-r--r--spec/rubocop/cop/migration/hash_index_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/prevent_strings_spec.rb6
-rw-r--r--spec/rubocop/cop/migration/remove_column_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/remove_concurrent_index_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/remove_index_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/safer_boolean_column_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/schedule_async_spec.rb2
-rw-r--r--spec/rubocop/cop/migration/timestamps_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/update_column_in_batches_spec.rb2
-rw-r--r--spec/rubocop/cop/migration/with_lock_retries_disallowed_method_spec.rb7
-rw-r--r--spec/rubocop/cop/migration/with_lock_retries_with_change_spec.rb7
-rw-r--r--spec/rubocop/cop/performance/ar_count_each_spec.rb4
-rw-r--r--spec/rubocop/cop/performance/ar_exists_and_present_blank_spec.rb4
-rw-r--r--spec/rubocop/cop/performance/readlines_each_spec.rb4
-rw-r--r--spec/rubocop/cop/prefer_class_methods_over_module_spec.rb4
-rw-r--r--spec/rubocop/cop/project_path_helper_spec.rb4
-rw-r--r--spec/rubocop/cop/put_group_routes_under_scope_spec.rb4
-rw-r--r--spec/rubocop/cop/put_project_routes_under_scope_spec.rb4
-rw-r--r--spec/rubocop/cop/qa/ambiguous_page_object_name_spec.rb4
-rw-r--r--spec/rubocop/cop/qa/element_with_pattern_spec.rb4
-rw-r--r--spec/rubocop/cop/rspec/any_instance_of_spec.rb4
-rw-r--r--spec/rubocop/cop/rspec/be_success_matcher_spec.rb4
-rw-r--r--spec/rubocop/cop/rspec/env_assignment_spec.rb4
-rw-r--r--spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb4
-rw-r--r--spec/rubocop/cop/rspec/have_gitlab_http_status_spec.rb2
-rw-r--r--spec/rubocop/cop/rspec/modify_sidekiq_middleware_spec.rb3
-rw-r--r--spec/rubocop/cop/rspec/top_level_describe_path_spec.rb6
-rw-r--r--spec/rubocop/cop/ruby_interpolation_in_translation_spec.rb4
-rw-r--r--spec/rubocop/cop/safe_params_spec.rb4
-rw-r--r--spec/rubocop/cop/scalability/bulk_perform_with_context_spec.rb3
-rw-r--r--spec/rubocop/cop/scalability/cron_worker_context_spec.rb3
-rw-r--r--spec/rubocop/cop/scalability/file_uploads_spec.rb3
-rw-r--r--spec/rubocop/cop/scalability/idempotent_worker_spec.rb3
-rw-r--r--spec/rubocop/cop/sidekiq_options_queue_spec.rb4
-rw-r--r--spec/rubocop/cop/static_translation_definition_spec.rb6
-rw-r--r--spec/rubocop/migration_helpers_spec.rb2
-rw-r--r--spec/rubocop/qa_helpers_spec.rb9
-rw-r--r--spec/serializers/accessibility_error_entity_spec.rb2
-rw-r--r--spec/serializers/accessibility_reports_comparer_entity_spec.rb2
-rw-r--r--spec/serializers/accessibility_reports_comparer_serializer_spec.rb2
-rw-r--r--spec/serializers/analytics_build_entity_spec.rb2
-rw-r--r--spec/serializers/analytics_build_serializer_spec.rb2
-rw-r--r--spec/serializers/analytics_issue_entity_spec.rb2
-rw-r--r--spec/serializers/analytics_issue_serializer_spec.rb2
-rw-r--r--spec/serializers/analytics_merge_request_serializer_spec.rb2
-rw-r--r--spec/serializers/analytics_stage_serializer_spec.rb2
-rw-r--r--spec/serializers/analytics_summary_serializer_spec.rb2
-rw-r--r--spec/serializers/blob_entity_spec.rb2
-rw-r--r--spec/serializers/board_serializer_spec.rb2
-rw-r--r--spec/serializers/build_action_entity_spec.rb2
-rw-r--r--spec/serializers/build_artifact_entity_spec.rb2
-rw-r--r--spec/serializers/build_details_entity_spec.rb2
-rw-r--r--spec/serializers/build_serializer_spec.rb2
-rw-r--r--spec/serializers/build_trace_entity_spec.rb52
-rw-r--r--spec/serializers/ci/dag_job_entity_spec.rb18
-rw-r--r--spec/serializers/ci/dag_job_group_entity_spec.rb10
-rw-r--r--spec/serializers/ci/dag_pipeline_entity_spec.rb14
-rw-r--r--spec/serializers/ci/dag_pipeline_serializer_spec.rb6
-rw-r--r--spec/serializers/ci/dag_stage_entity_spec.rb6
-rw-r--r--spec/serializers/ci/daily_build_group_report_result_entity_spec.rb2
-rw-r--r--spec/serializers/ci/daily_build_group_report_result_serializer_spec.rb2
-rw-r--r--spec/serializers/ci/group_variable_entity_spec.rb (renamed from spec/serializers/group_variable_entity_spec.rb)2
-rw-r--r--spec/serializers/ci/variable_entity_spec.rb (renamed from spec/serializers/variable_entity_spec.rb)2
-rw-r--r--spec/serializers/cluster_application_entity_spec.rb2
-rw-r--r--spec/serializers/cluster_entity_spec.rb34
-rw-r--r--spec/serializers/cluster_serializer_spec.rb7
-rw-r--r--spec/serializers/commit_entity_spec.rb2
-rw-r--r--spec/serializers/container_repositories_serializer_spec.rb2
-rw-r--r--spec/serializers/container_repository_entity_spec.rb2
-rw-r--r--spec/serializers/container_tag_entity_spec.rb2
-rw-r--r--spec/serializers/deploy_key_entity_spec.rb43
-rw-r--r--spec/serializers/deployment_cluster_entity_spec.rb2
-rw-r--r--spec/serializers/deployment_entity_spec.rb2
-rw-r--r--spec/serializers/deployment_serializer_spec.rb2
-rw-r--r--spec/serializers/detailed_status_entity_spec.rb2
-rw-r--r--spec/serializers/diff_file_base_entity_spec.rb6
-rw-r--r--spec/serializers/diff_file_entity_spec.rb2
-rw-r--r--spec/serializers/diff_line_entity_spec.rb2
-rw-r--r--spec/serializers/diff_line_serializer_spec.rb2
-rw-r--r--spec/serializers/diff_viewer_entity_spec.rb2
-rw-r--r--spec/serializers/diffs_entity_spec.rb2
-rw-r--r--spec/serializers/diffs_metadata_entity_spec.rb2
-rw-r--r--spec/serializers/discussion_diff_file_entity_spec.rb2
-rw-r--r--spec/serializers/discussion_entity_spec.rb2
-rw-r--r--spec/serializers/entity_date_helper_spec.rb2
-rw-r--r--spec/serializers/entity_request_spec.rb2
-rw-r--r--spec/serializers/environment_entity_spec.rb2
-rw-r--r--spec/serializers/environment_serializer_spec.rb2
-rw-r--r--spec/serializers/environment_status_entity_spec.rb2
-rw-r--r--spec/serializers/evidences/evidence_entity_spec.rb2
-rw-r--r--spec/serializers/evidences/evidence_serializer_spec.rb2
-rw-r--r--spec/serializers/evidences/issue_entity_spec.rb2
-rw-r--r--spec/serializers/evidences/milestone_entity_spec.rb2
-rw-r--r--spec/serializers/evidences/project_entity_spec.rb2
-rw-r--r--spec/serializers/evidences/release_entity_spec.rb2
-rw-r--r--spec/serializers/evidences/release_serializer_spec.rb2
-rw-r--r--spec/serializers/fork_namespace_entity_spec.rb70
-rw-r--r--spec/serializers/fork_namespace_serializer_spec.rb9
-rw-r--r--spec/serializers/group_child_entity_spec.rb2
-rw-r--r--spec/serializers/group_child_serializer_spec.rb2
-rw-r--r--spec/serializers/import/bitbucket_provider_repo_entity_spec.rb2
-rw-r--r--spec/serializers/import/bitbucket_server_provider_repo_entity_spec.rb2
-rw-r--r--spec/serializers/import/fogbugz_provider_repo_entity_spec.rb2
-rw-r--r--spec/serializers/import/githubish_provider_repo_entity_spec.rb2
-rw-r--r--spec/serializers/import/gitlab_provider_repo_entity_spec.rb2
-rw-r--r--spec/serializers/import/provider_repo_serializer_spec.rb2
-rw-r--r--spec/serializers/issuable_sidebar_extras_entity_spec.rb2
-rw-r--r--spec/serializers/issue_board_entity_spec.rb2
-rw-r--r--spec/serializers/issue_entity_spec.rb2
-rw-r--r--spec/serializers/issue_serializer_spec.rb2
-rw-r--r--spec/serializers/job_artifact_report_entity_spec.rb2
-rw-r--r--spec/serializers/job_entity_spec.rb2
-rw-r--r--spec/serializers/label_serializer_spec.rb2
-rw-r--r--spec/serializers/lfs_file_lock_entity_spec.rb2
-rw-r--r--spec/serializers/merge_request_basic_entity_spec.rb2
-rw-r--r--spec/serializers/merge_request_diff_entity_spec.rb2
-rw-r--r--spec/serializers/merge_request_for_pipeline_entity_spec.rb2
-rw-r--r--spec/serializers/merge_request_poll_cached_widget_entity_spec.rb24
-rw-r--r--spec/serializers/merge_request_poll_widget_entity_spec.rb24
-rw-r--r--spec/serializers/merge_request_serializer_spec.rb2
-rw-r--r--spec/serializers/merge_request_sidebar_basic_entity_spec.rb2
-rw-r--r--spec/serializers/merge_request_user_entity_spec.rb2
-rw-r--r--spec/serializers/merge_request_widget_commit_entity_spec.rb2
-rw-r--r--spec/serializers/merge_request_widget_entity_spec.rb69
-rw-r--r--spec/serializers/move_to_project_entity_spec.rb2
-rw-r--r--spec/serializers/move_to_project_serializer_spec.rb2
-rw-r--r--spec/serializers/namespace_basic_entity_spec.rb2
-rw-r--r--spec/serializers/namespace_serializer_spec.rb2
-rw-r--r--spec/serializers/note_entity_spec.rb2
-rw-r--r--spec/serializers/paginated_diff_entity_spec.rb2
-rw-r--r--spec/serializers/pipeline_details_entity_spec.rb20
-rw-r--r--spec/serializers/pipeline_entity_spec.rb26
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb61
-rw-r--r--spec/serializers/project_import_entity_spec.rb2
-rw-r--r--spec/serializers/project_mirror_entity_spec.rb2
-rw-r--r--spec/serializers/project_mirror_serializer_spec.rb2
-rw-r--r--spec/serializers/project_note_entity_spec.rb2
-rw-r--r--spec/serializers/project_serializer_spec.rb2
-rw-r--r--spec/serializers/prometheus_alert_entity_spec.rb2
-rw-r--r--spec/serializers/remote_mirror_entity_spec.rb2
-rw-r--r--spec/serializers/request_aware_entity_spec.rb2
-rw-r--r--spec/serializers/review_app_setup_entity_spec.rb2
-rw-r--r--spec/serializers/runner_entity_spec.rb2
-rw-r--r--spec/serializers/serverless/domain_entity_spec.rb2
-rw-r--r--spec/serializers/service_event_entity_spec.rb2
-rw-r--r--spec/serializers/service_field_entity_spec.rb8
-rw-r--r--spec/serializers/stage_entity_spec.rb2
-rw-r--r--spec/serializers/stage_serializer_spec.rb2
-rw-r--r--spec/serializers/suggestion_entity_spec.rb113
-rw-r--r--spec/serializers/test_case_entity_spec.rb2
-rw-r--r--spec/serializers/test_report_entity_spec.rb2
-rw-r--r--spec/serializers/test_report_summary_entity_spec.rb31
-rw-r--r--spec/serializers/test_reports_comparer_entity_spec.rb2
-rw-r--r--spec/serializers/test_reports_comparer_serializer_spec.rb2
-rw-r--r--spec/serializers/test_suite_comparer_entity_spec.rb2
-rw-r--r--spec/serializers/test_suite_entity_spec.rb56
-rw-r--r--spec/serializers/test_suite_summary_entity_spec.rb24
-rw-r--r--spec/serializers/trigger_variable_entity_spec.rb2
-rw-r--r--spec/serializers/user_entity_spec.rb2
-rw-r--r--spec/serializers/user_serializer_spec.rb2
-rw-r--r--spec/serializers/web_ide_terminal_entity_spec.rb2
-rw-r--r--spec/serializers/web_ide_terminal_serializer_spec.rb2
-rw-r--r--spec/services/access_token_validation_service_spec.rb2
-rw-r--r--spec/services/alert_management/alerts/todo/create_service_spec.rb84
-rw-r--r--spec/services/alert_management/alerts/update_service_spec.rb227
-rw-r--r--spec/services/alert_management/create_alert_issue_service_spec.rb47
-rw-r--r--spec/services/alert_management/process_prometheus_alert_service_spec.rb97
-rw-r--r--spec/services/alert_management/update_alert_status_service_spec.rb66
-rw-r--r--spec/services/application_settings/update_service_spec.rb2
-rw-r--r--spec/services/applications/create_service_spec.rb2
-rw-r--r--spec/services/audit_event_service_spec.rb5
-rw-r--r--spec/services/auth/container_registry_authentication_service_spec.rb2
-rw-r--r--spec/services/authorized_project_update/periodic_recalculate_service_spec.rb2
-rw-r--r--spec/services/authorized_project_update/project_create_service_spec.rb2
-rw-r--r--spec/services/authorized_project_update/project_group_link_create_service_spec.rb190
-rw-r--r--spec/services/authorized_project_update/recalculate_for_user_range_service_spec.rb2
-rw-r--r--spec/services/auto_merge/base_service_spec.rb4
-rw-r--r--spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb16
-rw-r--r--spec/services/auto_merge_service_spec.rb2
-rw-r--r--spec/services/award_emojis/add_service_spec.rb2
-rw-r--r--spec/services/award_emojis/collect_user_emoji_service_spec.rb2
-rw-r--r--spec/services/award_emojis/destroy_service_spec.rb2
-rw-r--r--spec/services/award_emojis/toggle_service_spec.rb2
-rw-r--r--spec/services/base_container_service_spec.rb2
-rw-r--r--spec/services/base_count_service_spec.rb2
-rw-r--r--spec/services/boards/create_service_spec.rb2
-rw-r--r--spec/services/boards/issues/create_service_spec.rb2
-rw-r--r--spec/services/boards/issues/list_service_spec.rb2
-rw-r--r--spec/services/boards/issues/move_service_spec.rb2
-rw-r--r--spec/services/boards/list_service_spec.rb2
-rw-r--r--spec/services/boards/lists/create_service_spec.rb2
-rw-r--r--spec/services/boards/lists/destroy_service_spec.rb2
-rw-r--r--spec/services/boards/lists/generate_service_spec.rb2
-rw-r--r--spec/services/boards/lists/list_service_spec.rb2
-rw-r--r--spec/services/boards/lists/move_service_spec.rb2
-rw-r--r--spec/services/boards/lists/update_service_spec.rb2
-rw-r--r--spec/services/boards/visits/create_service_spec.rb2
-rw-r--r--spec/services/branches/create_service_spec.rb2
-rw-r--r--spec/services/branches/delete_merged_service_spec.rb2
-rw-r--r--spec/services/branches/delete_service_spec.rb12
-rw-r--r--spec/services/branches/diverging_commit_counts_service_spec.rb2
-rw-r--r--spec/services/branches/validate_new_service_spec.rb2
-rw-r--r--spec/services/bulk_push_event_payload_service_spec.rb2
-rw-r--r--spec/services/chat_names/authorize_user_service_spec.rb2
-rw-r--r--spec/services/chat_names/find_user_service_spec.rb2
-rw-r--r--spec/services/ci/archive_trace_service_spec.rb2
-rw-r--r--spec/services/ci/build_report_result_service_spec.rb2
-rw-r--r--spec/services/ci/cancel_user_pipelines_service_spec.rb2
-rw-r--r--spec/services/ci/compare_accessibility_reports_service_spec.rb2
-rw-r--r--spec/services/ci/compare_test_reports_service_spec.rb2
-rw-r--r--spec/services/ci/create_cross_project_pipeline_service_spec.rb2
-rw-r--r--spec/services/ci/create_job_artifacts_service_spec.rb32
-rw-r--r--spec/services/ci/create_pipeline_service/cache_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service/creation_errors_and_warnings_spec.rb114
-rw-r--r--spec/services/ci/create_pipeline_service/custom_config_content_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service/needs_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service/parameter_content_spec.rb62
-rw-r--r--spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service/pre_post_stages_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service/rules_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb92
-rw-r--r--spec/services/ci/create_web_ide_terminal_service_spec.rb2
-rw-r--r--spec/services/ci/daily_build_group_report_result_service_spec.rb2
-rw-r--r--spec/services/ci/destroy_expired_job_artifacts_service_spec.rb6
-rw-r--r--spec/services/ci/destroy_pipeline_service_spec.rb2
-rw-r--r--spec/services/ci/ensure_stage_service_spec.rb2
-rw-r--r--spec/services/ci/expire_pipeline_cache_service_spec.rb2
-rw-r--r--spec/services/ci/external_pull_requests/create_pipeline_service_spec.rb2
-rw-r--r--spec/services/ci/extract_sections_from_build_trace_service_spec.rb2
-rw-r--r--spec/services/ci/find_exposed_artifacts_service_spec.rb2
-rw-r--r--spec/services/ci/generate_coverage_reports_service_spec.rb2
-rw-r--r--spec/services/ci/generate_terraform_reports_service_spec.rb25
-rw-r--r--spec/services/ci/parse_dotenv_artifact_service_spec.rb2
-rw-r--r--spec/services/ci/pipeline_bridge_status_service_spec.rb2
-rw-r--r--spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb2
-rw-r--r--spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb2
-rw-r--r--spec/services/ci/pipeline_processing/legacy_processing_service_spec.rb2
-rw-r--r--spec/services/ci/pipeline_processing/shared_processing_service.rb2
-rw-r--r--spec/services/ci/pipeline_processing/shared_processing_service_tests_with_yaml.rb2
-rw-r--r--spec/services/ci/pipeline_schedule_service_spec.rb2
-rw-r--r--spec/services/ci/pipeline_trigger_service_spec.rb2
-rw-r--r--spec/services/ci/play_build_service_spec.rb2
-rw-r--r--spec/services/ci/play_manual_stage_service_spec.rb2
-rw-r--r--spec/services/ci/prepare_build_service_spec.rb2
-rw-r--r--spec/services/ci/process_build_service_spec.rb2
-rw-r--r--spec/services/ci/process_pipeline_service_spec.rb44
-rw-r--r--spec/services/ci/register_job_service_spec.rb17
-rw-r--r--spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb2
-rw-r--r--spec/services/ci/retry_build_service_spec.rb29
-rw-r--r--spec/services/ci/retry_pipeline_service_spec.rb2
-rw-r--r--spec/services/ci/run_scheduled_build_service_spec.rb2
-rw-r--r--spec/services/ci/stop_environments_service_spec.rb2
-rw-r--r--spec/services/ci/unlock_artifacts_service_spec.rb97
-rw-r--r--spec/services/ci/update_build_queue_service_spec.rb2
-rw-r--r--spec/services/ci/update_instance_variables_service_spec.rb2
-rw-r--r--spec/services/ci/update_runner_service_spec.rb2
-rw-r--r--spec/services/ci/web_ide_config_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/check_ingress_ip_address_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/check_installation_progress_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/check_uninstall_progress_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/check_upgrade_progress_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/create_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/destroy_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/install_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/patch_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/prometheus_config_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/prometheus_health_check_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/prometheus_update_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/schedule_update_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/uninstall_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/update_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/upgrade_service_spec.rb2
-rw-r--r--spec/services/clusters/aws/authorize_role_service_spec.rb2
-rw-r--r--spec/services/clusters/aws/fetch_credentials_service_spec.rb2
-rw-r--r--spec/services/clusters/aws/finalize_creation_service_spec.rb2
-rw-r--r--spec/services/clusters/aws/provision_service_spec.rb2
-rw-r--r--spec/services/clusters/aws/verify_provision_status_service_spec.rb2
-rw-r--r--spec/services/clusters/build_kubernetes_namespace_service_spec.rb2
-rw-r--r--spec/services/clusters/build_service_spec.rb2
-rw-r--r--spec/services/clusters/cleanup/app_service_spec.rb2
-rw-r--r--spec/services/clusters/cleanup/project_namespace_service_spec.rb2
-rw-r--r--spec/services/clusters/cleanup/service_account_service_spec.rb2
-rw-r--r--spec/services/clusters/create_service_spec.rb49
-rw-r--r--spec/services/clusters/destroy_service_spec.rb2
-rw-r--r--spec/services/clusters/gcp/fetch_operation_service_spec.rb2
-rw-r--r--spec/services/clusters/gcp/finalize_creation_service_spec.rb2
-rw-r--r--spec/services/clusters/gcp/provision_service_spec.rb2
-rw-r--r--spec/services/clusters/gcp/verify_provision_status_service_spec.rb2
-rw-r--r--spec/services/clusters/kubernetes/configure_istio_ingress_service_spec.rb2
-rw-r--r--spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb2
-rw-r--r--spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb2
-rw-r--r--spec/services/clusters/kubernetes/fetch_kubernetes_token_service_spec.rb2
-rw-r--r--spec/services/clusters/kubernetes_spec.rb2
-rw-r--r--spec/services/clusters/management/create_project_service_spec.rb2
-rw-r--r--spec/services/clusters/management/validate_management_project_permissions_service_spec.rb2
-rw-r--r--spec/services/clusters/parse_cluster_applications_artifact_service_spec.rb89
-rw-r--r--spec/services/clusters/update_service_spec.rb2
-rw-r--r--spec/services/cohorts_service_spec.rb2
-rw-r--r--spec/services/commits/cherry_pick_service_spec.rb2
-rw-r--r--spec/services/commits/commit_patch_service_spec.rb2
-rw-r--r--spec/services/commits/tag_service_spec.rb2
-rw-r--r--spec/services/compare_service_spec.rb2
-rw-r--r--spec/services/concerns/exclusive_lease_guard_spec.rb2
-rw-r--r--spec/services/concerns/merge_requests/assigns_merge_params_spec.rb2
-rw-r--r--spec/services/container_expiration_policies/update_service_spec.rb2
-rw-r--r--spec/services/container_expiration_policy_service_spec.rb2
-rw-r--r--spec/services/deploy_keys/collect_keys_service_spec.rb58
-rw-r--r--spec/services/deploy_keys/create_service_spec.rb2
-rw-r--r--spec/services/deployments/after_create_service_spec.rb2
-rw-r--r--spec/services/deployments/create_service_spec.rb2
-rw-r--r--spec/services/deployments/link_merge_requests_service_spec.rb2
-rw-r--r--spec/services/deployments/older_deployments_drop_service_spec.rb2
-rw-r--r--spec/services/deployments/update_service_spec.rb2
-rw-r--r--spec/services/design_management/delete_designs_service_spec.rb2
-rw-r--r--spec/services/design_management/design_user_notes_count_service_spec.rb2
-rw-r--r--spec/services/design_management/generate_image_versions_service_spec.rb2
-rw-r--r--spec/services/design_management/save_designs_service_spec.rb2
-rw-r--r--spec/services/discussions/capture_diff_note_position_service_spec.rb2
-rw-r--r--spec/services/discussions/capture_diff_note_positions_service_spec.rb2
-rw-r--r--spec/services/discussions/resolve_service_spec.rb2
-rw-r--r--spec/services/discussions/update_diff_position_service_spec.rb2
-rw-r--r--spec/services/draft_notes/create_service_spec.rb2
-rw-r--r--spec/services/draft_notes/destroy_service_spec.rb2
-rw-r--r--spec/services/draft_notes/publish_service_spec.rb5
-rw-r--r--spec/services/emails/confirm_service_spec.rb2
-rw-r--r--spec/services/emails/create_service_spec.rb2
-rw-r--r--spec/services/emails/destroy_service_spec.rb2
-rw-r--r--spec/services/environments/auto_stop_service_spec.rb2
-rw-r--r--spec/services/environments/reset_auto_stop_service_spec.rb2
-rw-r--r--spec/services/error_tracking/base_service_spec.rb2
-rw-r--r--spec/services/error_tracking/issue_details_service_spec.rb2
-rw-r--r--spec/services/error_tracking/issue_latest_event_service_spec.rb2
-rw-r--r--spec/services/error_tracking/issue_update_service_spec.rb2
-rw-r--r--spec/services/error_tracking/list_issues_service_spec.rb2
-rw-r--r--spec/services/error_tracking/list_projects_service_spec.rb2
-rw-r--r--spec/services/event_create_service_spec.rb106
-rw-r--r--spec/services/events/render_service_spec.rb2
-rw-r--r--spec/services/files/create_service_spec.rb2
-rw-r--r--spec/services/files/delete_service_spec.rb2
-rw-r--r--spec/services/files/multi_service_spec.rb2
-rw-r--r--spec/services/files/update_service_spec.rb2
-rw-r--r--spec/services/git/base_hooks_service_spec.rb2
-rw-r--r--spec/services/git/branch_hooks_service_spec.rb2
-rw-r--r--spec/services/git/branch_push_service_spec.rb33
-rw-r--r--spec/services/git/process_ref_changes_service_spec.rb2
-rw-r--r--spec/services/git/tag_hooks_service_spec.rb2
-rw-r--r--spec/services/git/tag_push_service_spec.rb39
-rw-r--r--spec/services/git/wiki_push_service/change_spec.rb2
-rw-r--r--spec/services/git/wiki_push_service_spec.rb10
-rw-r--r--spec/services/gpg_keys/create_service_spec.rb2
-rw-r--r--spec/services/gpg_keys/destroy_service_spec.rb15
-rw-r--r--spec/services/grafana/proxy_service_spec.rb2
-rw-r--r--spec/services/gravatar_service_spec.rb2
-rw-r--r--spec/services/groups/auto_devops_service_spec.rb2
-rw-r--r--spec/services/groups/create_service_spec.rb11
-rw-r--r--spec/services/groups/deploy_tokens/create_service_spec.rb2
-rw-r--r--spec/services/groups/deploy_tokens/destroy_service_spec.rb2
-rw-r--r--spec/services/groups/destroy_service_spec.rb2
-rw-r--r--spec/services/groups/group_links/create_service_spec.rb2
-rw-r--r--spec/services/groups/group_links/destroy_service_spec.rb2
-rw-r--r--spec/services/groups/group_links/update_service_spec.rb2
-rw-r--r--spec/services/groups/import_export/export_service_spec.rb2
-rw-r--r--spec/services/groups/import_export/import_service_spec.rb2
-rw-r--r--spec/services/groups/nested_create_service_spec.rb2
-rw-r--r--spec/services/groups/transfer_service_spec.rb2
-rw-r--r--spec/services/groups/update_service_spec.rb2
-rw-r--r--spec/services/groups/update_shared_runners_service_spec.rb230
-rw-r--r--spec/services/import/bitbucket_server_service_spec.rb113
-rw-r--r--spec/services/import/github_service_spec.rb2
-rw-r--r--spec/services/import_export_clean_up_service_spec.rb2
-rw-r--r--spec/services/incident_management/create_incident_label_service_spec.rb58
-rw-r--r--spec/services/incident_management/create_issue_service_spec.rb97
-rw-r--r--spec/services/incident_management/pager_duty/create_incident_issue_service_spec.rb104
-rw-r--r--spec/services/incident_management/pager_duty/process_webhook_service_spec.rb148
-rw-r--r--spec/services/integrations/test/project_service_spec.rb2
-rw-r--r--spec/services/issuable/bulk_update_service_spec.rb30
-rw-r--r--spec/services/issuable/clone/attributes_rewriter_spec.rb2
-rw-r--r--spec/services/issuable/clone/content_rewriter_spec.rb2
-rw-r--r--spec/services/issuable/common_system_notes_service_spec.rb18
-rw-r--r--spec/services/issuable/destroy_service_spec.rb2
-rw-r--r--spec/services/issues/build_service_spec.rb2
-rw-r--r--spec/services/issues/close_service_spec.rb2
-rw-r--r--spec/services/issues/create_service_spec.rb6
-rw-r--r--spec/services/issues/duplicate_service_spec.rb2
-rw-r--r--spec/services/issues/export_csv_service_spec.rb2
-rw-r--r--spec/services/issues/import_csv_service_spec.rb2
-rw-r--r--spec/services/issues/move_service_spec.rb47
-rw-r--r--spec/services/issues/referenced_merge_requests_service_spec.rb2
-rw-r--r--spec/services/issues/related_branches_service_spec.rb2
-rw-r--r--spec/services/issues/reopen_service_spec.rb2
-rw-r--r--spec/services/issues/reorder_service_spec.rb2
-rw-r--r--spec/services/issues/resolve_discussions_spec.rb2
-rw-r--r--spec/services/issues/update_service_spec.rb7
-rw-r--r--spec/services/issues/zoom_link_service_spec.rb2
-rw-r--r--spec/services/jira/requests/projects/list_service_spec.rb (renamed from spec/services/jira/requests/projects_spec.rb)22
-rw-r--r--spec/services/jira_import/start_import_service_spec.rb86
-rw-r--r--spec/services/jira_import/users_importer_spec.rb2
-rw-r--r--spec/services/jira_import/users_mapper_spec.rb8
-rw-r--r--spec/services/keys/create_service_spec.rb2
-rw-r--r--spec/services/keys/destroy_service_spec.rb2
-rw-r--r--spec/services/keys/last_used_service_spec.rb2
-rw-r--r--spec/services/labels/available_labels_service_spec.rb2
-rw-r--r--spec/services/labels/create_service_spec.rb2
-rw-r--r--spec/services/labels/find_or_create_service_spec.rb2
-rw-r--r--spec/services/labels/promote_service_spec.rb2
-rw-r--r--spec/services/labels/transfer_service_spec.rb2
-rw-r--r--spec/services/labels/update_service_spec.rb2
-rw-r--r--spec/services/lfs/file_transformer_spec.rb2
-rw-r--r--spec/services/lfs/lock_file_service_spec.rb2
-rw-r--r--spec/services/lfs/locks_finder_service_spec.rb2
-rw-r--r--spec/services/lfs/unlock_file_service_spec.rb2
-rw-r--r--spec/services/members/approve_access_request_service_spec.rb2
-rw-r--r--spec/services/members/create_service_spec.rb2
-rw-r--r--spec/services/members/destroy_service_spec.rb51
-rw-r--r--spec/services/members/request_access_service_spec.rb2
-rw-r--r--spec/services/members/unassign_issuables_service_spec.rb66
-rw-r--r--spec/services/members/update_service_spec.rb2
-rw-r--r--spec/services/merge_requests/add_context_service_spec.rb2
-rw-r--r--spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb2
-rw-r--r--spec/services/merge_requests/after_create_service_spec.rb2
-rw-r--r--spec/services/merge_requests/approval_service_spec.rb74
-rw-r--r--spec/services/merge_requests/assign_issues_service_spec.rb2
-rw-r--r--spec/services/merge_requests/build_service_spec.rb6
-rw-r--r--spec/services/merge_requests/close_service_spec.rb2
-rw-r--r--spec/services/merge_requests/conflicts/list_service_spec.rb3
-rw-r--r--spec/services/merge_requests/conflicts/resolve_service_spec.rb2
-rw-r--r--spec/services/merge_requests/create_from_issue_service_spec.rb10
-rw-r--r--spec/services/merge_requests/create_pipeline_service_spec.rb55
-rw-r--r--spec/services/merge_requests/create_service_spec.rb12
-rw-r--r--spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb2
-rw-r--r--spec/services/merge_requests/ff_merge_service_spec.rb2
-rw-r--r--spec/services/merge_requests/get_urls_service_spec.rb2
-rw-r--r--spec/services/merge_requests/link_lfs_objects_service_spec.rb2
-rw-r--r--spec/services/merge_requests/merge_orchestration_service_spec.rb2
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb21
-rw-r--r--spec/services/merge_requests/merge_to_ref_service_spec.rb2
-rw-r--r--spec/services/merge_requests/mergeability_check_service_spec.rb2
-rw-r--r--spec/services/merge_requests/migrate_external_diffs_service_spec.rb2
-rw-r--r--spec/services/merge_requests/post_merge_service_spec.rb67
-rw-r--r--spec/services/merge_requests/push_options_handler_service_spec.rb2
-rw-r--r--spec/services/merge_requests/pushed_branches_service_spec.rb2
-rw-r--r--spec/services/merge_requests/rebase_service_spec.rb2
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb9
-rw-r--r--spec/services/merge_requests/reload_diffs_service_spec.rb8
-rw-r--r--spec/services/merge_requests/remove_approval_service_spec.rb46
-rw-r--r--spec/services/merge_requests/reopen_service_spec.rb2
-rw-r--r--spec/services/merge_requests/resolved_discussion_notification_service_spec.rb2
-rw-r--r--spec/services/merge_requests/squash_service_spec.rb38
-rw-r--r--spec/services/merge_requests/update_service_spec.rb7
-rw-r--r--spec/services/metrics/dashboard/annotations/create_service_spec.rb2
-rw-r--r--spec/services/metrics/dashboard/annotations/delete_service_spec.rb2
-rw-r--r--spec/services/metrics/dashboard/clone_dashboard_service_spec.rb19
-rw-r--r--spec/services/metrics/dashboard/cluster_dashboard_service_spec.rb59
-rw-r--r--spec/services/metrics/dashboard/cluster_metrics_embed_service_spec.rb72
-rw-r--r--spec/services/metrics/dashboard/custom_dashboard_service_spec.rb20
-rw-r--r--spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb2
-rw-r--r--spec/services/metrics/dashboard/default_embed_service_spec.rb2
-rw-r--r--spec/services/metrics/dashboard/dynamic_embed_service_spec.rb2
-rw-r--r--spec/services/metrics/dashboard/gitlab_alert_embed_service_spec.rb2
-rw-r--r--spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb6
-rw-r--r--spec/services/metrics/dashboard/pod_dashboard_service_spec.rb20
-rw-r--r--spec/services/metrics/dashboard/self_monitoring_dashboard_service_spec.rb22
-rw-r--r--spec/services/metrics/dashboard/system_dashboard_service_spec.rb23
-rw-r--r--spec/services/metrics/dashboard/transient_embed_service_spec.rb2
-rw-r--r--spec/services/metrics/dashboard/update_dashboard_service_spec.rb2
-rw-r--r--spec/services/metrics/sample_metrics_service_spec.rb2
-rw-r--r--spec/services/metrics/users_starred_dashboards/create_service_spec.rb2
-rw-r--r--spec/services/metrics/users_starred_dashboards/delete_service_spec.rb2
-rw-r--r--spec/services/milestones/close_service_spec.rb2
-rw-r--r--spec/services/milestones/closed_issues_count_service_spec.rb2
-rw-r--r--spec/services/milestones/create_service_spec.rb2
-rw-r--r--spec/services/milestones/destroy_service_spec.rb2
-rw-r--r--spec/services/milestones/find_or_create_service_spec.rb2
-rw-r--r--spec/services/milestones/issues_count_service_spec.rb2
-rw-r--r--spec/services/milestones/promote_service_spec.rb2
-rw-r--r--spec/services/milestones/transfer_service_spec.rb2
-rw-r--r--spec/services/milestones/update_service_spec.rb2
-rw-r--r--spec/services/namespaces/check_storage_size_service_spec.rb165
-rw-r--r--spec/services/namespaces/statistics_refresher_service_spec.rb2
-rw-r--r--spec/services/note_summary_spec.rb2
-rw-r--r--spec/services/notes/build_service_spec.rb2
-rw-r--r--spec/services/notes/create_service_spec.rb4
-rw-r--r--spec/services/notes/destroy_service_spec.rb2
-rw-r--r--spec/services/notes/post_process_service_spec.rb2
-rw-r--r--spec/services/notes/quick_actions_service_spec.rb2
-rw-r--r--spec/services/notes/render_service_spec.rb2
-rw-r--r--spec/services/notes/resolve_service_spec.rb2
-rw-r--r--spec/services/notes/update_service_spec.rb41
-rw-r--r--spec/services/notification_recipients/build_service_spec.rb2
-rw-r--r--spec/services/notification_recipients/builder/default_spec.rb2
-rw-r--r--spec/services/notification_recipients/builder/new_note_spec.rb2
-rw-r--r--spec/services/notification_service_spec.rb95
-rw-r--r--spec/services/packages/composer/composer_json_service_spec.rb39
-rw-r--r--spec/services/packages/composer/create_package_service_spec.rb97
-rw-r--r--spec/services/packages/composer/version_parser_service_spec.rb31
-rw-r--r--spec/services/packages/conan/create_package_file_service_spec.rb130
-rw-r--r--spec/services/packages/conan/create_package_service_spec.rb48
-rw-r--r--spec/services/packages/conan/search_service_spec.rb74
-rw-r--r--spec/services/packages/create_dependency_service_spec.rb113
-rw-r--r--spec/services/packages/create_package_file_service_spec.rb38
-rw-r--r--spec/services/packages/maven/create_package_service_spec.rb77
-rw-r--r--spec/services/packages/maven/find_or_create_package_service_spec.rb38
-rw-r--r--spec/services/packages/npm/create_package_service_spec.rb96
-rw-r--r--spec/services/packages/npm/create_tag_service_spec.rb53
-rw-r--r--spec/services/packages/nuget/create_dependency_service_spec.rb76
-rw-r--r--spec/services/packages/nuget/create_package_service_spec.rb34
-rw-r--r--spec/services/packages/nuget/metadata_extraction_service_spec.rb106
-rw-r--r--spec/services/packages/nuget/search_service_spec.rb116
-rw-r--r--spec/services/packages/nuget/sync_metadatum_service_spec.rb57
-rw-r--r--spec/services/packages/nuget/update_package_from_metadata_service_spec.rb237
-rw-r--r--spec/services/packages/pypi/create_package_service_spec.rb83
-rw-r--r--spec/services/packages/remove_tag_service_spec.rb20
-rw-r--r--spec/services/packages/update_tags_service_spec.rb59
-rw-r--r--spec/services/pages/delete_services_spec.rb2
-rw-r--r--spec/services/pages_domains/create_acme_order_service_spec.rb2
-rw-r--r--spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb2
-rw-r--r--spec/services/pages_domains/retry_acme_order_service_spec.rb2
-rw-r--r--spec/services/personal_access_tokens/create_service_spec.rb2
-rw-r--r--spec/services/personal_access_tokens/last_used_service_spec.rb47
-rw-r--r--spec/services/pod_logs/base_service_spec.rb2
-rw-r--r--spec/services/pod_logs/elasticsearch_service_spec.rb2
-rw-r--r--spec/services/pod_logs/kubernetes_service_spec.rb2
-rw-r--r--spec/services/post_receive_service_spec.rb37
-rw-r--r--spec/services/preview_markdown_service_spec.rb2
-rw-r--r--spec/services/projects/after_import_service_spec.rb22
-rw-r--r--spec/services/projects/after_rename_service_spec.rb2
-rw-r--r--spec/services/projects/alerting/notify_service_spec.rb110
-rw-r--r--spec/services/projects/auto_devops/disable_service_spec.rb2
-rw-r--r--spec/services/projects/autocomplete_service_spec.rb2
-rw-r--r--spec/services/projects/batch_open_issues_count_service_spec.rb2
-rw-r--r--spec/services/projects/cleanup_service_spec.rb2
-rw-r--r--spec/services/projects/container_repository/cleanup_tags_service_spec.rb2
-rw-r--r--spec/services/projects/container_repository/delete_tags_service_spec.rb118
-rw-r--r--spec/services/projects/container_repository/destroy_service_spec.rb2
-rw-r--r--spec/services/projects/count_service_spec.rb2
-rw-r--r--spec/services/projects/create_from_template_service_spec.rb2
-rw-r--r--spec/services/projects/create_service_spec.rb61
-rw-r--r--spec/services/projects/deploy_tokens/create_service_spec.rb2
-rw-r--r--spec/services/projects/deploy_tokens/destroy_service_spec.rb2
-rw-r--r--spec/services/projects/destroy_rollback_service_spec.rb2
-rw-r--r--spec/services/projects/destroy_service_spec.rb2
-rw-r--r--spec/services/projects/detect_repository_languages_service_spec.rb2
-rw-r--r--spec/services/projects/download_service_spec.rb2
-rw-r--r--spec/services/projects/enable_deploy_key_service_spec.rb2
-rw-r--r--spec/services/projects/fetch_statistics_increment_service_spec.rb2
-rw-r--r--spec/services/projects/fork_service_spec.rb6
-rw-r--r--spec/services/projects/forks_count_service_spec.rb2
-rw-r--r--spec/services/projects/git_deduplication_service_spec.rb4
-rw-r--r--spec/services/projects/gitlab_projects_import_service_spec.rb2
-rw-r--r--spec/services/projects/group_links/create_service_spec.rb50
-rw-r--r--spec/services/projects/group_links/destroy_service_spec.rb2
-rw-r--r--spec/services/projects/group_links/update_service_spec.rb2
-rw-r--r--spec/services/projects/hashed_storage/base_attachment_service_spec.rb2
-rw-r--r--spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb2
-rw-r--r--spec/services/projects/hashed_storage/migrate_repository_service_spec.rb2
-rw-r--r--spec/services/projects/hashed_storage/migration_service_spec.rb2
-rw-r--r--spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb2
-rw-r--r--spec/services/projects/hashed_storage/rollback_repository_service_spec.rb2
-rw-r--r--spec/services/projects/hashed_storage/rollback_service_spec.rb2
-rw-r--r--spec/services/projects/housekeeping_service_spec.rb2
-rw-r--r--spec/services/projects/import_error_filter_spec.rb2
-rw-r--r--spec/services/projects/import_export/export_service_spec.rb2
-rw-r--r--spec/services/projects/import_service_spec.rb2
-rw-r--r--spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb2
-rw-r--r--spec/services/projects/lfs_pointers/lfs_download_service_spec.rb2
-rw-r--r--spec/services/projects/lfs_pointers/lfs_import_service_spec.rb2
-rw-r--r--spec/services/projects/lfs_pointers/lfs_link_service_spec.rb2
-rw-r--r--spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb2
-rw-r--r--spec/services/projects/move_access_service_spec.rb2
-rw-r--r--spec/services/projects/move_deploy_keys_projects_service_spec.rb2
-rw-r--r--spec/services/projects/move_forks_service_spec.rb2
-rw-r--r--spec/services/projects/move_lfs_objects_projects_service_spec.rb2
-rw-r--r--spec/services/projects/move_notification_settings_service_spec.rb2
-rw-r--r--spec/services/projects/move_project_authorizations_service_spec.rb2
-rw-r--r--spec/services/projects/move_project_group_links_service_spec.rb2
-rw-r--r--spec/services/projects/move_project_members_service_spec.rb2
-rw-r--r--spec/services/projects/move_users_star_projects_service_spec.rb2
-rw-r--r--spec/services/projects/open_issues_count_service_spec.rb2
-rw-r--r--spec/services/projects/open_merge_requests_count_service_spec.rb2
-rw-r--r--spec/services/projects/operations/update_service_spec.rb2
-rw-r--r--spec/services/projects/overwrite_project_service_spec.rb2
-rw-r--r--spec/services/projects/participants_service_spec.rb2
-rw-r--r--spec/services/projects/prometheus/alerts/create_events_service_spec.rb312
-rw-r--r--spec/services/projects/prometheus/alerts/create_service_spec.rb2
-rw-r--r--spec/services/projects/prometheus/alerts/destroy_service_spec.rb2
-rw-r--r--spec/services/projects/prometheus/alerts/notify_service_spec.rb127
-rw-r--r--spec/services/projects/prometheus/alerts/update_service_spec.rb2
-rw-r--r--spec/services/projects/prometheus/metrics/destroy_service_spec.rb2
-rw-r--r--spec/services/projects/prometheus/metrics/update_service_spec.rb2
-rw-r--r--spec/services/projects/propagate_service_template_spec.rb2
-rw-r--r--spec/services/projects/protect_default_branch_service_spec.rb2
-rw-r--r--spec/services/projects/repository_languages_service_spec.rb2
-rw-r--r--spec/services/projects/transfer_service_spec.rb2
-rw-r--r--spec/services/projects/unlink_fork_service_spec.rb5
-rw-r--r--spec/services/projects/update_pages_configuration_service_spec.rb2
-rw-r--r--spec/services/projects/update_pages_service_spec.rb2
-rw-r--r--spec/services/projects/update_remote_mirror_service_spec.rb2
-rw-r--r--spec/services/projects/update_repository_storage_service_spec.rb38
-rw-r--r--spec/services/projects/update_service_spec.rb2
-rw-r--r--spec/services/projects/update_statistics_service_spec.rb2
-rw-r--r--spec/services/prometheus/create_default_alerts_service_spec.rb2
-rw-r--r--spec/services/prometheus/proxy_service_spec.rb2
-rw-r--r--spec/services/prometheus/proxy_variable_substitution_service_spec.rb2
-rw-r--r--spec/services/protected_branches/create_service_spec.rb2
-rw-r--r--spec/services/protected_branches/destroy_service_spec.rb2
-rw-r--r--spec/services/protected_branches/update_service_spec.rb2
-rw-r--r--spec/services/protected_tags/create_service_spec.rb2
-rw-r--r--spec/services/protected_tags/destroy_service_spec.rb2
-rw-r--r--spec/services/protected_tags/update_service_spec.rb2
-rw-r--r--spec/services/push_event_payload_service_spec.rb2
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb4
-rw-r--r--spec/services/quick_actions/target_service_spec.rb2
-rw-r--r--spec/services/releases/create_evidence_service_spec.rb2
-rw-r--r--spec/services/releases/create_service_spec.rb2
-rw-r--r--spec/services/releases/destroy_service_spec.rb2
-rw-r--r--spec/services/releases/update_service_spec.rb2
-rw-r--r--spec/services/repositories/destroy_rollback_service_spec.rb2
-rw-r--r--spec/services/repositories/destroy_service_spec.rb17
-rw-r--r--spec/services/repositories/shell_destroy_service_spec.rb2
-rw-r--r--spec/services/repository_archive_clean_up_service_spec.rb2
-rw-r--r--spec/services/reset_project_cache_service_spec.rb2
-rw-r--r--spec/services/resource_access_tokens/create_service_spec.rb23
-rw-r--r--spec/services/resource_access_tokens/revoke_service_spec.rb2
-rw-r--r--spec/services/resource_events/change_labels_service_spec.rb2
-rw-r--r--spec/services/resource_events/change_milestone_service_spec.rb2
-rw-r--r--spec/services/resource_events/change_state_service_spec.rb91
-rw-r--r--spec/services/resource_events/merge_into_notes_service_spec.rb4
-rw-r--r--spec/services/resource_events/synthetic_label_notes_builder_service_spec.rb2
-rw-r--r--spec/services/resource_events/synthetic_milestone_notes_builder_service_spec.rb2
-rw-r--r--spec/services/search/global_service_spec.rb2
-rw-r--r--spec/services/search/group_service_spec.rb2
-rw-r--r--spec/services/search/snippet_service_spec.rb2
-rw-r--r--spec/services/search_service_spec.rb2
-rw-r--r--spec/services/serverless/associate_domain_service_spec.rb2
-rw-r--r--spec/services/service_desk_settings/update_service_spec.rb46
-rw-r--r--spec/services/service_response_spec.rb2
-rw-r--r--spec/services/snippets/bulk_destroy_service_spec.rb2
-rw-r--r--spec/services/snippets/count_service_spec.rb2
-rw-r--r--spec/services/snippets/create_service_spec.rb54
-rw-r--r--spec/services/snippets/destroy_service_spec.rb28
-rw-r--r--spec/services/snippets/repository_validation_service_spec.rb2
-rw-r--r--spec/services/snippets/update_service_spec.rb256
-rw-r--r--spec/services/snippets/update_statistics_service_spec.rb86
-rw-r--r--spec/services/spam/akismet_service_spec.rb2
-rw-r--r--spec/services/spam/ham_service_spec.rb2
-rw-r--r--spec/services/spam/mark_as_spam_service_spec.rb2
-rw-r--r--spec/services/spam/spam_action_service_spec.rb2
-rw-r--r--spec/services/spam/spam_verdict_service_spec.rb31
-rw-r--r--spec/services/submit_usage_ping_service_spec.rb2
-rw-r--r--spec/services/submodules/update_service_spec.rb2
-rw-r--r--spec/services/suggestions/apply_service_spec.rb2
-rw-r--r--spec/services/suggestions/create_service_spec.rb2
-rw-r--r--spec/services/suggestions/outdate_service_spec.rb2
-rw-r--r--spec/services/system_hooks_service_spec.rb2
-rw-r--r--spec/services/system_note_service_spec.rb46
-rw-r--r--spec/services/system_notes/alert_management_service_spec.rb35
-rw-r--r--spec/services/system_notes/base_service_spec.rb2
-rw-r--r--spec/services/system_notes/commit_service_spec.rb2
-rw-r--r--spec/services/system_notes/design_management_service_spec.rb2
-rw-r--r--spec/services/system_notes/issuables_service_spec.rb64
-rw-r--r--spec/services/system_notes/merge_requests_service_spec.rb20
-rw-r--r--spec/services/system_notes/time_tracking_service_spec.rb2
-rw-r--r--spec/services/system_notes/zoom_service_spec.rb2
-rw-r--r--spec/services/tags/create_service_spec.rb2
-rw-r--r--spec/services/tags/destroy_service_spec.rb12
-rw-r--r--spec/services/task_list_toggle_service_spec.rb2
-rw-r--r--spec/services/terraform/remote_state_handler_spec.rb123
-rw-r--r--spec/services/test_hooks/project_service_spec.rb2
-rw-r--r--spec/services/test_hooks/system_service_spec.rb2
-rw-r--r--spec/services/todo_service_spec.rb2
-rw-r--r--spec/services/todos/destroy/confidential_issue_service_spec.rb2
-rw-r--r--spec/services/todos/destroy/entity_leave_service_spec.rb2
-rw-r--r--spec/services/todos/destroy/group_private_service_spec.rb2
-rw-r--r--spec/services/todos/destroy/private_features_service_spec.rb2
-rw-r--r--spec/services/todos/destroy/project_private_service_spec.rb2
-rw-r--r--spec/services/update_container_registry_info_service_spec.rb115
-rw-r--r--spec/services/update_merge_request_metrics_service_spec.rb2
-rw-r--r--spec/services/upload_service_spec.rb2
-rw-r--r--spec/services/user_project_access_changed_service_spec.rb2
-rw-r--r--spec/services/users/activity_service_spec.rb2
-rw-r--r--spec/services/users/block_service_spec.rb2
-rw-r--r--spec/services/users/build_service_spec.rb2
-rw-r--r--spec/services/users/create_service_spec.rb2
-rw-r--r--spec/services/users/destroy_service_spec.rb2
-rw-r--r--spec/services/users/keys_count_service_spec.rb2
-rw-r--r--spec/services/users/last_push_event_service_spec.rb2
-rw-r--r--spec/services/users/migrate_to_ghost_user_service_spec.rb2
-rw-r--r--spec/services/users/refresh_authorized_projects_service_spec.rb2
-rw-r--r--spec/services/users/repair_ldap_blocked_service_spec.rb2
-rw-r--r--spec/services/users/respond_to_terms_service_spec.rb2
-rw-r--r--spec/services/users/set_status_service_spec.rb2
-rw-r--r--spec/services/users/signup_service_spec.rb2
-rw-r--r--spec/services/users/update_canonical_email_service_spec.rb2
-rw-r--r--spec/services/users/update_highest_member_role_service_spec.rb2
-rw-r--r--spec/services/users/update_service_spec.rb2
-rw-r--r--spec/services/verify_pages_domain_service_spec.rb2
-rw-r--r--spec/services/web_hook_service_spec.rb2
-rw-r--r--spec/services/wiki_pages/base_service_spec.rb2
-rw-r--r--spec/services/wiki_pages/create_service_spec.rb2
-rw-r--r--spec/services/wiki_pages/destroy_service_spec.rb2
-rw-r--r--spec/services/wiki_pages/event_create_service_spec.rb17
-rw-r--r--spec/services/wiki_pages/update_service_spec.rb2
-rw-r--r--spec/services/wikis/create_attachment_service_spec.rb2
-rw-r--r--spec/services/x509_certificate_revoke_service_spec.rb2
-rw-r--r--spec/sidekiq/cron/job_gem_dependency_spec.rb2
-rw-r--r--spec/simplecov_env.rb53
-rw-r--r--spec/spec_helper.rb6
-rw-r--r--spec/support/capybara.rb22
-rw-r--r--spec/support/controllers/project_import_rate_limiter_shared_examples.rb2
-rw-r--r--spec/support/helpers/expect_offense.rb22
-rw-r--r--spec/support/helpers/fast_rails_root.rb10
-rw-r--r--spec/support/helpers/filtered_search_helpers.rb5
-rw-r--r--spec/support/helpers/jira_service_helper.rb3
-rw-r--r--spec/support/helpers/metrics_dashboard_helpers.rb14
-rw-r--r--spec/support/helpers/notification_helpers.rb12
-rw-r--r--spec/support/helpers/packages_manager_api_spec_helper.rb46
-rw-r--r--spec/support/helpers/partitioning_helpers.rb46
-rw-r--r--spec/support/helpers/rack_attack_spec_helpers.rb12
-rw-r--r--spec/support/helpers/reference_parser_helpers.rb4
-rw-r--r--spec/support/helpers/snippet_helpers.rb14
-rw-r--r--spec/support/helpers/stub_configuration.rb8
-rw-r--r--spec/support/helpers/stub_object_storage.rb26
-rw-r--r--spec/support/helpers/test_env.rb30
-rw-r--r--spec/support/helpers/trigger_helpers.rb5
-rw-r--r--spec/support/helpers/usage_data_helpers.rb12
-rw-r--r--spec/support/matchers/background_migrations_matchers.rb3
-rw-r--r--spec/support/matchers/jsonb_matchers.rb24
-rw-r--r--spec/support/rspec.rb6
-rw-r--r--spec/support/services/clusters/create_service_shared.rb9
-rw-r--r--spec/support/services/issuable_description_quick_actions_shared_examples.rb (renamed from spec/support/services/issuable_create_service_slash_commands_shared_examples.rb)37
-rw-r--r--spec/support/shared_contexts/cache_allowed_users_in_namespace_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/design_management_shared_contexts.rb2
-rw-r--r--spec/support/shared_contexts/features/error_tracking_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb10
-rw-r--r--spec/support/shared_contexts/issuable/merge_request_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/issuable/project_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/navbar_structure_context.rb1
-rw-r--r--spec/support/shared_contexts/policies/group_policy_shared_context.rb3
-rw-r--r--spec/support/shared_contexts/presenters/nuget_shared_context.rb41
-rw-r--r--spec/support/shared_contexts/project_service_jira_context.rb15
-rw-r--r--spec/support/shared_contexts/project_service_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/prometheus/alert_shared_context.rb76
-rw-r--r--spec/support/shared_contexts/requests/api/graphql/jira_import/jira_projects_context.rb70
-rw-r--r--spec/support/shared_contexts/sentry_error_tracking_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/services_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/spam_constants.rb2
-rw-r--r--spec/support/shared_examples/controllers/import_controller_new_import_ui_shared_examples.rb36
-rw-r--r--spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb33
-rw-r--r--spec/support/shared_examples/controllers/known_sign_in_shared_examples.rb98
-rw-r--r--spec/support/shared_examples/controllers/metrics/dashboard/prometheus_api_proxy_shared_examples.rb147
-rw-r--r--spec/support/shared_examples/controllers/metrics_dashboard_shared_examples.rb27
-rw-r--r--spec/support/shared_examples/controllers/namespace_storage_limit_alert_shared_examples.rb53
-rw-r--r--spec/support/shared_examples/controllers/snippet_blob_shared_examples.rb49
-rw-r--r--spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb41
-rw-r--r--spec/support/shared_examples/controllers/unique_visits_shared_examples.rb29
-rw-r--r--spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb111
-rw-r--r--spec/support/shared_examples/create_alert_issue_shared_examples.rb27
-rw-r--r--spec/support/shared_examples/features/discussion_comments_shared_example.rb2
-rw-r--r--spec/support/shared_examples/features/editable_merge_request_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/features/error_tracking_shared_example.rb6
-rw-r--r--spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb7
-rw-r--r--spec/support/shared_examples/graphql/jira_import/jira_import_resolver_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/graphql/mutation_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/graphql/resolves_issuable_shared_examples.rb41
-rw-r--r--spec/support/shared_examples/helm_commands_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/lib/gitlab/cycle_analytics/base_stage_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/lib/gitlab/cycle_analytics/default_query_config_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb66
-rw-r--r--spec/support/shared_examples/lib/gitlab/gl_repository_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/lib/gitlab/import/stuck_import_job_workers_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/gitlab/jira_import/base_importer_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/wikis_api_examples.rb174
-rw-r--r--spec/support/shared_examples/models/cluster_application_status_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/models/cluster_application_version_shared_examples.rb28
-rw-r--r--spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/models/jira_import_state_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/models/note_access_check_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/models/services_fields_shared_examples.rb31
-rw-r--r--spec/support/shared_examples/models/synthetic_note_shared_examples.rb17
-rw-r--r--spec/support/shared_examples/namespaces/hierarchy_examples.rb21
-rw-r--r--spec/support/shared_examples/policies/namespace_policy_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/policies/project_policy_shared_examples.rb28
-rw-r--r--spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb91
-rw-r--r--spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb138
-rw-r--r--spec/support/shared_examples/requests/api/graphql/projects/services_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/notes_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb408
-rw-r--r--spec/support/shared_examples/requests/api/packages_shared_examples.rb43
-rw-r--r--spec/support/shared_examples/requests/api/packages_tags_shared_examples.rb185
-rw-r--r--spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb152
-rw-r--r--spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb37
-rw-r--r--spec/support/shared_examples/requests/api/snippets_shared_examples.rb79
-rw-r--r--spec/support/shared_examples/requests/snippet_shared_examples.rb24
-rw-r--r--spec/support/shared_examples/resource_events.rb26
-rw-r--r--spec/support/shared_examples/routing/resource_routing_shared_examples.rb73
-rw-r--r--spec/support/shared_examples/routing/wiki_routing_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/services/alert_management_shared_examples.rb41
-rw-r--r--spec/support/shared_examples/services/clusters/parse_cluster_applications_artifact_shared_examples.rb89
-rw-r--r--spec/support/shared_examples/services/common_system_notes_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/jira_import/start_import_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb80
-rw-r--r--spec/support/shared_examples/services/packages_shared_examples.rb193
-rw-r--r--spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/services/resource_events/change_milestone_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/snippet_blob_shared_examples.rb24
-rw-r--r--spec/support/shared_examples/uploaders/upload_type_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/views/pipeline_status_changes_email.rb2
-rw-r--r--spec/support/shared_examples/views/plain_text_email.rb9
-rw-r--r--spec/support/shared_examples/workers/gitlab/jira_import/jira_import_workers_shared_examples.rb8
-rw-r--r--spec/support_specs/helpers/active_record/query_recorder_spec.rb2
-rw-r--r--spec/support_specs/helpers/graphql_helpers_spec.rb2
-rw-r--r--spec/support_specs/helpers/stub_feature_flags_spec.rb2
-rw-r--r--spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb2
-rw-r--r--spec/tasks/cache/clear/redis_spec.rb2
-rw-r--r--spec/tasks/config_lint_spec.rb4
-rw-r--r--spec/tasks/gitlab/artifacts/check_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/artifacts/migrate_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/check_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/cleanup_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/container_registry_rake_spec.rb84
-rw-r--r--spec/tasks/gitlab/db_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/external_diffs_rake_spec.rb34
-rw-r--r--spec/tasks/gitlab/generate_sample_prometheus_data_spec.rb2
-rw-r--r--spec/tasks/gitlab/git_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/gitaly_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/info_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/ldap_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/lfs/check_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/lfs/migrate_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/packages/migrate_rake_spec.rb39
-rw-r--r--spec/tasks/gitlab/praefect_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/seed/group_seed_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/shell_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/snippets_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/storage_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/task_helpers_spec.rb2
-rw-r--r--spec/tasks/gitlab/update_templates_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/uploads/check_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/uploads/migrate_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/web_hook_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/workhorse_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/x509/update_rake_spec.rb2
-rw-r--r--spec/tasks/migrate/schema_check_rake_spec.rb2
-rw-r--r--spec/tasks/tokens_spec.rb2
-rw-r--r--spec/tooling/lib/tooling/helm3_client_spec.rb (renamed from spec/lib/quality/helm3_client_spec.rb)6
-rw-r--r--spec/tooling/lib/tooling/kubernetes_client_spec.rb (renamed from spec/lib/quality/kubernetes_client_spec.rb)6
-rw-r--r--spec/tooling/lib/tooling/test_file_finder_spec.rb74
-rw-r--r--spec/uploaders/attachment_uploader_spec.rb2
-rw-r--r--spec/uploaders/avatar_uploader_spec.rb2
-rw-r--r--spec/uploaders/content_type_whitelist_spec.rb2
-rw-r--r--spec/uploaders/design_management/design_v432x230_uploader_spec.rb2
-rw-r--r--spec/uploaders/external_diff_uploader_spec.rb2
-rw-r--r--spec/uploaders/favicon_uploader_spec.rb2
-rw-r--r--spec/uploaders/file_mover_spec.rb2
-rw-r--r--spec/uploaders/file_uploader_spec.rb2
-rw-r--r--spec/uploaders/gitlab_uploader_spec.rb2
-rw-r--r--spec/uploaders/import_export_uploader_spec.rb2
-rw-r--r--spec/uploaders/job_artifact_uploader_spec.rb2
-rw-r--r--spec/uploaders/lfs_object_uploader_spec.rb2
-rw-r--r--spec/uploaders/namespace_file_uploader_spec.rb2
-rw-r--r--spec/uploaders/object_storage_spec.rb2
-rw-r--r--spec/uploaders/packages/package_file_uploader_spec.rb45
-rw-r--r--spec/uploaders/personal_file_uploader_spec.rb2
-rw-r--r--spec/uploaders/records_uploads_spec.rb2
-rw-r--r--spec/uploaders/terraform/state_uploader_spec.rb2
-rw-r--r--spec/uploaders/uploader_helper_spec.rb2
-rw-r--r--spec/uploaders/workers/object_storage/background_move_worker_spec.rb2
-rw-r--r--spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb2
-rw-r--r--spec/validators/addressable_url_validator_spec.rb2
-rw-r--r--spec/validators/array_members_validator_spec.rb69
-rw-r--r--spec/validators/branch_filter_validator_spec.rb2
-rw-r--r--spec/validators/color_validator_spec.rb2
-rw-r--r--spec/validators/cron_freeze_period_timezone_validator_spec.rb2
-rw-r--r--spec/validators/cron_validator_spec.rb2
-rw-r--r--spec/validators/devise_email_validator_spec.rb2
-rw-r--r--spec/validators/js_regex_validator_spec.rb2
-rw-r--r--spec/validators/json_schema_validator_spec.rb2
-rw-r--r--spec/validators/named_ecdsa_key_validator_spec.rb2
-rw-r--r--spec/validators/namespace_path_validator_spec.rb2
-rw-r--r--spec/validators/project_path_validator_spec.rb2
-rw-r--r--spec/validators/public_url_validator_spec.rb2
-rw-r--r--spec/validators/qualified_domain_array_validator_spec.rb2
-rw-r--r--spec/validators/sha_validator_spec.rb2
-rw-r--r--spec/validators/system_hook_url_validator_spec.rb2
-rw-r--r--spec/validators/variable_duplicates_validator_spec.rb2
-rw-r--r--spec/validators/x509_certificate_credentials_validator_spec.rb2
-rw-r--r--spec/views/admin/application_settings/_eks.html.haml_spec.rb2
-rw-r--r--spec/views/admin/application_settings/_repository_storage.html.haml_spec.rb2
-rw-r--r--spec/views/admin/application_settings/general.html.haml_spec.rb2
-rw-r--r--spec/views/admin/application_settings/repository.html.haml_spec.rb46
-rw-r--r--spec/views/admin/dashboard/index.html.haml_spec.rb2
-rw-r--r--spec/views/admin/sessions/new.html.haml_spec.rb2
-rw-r--r--spec/views/admin/sessions/two_factor.html.haml_spec.rb2
-rw-r--r--spec/views/admin/users/_user.html.haml_spec.rb2
-rw-r--r--spec/views/ci/status/_badge.html.haml_spec.rb2
-rw-r--r--spec/views/ci/status/_icon.html.haml_spec.rb2
-rw-r--r--spec/views/dashboard/projects/_blank_state_admin_welcome.haml_spec.rb2
-rw-r--r--spec/views/dashboard/projects/_nav.html.haml_spec.rb2
-rw-r--r--spec/views/devise/sessions/new.html.haml_spec.rb2
-rw-r--r--spec/views/devise/shared/_signin_box.html.haml_spec.rb2
-rw-r--r--spec/views/errors/access_denied.html.haml_spec.rb2
-rw-r--r--spec/views/events/event/_push.html.haml_spec.rb2
-rw-r--r--spec/views/groups/_home_panel.html.haml_spec.rb2
-rw-r--r--spec/views/groups/edit.html.haml_spec.rb2
-rw-r--r--spec/views/help/index.html.haml_spec.rb2
-rw-r--r--spec/views/help/instance_configuration.html.haml_spec.rb2
-rw-r--r--spec/views/help/show.html.haml_spec.rb2
-rw-r--r--spec/views/import/gitlab_projects/new.html.haml_spec.rb2
-rw-r--r--spec/views/layouts/_head.html.haml_spec.rb2
-rw-r--r--spec/views/layouts/application.html.haml_spec.rb2
-rw-r--r--spec/views/layouts/header/_new_dropdown.haml_spec.rb2
-rw-r--r--spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb2
-rw-r--r--spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb2
-rw-r--r--spec/views/layouts/nav/sidebar/_instance_statistics.html.haml_spec.rb2
-rw-r--r--spec/views/layouts/nav/sidebar/_profile.html.haml_spec.rb2
-rw-r--r--spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb36
-rw-r--r--spec/views/notify/changed_milestone_email.html.haml_spec.rb2
-rw-r--r--spec/views/notify/pipeline_failed_email.html.haml_spec.rb2
-rw-r--r--spec/views/notify/pipeline_failed_email.text.erb_spec.rb2
-rw-r--r--spec/views/notify/pipeline_fixed_email.html.haml_spec.rb2
-rw-r--r--spec/views/notify/pipeline_fixed_email.text.erb_spec.rb2
-rw-r--r--spec/views/notify/pipeline_success_email.html.haml_spec.rb2
-rw-r--r--spec/views/notify/pipeline_success_email.text.erb_spec.rb2
-rw-r--r--spec/views/notify/push_to_merge_request_email.text.haml_spec.rb19
-rw-r--r--spec/views/profiles/preferences/show.html.haml_spec.rb2
-rw-r--r--spec/views/profiles/show.html.haml_spec.rb2
-rw-r--r--spec/views/projects/_home_panel.html.haml_spec.rb2
-rw-r--r--spec/views/projects/blob/_viewer.html.haml_spec.rb2
-rw-r--r--spec/views/projects/buttons/_dropdown.html.haml_spec.rb2
-rw-r--r--spec/views/projects/ci/lints/show.html.haml_spec.rb2
-rw-r--r--spec/views/projects/clusters/clusters/gcp/_form.html.haml_spec.rb2
-rw-r--r--spec/views/projects/commit/_commit_box.html.haml_spec.rb2
-rw-r--r--spec/views/projects/commit/branches.html.haml_spec.rb2
-rw-r--r--spec/views/projects/commit/show.html.haml_spec.rb2
-rw-r--r--spec/views/projects/commits/_commit.html.haml_spec.rb2
-rw-r--r--spec/views/projects/deployments/_confirm_rollback_modal_spec.html_spec.rb2
-rw-r--r--spec/views/projects/diffs/_stats.html.haml_spec.rb2
-rw-r--r--spec/views/projects/diffs/_viewer.html.haml_spec.rb2
-rw-r--r--spec/views/projects/edit.html.haml_spec.rb2
-rw-r--r--spec/views/projects/environments/terminal.html.haml_spec.rb2
-rw-r--r--spec/views/projects/imports/new.html.haml_spec.rb2
-rw-r--r--spec/views/projects/issues/_related_branches.html.haml_spec.rb2
-rw-r--r--spec/views/projects/issues/import_csv/_button.html.haml_spec.rb2
-rw-r--r--spec/views/projects/issues/show.html.haml_spec.rb12
-rw-r--r--spec/views/projects/jobs/_build.html.haml_spec.rb2
-rw-r--r--spec/views/projects/jobs/_generic_commit_status.html.haml_spec.rb2
-rw-r--r--spec/views/projects/jobs/show.html.haml_spec.rb2
-rw-r--r--spec/views/projects/merge_requests/_commits.html.haml_spec.rb2
-rw-r--r--spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb2
-rw-r--r--spec/views/projects/merge_requests/diffs/_diffs.html.haml_spec.rb2
-rw-r--r--spec/views/projects/merge_requests/edit.html.haml_spec.rb2
-rw-r--r--spec/views/projects/merge_requests/show.html.haml_spec.rb2
-rw-r--r--spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb2
-rw-r--r--spec/views/projects/pages/show.html.haml_spec.rb2
-rw-r--r--spec/views/projects/pages_domains/show.html.haml_spec.rb2
-rw-r--r--spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb2
-rw-r--r--spec/views/projects/pipelines/_stage.html.haml_spec.rb6
-rw-r--r--spec/views/projects/services/_form.haml_spec.rb2
-rw-r--r--spec/views/projects/services/edit.html.haml_spec.rb2
-rw-r--r--spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb2
-rw-r--r--spec/views/projects/settings/operations/show.html.haml_spec.rb17
-rw-r--r--spec/views/projects/show.html.haml_spec.rb41
-rw-r--r--spec/views/projects/tags/index.html.haml_spec.rb2
-rw-r--r--spec/views/projects/tree/_tree_header.html.haml_spec.rb55
-rw-r--r--spec/views/projects/tree/_tree_row.html.haml_spec.rb2
-rw-r--r--spec/views/projects/tree/show.html.haml_spec.rb24
-rw-r--r--spec/views/search/_filter.html.haml_spec.rb2
-rw-r--r--spec/views/search/_form.html.haml_spec.rb2
-rw-r--r--spec/views/search/_results.html.haml_spec.rb2
-rw-r--r--spec/views/search/show.html.haml_spec.rb2
-rw-r--r--spec/views/shared/_label_row.html.haml_spec.rb2
-rw-r--r--spec/views/shared/milestones/_issuable.html.haml_spec.rb2
-rw-r--r--spec/views/shared/milestones/_issuables.html.haml_spec.rb2
-rw-r--r--spec/views/shared/milestones/_top.html.haml_spec.rb2
-rw-r--r--spec/views/shared/notes/_form.html.haml_spec.rb2
-rw-r--r--spec/views/shared/projects/_list.html.haml_spec.rb2
-rw-r--r--spec/views/shared/projects/_project.html.haml_spec.rb2
-rw-r--r--spec/views/shared/runners/show.html.haml_spec.rb2
-rw-r--r--spec/workers/admin_email_worker_spec.rb2
-rw-r--r--spec/workers/archive_trace_worker_spec.rb2
-rw-r--r--spec/workers/authorized_keys_worker_spec.rb2
-rw-r--r--spec/workers/authorized_project_update/periodic_recalculate_worker_spec.rb2
-rw-r--r--spec/workers/authorized_project_update/project_create_worker_spec.rb4
-rw-r--r--spec/workers/authorized_project_update/project_group_link_create_worker_spec.rb52
-rw-r--r--spec/workers/authorized_project_update/user_refresh_over_user_range_worker_spec.rb2
-rw-r--r--spec/workers/authorized_project_update/user_refresh_with_low_urgency_worker_spec.rb2
-rw-r--r--spec/workers/authorized_projects_worker_spec.rb2
-rw-r--r--spec/workers/auto_devops/disable_worker_spec.rb2
-rw-r--r--spec/workers/auto_merge_process_worker_spec.rb2
-rw-r--r--spec/workers/background_migration_worker_spec.rb2
-rw-r--r--spec/workers/build_coverage_worker_spec.rb2
-rw-r--r--spec/workers/build_finished_worker_spec.rb2
-rw-r--r--spec/workers/build_hooks_worker_spec.rb2
-rw-r--r--spec/workers/build_success_worker_spec.rb2
-rw-r--r--spec/workers/build_trace_sections_worker_spec.rb2
-rw-r--r--spec/workers/chat_notification_worker_spec.rb2
-rw-r--r--spec/workers/ci/archive_traces_cron_worker_spec.rb2
-rw-r--r--spec/workers/ci/build_prepare_worker_spec.rb2
-rw-r--r--spec/workers/ci/build_report_result_worker_spec.rb2
-rw-r--r--spec/workers/ci/build_schedule_worker_spec.rb2
-rw-r--r--spec/workers/ci/create_cross_project_pipeline_worker_spec.rb2
-rw-r--r--spec/workers/ci/daily_build_group_report_results_worker_spec.rb2
-rw-r--r--spec/workers/ci/pipeline_bridge_status_worker_spec.rb2
-rw-r--r--spec/workers/ci/pipeline_success_unlock_artifacts_worker_spec.rb67
-rw-r--r--spec/workers/ci/ref_delete_unlock_artifacts_worker_spec.rb78
-rw-r--r--spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb2
-rw-r--r--spec/workers/cleanup_container_repository_worker_spec.rb2
-rw-r--r--spec/workers/cluster_configure_istio_worker_spec.rb2
-rw-r--r--spec/workers/cluster_provision_worker_spec.rb2
-rw-r--r--spec/workers/cluster_update_app_worker_spec.rb20
-rw-r--r--spec/workers/cluster_wait_for_app_update_worker_spec.rb2
-rw-r--r--spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb2
-rw-r--r--spec/workers/clusters/applications/activate_service_worker_spec.rb2
-rw-r--r--spec/workers/clusters/applications/check_prometheus_health_worker_spec.rb2
-rw-r--r--spec/workers/clusters/applications/deactivate_service_worker_spec.rb2
-rw-r--r--spec/workers/clusters/applications/wait_for_uninstall_app_worker_spec.rb2
-rw-r--r--spec/workers/clusters/cleanup/app_worker_spec.rb2
-rw-r--r--spec/workers/clusters/cleanup/project_namespace_worker_spec.rb2
-rw-r--r--spec/workers/clusters/cleanup/service_account_worker_spec.rb2
-rw-r--r--spec/workers/concerns/application_worker_spec.rb2
-rw-r--r--spec/workers/concerns/cluster_queue_spec.rb2
-rw-r--r--spec/workers/concerns/cronjob_queue_spec.rb2
-rw-r--r--spec/workers/concerns/gitlab/github_import/object_importer_spec.rb2
-rw-r--r--spec/workers/concerns/gitlab/github_import/queue_spec.rb2
-rw-r--r--spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb2
-rw-r--r--spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb2
-rw-r--r--spec/workers/concerns/gitlab/notify_upon_death_spec.rb2
-rw-r--r--spec/workers/concerns/pipeline_background_queue_spec.rb2
-rw-r--r--spec/workers/concerns/pipeline_queue_spec.rb2
-rw-r--r--spec/workers/concerns/project_export_options_spec.rb41
-rw-r--r--spec/workers/concerns/project_import_options_spec.rb2
-rw-r--r--spec/workers/concerns/reenqueuer_spec.rb4
-rw-r--r--spec/workers/concerns/repository_check_queue_spec.rb2
-rw-r--r--spec/workers/concerns/waitable_worker_spec.rb2
-rw-r--r--spec/workers/concerns/worker_context_spec.rb2
-rw-r--r--spec/workers/container_expiration_policy_worker_spec.rb2
-rw-r--r--spec/workers/create_commit_signature_worker_spec.rb2
-rw-r--r--spec/workers/create_evidence_worker_spec.rb2
-rw-r--r--spec/workers/create_note_diff_file_worker_spec.rb2
-rw-r--r--spec/workers/create_pipeline_worker_spec.rb2
-rw-r--r--spec/workers/delete_container_repository_worker_spec.rb2
-rw-r--r--spec/workers/delete_diff_files_worker_spec.rb2
-rw-r--r--spec/workers/delete_merged_branches_worker_spec.rb2
-rw-r--r--spec/workers/delete_user_worker_spec.rb2
-rw-r--r--spec/workers/deployments/finished_worker_spec.rb2
-rw-r--r--spec/workers/deployments/success_worker_spec.rb2
-rw-r--r--spec/workers/design_management/new_version_worker_spec.rb2
-rw-r--r--spec/workers/detect_repository_languages_worker_spec.rb2
-rw-r--r--spec/workers/email_receiver_worker_spec.rb2
-rw-r--r--spec/workers/emails_on_push_worker_spec.rb2
-rw-r--r--spec/workers/environments/auto_stop_cron_worker_spec.rb2
-rw-r--r--spec/workers/error_tracking_issue_link_worker_spec.rb2
-rw-r--r--spec/workers/every_sidekiq_worker_spec.rb8
-rw-r--r--spec/workers/expire_build_artifacts_worker_spec.rb2
-rw-r--r--spec/workers/expire_build_instance_artifacts_worker_spec.rb4
-rw-r--r--spec/workers/expire_job_cache_worker_spec.rb2
-rw-r--r--spec/workers/expire_pipeline_cache_worker_spec.rb2
-rw-r--r--spec/workers/export_csv_worker_spec.rb2
-rw-r--r--spec/workers/external_service_reactive_caching_worker_spec.rb2
-rw-r--r--spec/workers/file_hook_worker_spec.rb2
-rw-r--r--spec/workers/git_garbage_collect_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/advance_stage_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_issue_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_note_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/import/stuck_project_import_jobs_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/jira_import/import_issue_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/jira_import/stage/finish_import_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/jira_import/stage/import_attachments_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/jira_import/stage/import_issues_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/jira_import/stage/import_labels_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/jira_import/stage/import_notes_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/jira_import/stage/start_import_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/jira_import/stuck_jira_import_jobs_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/phabricator_import/base_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/phabricator_import/import_tasks_worker_spec.rb2
-rw-r--r--spec/workers/gitlab_shell_worker_spec.rb2
-rw-r--r--spec/workers/gitlab_usage_ping_worker_spec.rb2
-rw-r--r--spec/workers/group_destroy_worker_spec.rb2
-rw-r--r--spec/workers/group_export_worker_spec.rb2
-rw-r--r--spec/workers/group_import_worker_spec.rb2
-rw-r--r--spec/workers/hashed_storage/migrator_worker_spec.rb2
-rw-r--r--spec/workers/hashed_storage/project_migrate_worker_spec.rb2
-rw-r--r--spec/workers/hashed_storage/project_rollback_worker_spec.rb2
-rw-r--r--spec/workers/hashed_storage/rollbacker_worker_spec.rb2
-rw-r--r--spec/workers/import_issues_csv_worker_spec.rb2
-rw-r--r--spec/workers/incident_management/pager_duty/process_incident_worker_spec.rb57
-rw-r--r--spec/workers/incident_management/process_alert_worker_spec.rb66
-rw-r--r--spec/workers/incident_management/process_prometheus_alert_worker_spec.rb134
-rw-r--r--spec/workers/invalid_gpg_signature_update_worker_spec.rb2
-rw-r--r--spec/workers/irker_worker_spec.rb2
-rw-r--r--spec/workers/issue_due_scheduler_worker_spec.rb2
-rw-r--r--spec/workers/mail_scheduler/issue_due_worker_spec.rb2
-rw-r--r--spec/workers/mail_scheduler/notification_service_worker_spec.rb2
-rw-r--r--spec/workers/members_destroyer/unassign_issuables_worker_spec.rb27
-rw-r--r--spec/workers/merge_request_mergeability_check_worker_spec.rb2
-rw-r--r--spec/workers/merge_worker_spec.rb2
-rw-r--r--spec/workers/metrics/dashboard/prune_old_annotations_worker_spec.rb2
-rw-r--r--spec/workers/metrics/dashboard/schedule_annotations_prune_worker_spec.rb2
-rw-r--r--spec/workers/migrate_external_diffs_worker_spec.rb2
-rw-r--r--spec/workers/namespaceless_project_destroy_worker_spec.rb2
-rw-r--r--spec/workers/namespaces/prune_aggregation_schedules_worker_spec.rb2
-rw-r--r--spec/workers/namespaces/root_statistics_worker_spec.rb2
-rw-r--r--spec/workers/namespaces/schedule_aggregation_worker_spec.rb2
-rw-r--r--spec/workers/new_issue_worker_spec.rb2
-rw-r--r--spec/workers/new_merge_request_worker_spec.rb2
-rw-r--r--spec/workers/new_note_worker_spec.rb2
-rw-r--r--spec/workers/new_release_worker_spec.rb17
-rw-r--r--spec/workers/object_pool/create_worker_spec.rb2
-rw-r--r--spec/workers/object_pool/destroy_worker_spec.rb2
-rw-r--r--spec/workers/object_pool/join_worker_spec.rb2
-rw-r--r--spec/workers/packages/nuget/extraction_worker_spec.rb94
-rw-r--r--spec/workers/pages_domain_removal_cron_worker_spec.rb2
-rw-r--r--spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb2
-rw-r--r--spec/workers/pages_domain_ssl_renewal_worker_spec.rb2
-rw-r--r--spec/workers/pages_domain_verification_cron_worker_spec.rb2
-rw-r--r--spec/workers/pages_domain_verification_worker_spec.rb2
-rw-r--r--spec/workers/partition_creation_worker_spec.rb19
-rw-r--r--spec/workers/pipeline_hooks_worker_spec.rb2
-rw-r--r--spec/workers/pipeline_metrics_worker_spec.rb4
-rw-r--r--spec/workers/pipeline_notification_worker_spec.rb2
-rw-r--r--spec/workers/pipeline_process_worker_spec.rb2
-rw-r--r--spec/workers/pipeline_schedule_worker_spec.rb4
-rw-r--r--spec/workers/pipeline_update_ci_ref_status_worker_service_spec.rb2
-rw-r--r--spec/workers/pipeline_update_worker_spec.rb10
-rw-r--r--spec/workers/post_receive_spec.rb9
-rw-r--r--spec/workers/process_commit_worker_spec.rb6
-rw-r--r--spec/workers/project_cache_worker_spec.rb2
-rw-r--r--spec/workers/project_daily_statistics_worker_spec.rb2
-rw-r--r--spec/workers/project_destroy_worker_spec.rb2
-rw-r--r--spec/workers/project_export_worker_spec.rb12
-rw-r--r--spec/workers/project_service_worker_spec.rb2
-rw-r--r--spec/workers/project_update_repository_storage_worker_spec.rb3
-rw-r--r--spec/workers/prometheus/create_default_alerts_worker_spec.rb2
-rw-r--r--spec/workers/propagate_integration_worker_spec.rb2
-rw-r--r--spec/workers/propagate_service_template_worker_spec.rb2
-rw-r--r--spec/workers/prune_old_events_worker_spec.rb2
-rw-r--r--spec/workers/prune_web_hook_logs_worker_spec.rb2
-rw-r--r--spec/workers/reactive_caching_worker_spec.rb2
-rw-r--r--spec/workers/rebase_worker_spec.rb2
-rw-r--r--spec/workers/remote_mirror_notification_worker_spec.rb2
-rw-r--r--spec/workers/remove_expired_group_links_worker_spec.rb2
-rw-r--r--spec/workers/remove_expired_members_worker_spec.rb2
-rw-r--r--spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb2
-rw-r--r--spec/workers/repository_check/batch_worker_spec.rb2
-rw-r--r--spec/workers/repository_check/clear_worker_spec.rb4
-rw-r--r--spec/workers/repository_check/dispatch_worker_spec.rb2
-rw-r--r--spec/workers/repository_check/single_repository_worker_spec.rb2
-rw-r--r--spec/workers/repository_cleanup_worker_spec.rb2
-rw-r--r--spec/workers/repository_fork_worker_spec.rb2
-rw-r--r--spec/workers/repository_import_worker_spec.rb8
-rw-r--r--spec/workers/repository_remove_remote_worker_spec.rb4
-rw-r--r--spec/workers/repository_update_remote_mirror_worker_spec.rb10
-rw-r--r--spec/workers/run_pipeline_schedule_worker_spec.rb2
-rw-r--r--spec/workers/schedule_migrate_external_diffs_worker_spec.rb2
-rw-r--r--spec/workers/self_monitoring_project_create_worker_spec.rb2
-rw-r--r--spec/workers/self_monitoring_project_delete_worker_spec.rb2
-rw-r--r--spec/workers/service_desk_email_receiver_worker_spec.rb53
-rw-r--r--spec/workers/stage_update_worker_spec.rb2
-rw-r--r--spec/workers/stuck_ci_jobs_worker_spec.rb2
-rw-r--r--spec/workers/stuck_export_jobs_worker_spec.rb2
-rw-r--r--spec/workers/stuck_import_jobs_worker_spec.rb27
-rw-r--r--spec/workers/stuck_merge_jobs_worker_spec.rb2
-rw-r--r--spec/workers/system_hook_push_worker_spec.rb2
-rw-r--r--spec/workers/todos_destroyer/confidential_issue_worker_spec.rb2
-rw-r--r--spec/workers/todos_destroyer/entity_leave_worker_spec.rb2
-rw-r--r--spec/workers/todos_destroyer/group_private_worker_spec.rb2
-rw-r--r--spec/workers/todos_destroyer/private_features_worker_spec.rb2
-rw-r--r--spec/workers/todos_destroyer/project_private_worker_spec.rb2
-rw-r--r--spec/workers/trending_projects_worker_spec.rb2
-rw-r--r--spec/workers/update_container_registry_info_worker_spec.rb15
-rw-r--r--spec/workers/update_external_pull_requests_worker_spec.rb2
-rw-r--r--spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb2
-rw-r--r--spec/workers/update_highest_role_worker_spec.rb2
-rw-r--r--spec/workers/update_merge_requests_worker_spec.rb2
-rw-r--r--spec/workers/update_project_statistics_worker_spec.rb2
-rw-r--r--spec/workers/upload_checksum_worker_spec.rb2
-rw-r--r--spec/workers/users/create_statistics_worker_spec.rb2
-rw-r--r--spec/workers/wait_for_cluster_creation_worker_spec.rb2
-rw-r--r--spec/workers/x509_certificate_revoke_worker_spec.rb2
-rw-r--r--spec/workers/x509_issuer_crl_check_worker_spec.rb2
4717 files changed, 77155 insertions, 19006 deletions
diff --git a/spec/bin/feature_flag_spec.rb b/spec/bin/feature_flag_spec.rb
new file mode 100644
index 00000000000..3a315a13686
--- /dev/null
+++ b/spec/bin/feature_flag_spec.rb
@@ -0,0 +1,191 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+load File.expand_path('../../bin/feature-flag', __dir__)
+
+RSpec.describe 'bin/feature-flag' do
+ using RSpec::Parameterized::TableSyntax
+
+ describe FeatureFlagCreator do
+ let(:argv) { %w[feature-flag-name -t development -g group::memory -i https://url] }
+ let(:options) { FeatureFlagOptionParser.parse(argv) }
+ let(:creator) { described_class.new(options) }
+ let(:existing_flag) { File.join('config', 'feature_flags', 'development', 'existing-feature-flag.yml') }
+
+ before do
+ # create a dummy feature flag
+ FileUtils.mkdir_p(File.dirname(existing_flag))
+ File.write(existing_flag, '{}')
+
+ # ignore writes
+ allow(File).to receive(:write).and_return(true)
+
+ # ignore stdin
+ allow($stdin).to receive(:gets).and_raise('EOF')
+
+ # ignore Git commands
+ allow(creator).to receive(:branch_name) { 'feature-branch' }
+ end
+
+ after do
+ FileUtils.rm_f(existing_flag)
+ end
+
+ subject { creator.execute }
+
+ it 'properly creates a feature flag' do
+ expect(File).to receive(:write).with(
+ File.join('config', 'feature_flags', 'development', 'feature-flag-name.yml'),
+ anything)
+
+ expect do
+ subject
+ end.to output(/name: feature-flag-name/).to_stdout
+ end
+
+ context 'when running on master' do
+ it 'requires feature branch' do
+ expect(creator).to receive(:branch_name) { 'master' }
+
+ expect { subject }.to raise_error(FeatureFlagHelpers::Abort, /Create a branch first/)
+ end
+ end
+
+ context 'validates feature flag name' do
+ where(:argv, :ex) do
+ %w[.invalid.feature.flag] | /Provide a name for the feature flag that is/
+ %w[existing-feature-flag] | /already exists!/
+ end
+
+ with_them do
+ it do
+ expect { subject }.to raise_error(ex)
+ end
+ end
+ end
+ end
+
+ describe FeatureFlagOptionParser do
+ describe '.parse' do
+ where(:param, :argv, :result) do
+ :name | %w[foo] | 'foo'
+ :amend | %w[foo --amend] | true
+ :force | %w[foo -f] | true
+ :force | %w[foo --force] | true
+ :ee | %w[foo -e] | true
+ :ee | %w[foo --ee] | true
+ :introduced_by_url | %w[foo -m https://url] | 'https://url'
+ :introduced_by_url | %w[foo --introduced-by-url https://url] | 'https://url'
+ :rollout_issue_url | %w[foo -i https://url] | 'https://url'
+ :rollout_issue_url | %w[foo --rollout-issue-url https://url] | 'https://url'
+ :dry_run | %w[foo -n] | true
+ :dry_run | %w[foo --dry-run] | true
+ :type | %w[foo -t development] | :development
+ :type | %w[foo --type development] | :development
+ :type | %w[foo -t invalid] | nil
+ :type | %w[foo --type invalid] | nil
+ :group | %w[foo -g group::memory] | 'group::memory'
+ :group | %w[foo --group group::memory] | 'group::memory'
+ :group | %w[foo -g invalid] | nil
+ :group | %w[foo --group invalid] | nil
+ end
+
+ with_them do
+ it do
+ options = described_class.parse(Array(argv))
+
+ expect(options.public_send(param)).to eq(result)
+ end
+ end
+
+ it 'missing feature flag name' do
+ expect do
+ expect { described_class.parse(%w[--amend]) }.to output(/Feature flag name is required/).to_stdout
+ end.to raise_error(FeatureFlagHelpers::Abort)
+ end
+
+ it 'parses -h' do
+ expect do
+ expect { described_class.parse(%w[foo -h]) }.to output(/Usage:/).to_stdout
+ end.to raise_error(FeatureFlagHelpers::Done)
+ end
+ end
+
+ describe '.read_type' do
+ let(:type) { 'development' }
+
+ it 'reads type from $stdin' do
+ expect($stdin).to receive(:gets).and_return(type)
+ expect do
+ expect(described_class.read_type).to eq(:development)
+ end.to output(/specify the type/).to_stdout
+ end
+
+ context 'invalid type given' do
+ let(:type) { 'invalid' }
+
+ it 'shows error message and retries' do
+ expect($stdin).to receive(:gets).and_return(type)
+ expect($stdin).to receive(:gets).and_raise('EOF')
+
+ expect do
+ expect { described_class.read_type }.to raise_error(/EOF/)
+ end.to output(/specify the type/).to_stdout
+ .and output(/Invalid type specified/).to_stderr
+ end
+ end
+ end
+
+ describe '.read_group' do
+ let(:group) { 'group::memory' }
+
+ it 'reads type from $stdin' do
+ expect($stdin).to receive(:gets).and_return(group)
+ expect do
+ expect(described_class.read_group).to eq('group::memory')
+ end.to output(/specify the group/).to_stdout
+ end
+
+ context 'invalid group given' do
+ let(:type) { 'invalid' }
+
+ it 'shows error message and retries' do
+ expect($stdin).to receive(:gets).and_return(type)
+ expect($stdin).to receive(:gets).and_raise('EOF')
+
+ expect do
+ expect { described_class.read_group }.to raise_error(/EOF/)
+ end.to output(/specify the group/).to_stdout
+ .and output(/Group needs to include/).to_stderr
+ end
+ end
+ end
+
+ describe '.rollout_issue_url' do
+ let(:options) { OpenStruct.new(name: 'foo', type: :development) }
+ let(:url) { 'https://issue' }
+
+ it 'reads type from $stdin' do
+ expect($stdin).to receive(:gets).and_return(url)
+ expect do
+ expect(described_class.read_issue_url(options)).to eq('https://issue')
+ end.to output(/Paste URL here/).to_stdout
+ end
+
+ context 'invalid URL given' do
+ let(:type) { 'invalid' }
+
+ it 'shows error message and retries' do
+ expect($stdin).to receive(:gets).and_return(type)
+ expect($stdin).to receive(:gets).and_raise('EOF')
+
+ expect do
+ expect { described_class.read_issue_url(options) }.to raise_error(/EOF/)
+ end.to output(/Paste URL here/).to_stdout
+ .and output(/URL needs to start/).to_stderr
+ end
+ end
+ end
+ end
+end
diff --git a/spec/config/object_store_settings_spec.rb b/spec/config/object_store_settings_spec.rb
index 67e77aa4466..4a800261625 100644
--- a/spec/config/object_store_settings_spec.rb
+++ b/spec/config/object_store_settings_spec.rb
@@ -4,9 +4,105 @@ require 'spec_helper'
require Rails.root.join('config', 'object_store_settings.rb')
RSpec.describe ObjectStoreSettings do
- describe '.parse' do
+ describe '#parse!' do
+ let(:settings) { Settingslogic.new(config) }
+
+ subject { described_class.new(settings).parse! }
+
+ context 'with valid config' do
+ let(:connection) do
+ {
+ 'provider' => 'AWS',
+ 'aws_access_key_id' => 'AWS_ACCESS_KEY_ID',
+ 'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY',
+ 'region' => 'us-east-1'
+ }
+ end
+ let(:config) do
+ {
+ 'lfs' => { 'enabled' => true },
+ 'artifacts' => { 'enabled' => true },
+ 'external_diffs' => { 'enabled' => false },
+ 'object_store' => {
+ 'enabled' => true,
+ 'connection' => connection,
+ 'proxy_download' => true,
+ 'objects' => {
+ 'artifacts' => {
+ 'bucket' => 'artifacts',
+ 'proxy_download' => false
+ },
+ 'lfs' => {
+ 'bucket' => 'lfs-objects'
+ },
+ 'external_diffs' => {
+ 'bucket' => 'external_diffs',
+ 'enabled' => false
+ }
+ }
+ }
+ }
+ end
+
+ it 'sets correct default values' do
+ subject
+
+ expect(settings.artifacts['enabled']).to be true
+ expect(settings.artifacts['object_store']['enabled']).to be true
+ expect(settings.artifacts['object_store']['connection']).to eq(connection)
+ expect(settings.artifacts['object_store']['direct_upload']).to be true
+ expect(settings.artifacts['object_store']['background_upload']).to be false
+ expect(settings.artifacts['object_store']['proxy_download']).to be false
+ expect(settings.artifacts['object_store']['remote_directory']).to eq('artifacts')
+
+ expect(settings.lfs['enabled']).to be true
+ expect(settings.lfs['object_store']['enabled']).to be true
+ expect(settings.lfs['object_store']['connection']).to eq(connection)
+ expect(settings.lfs['object_store']['direct_upload']).to be true
+ expect(settings.lfs['object_store']['background_upload']).to be false
+ expect(settings.lfs['object_store']['proxy_download']).to be true
+ expect(settings.lfs['object_store']['remote_directory']).to eq('lfs-objects')
+
+ expect(settings.external_diffs['enabled']).to be false
+ expect(settings.external_diffs['object_store']['enabled']).to be false
+ expect(settings.external_diffs['object_store']['remote_directory']).to eq('external_diffs')
+ end
+
+ it 'raises an error when a bucket is missing' do
+ config['object_store']['objects']['lfs'].delete('bucket')
+
+ expect { subject }.to raise_error(/Object storage for lfs must have a bucket specified/)
+ end
+
+ context 'with legacy config' do
+ let(:legacy_settings) do
+ {
+ 'enabled' => true,
+ 'remote_directory' => 'some-bucket',
+ 'direct_upload' => true,
+ 'background_upload' => false,
+ 'proxy_download' => false
+ }
+ end
+
+ before do
+ settings.lfs['object_store'] = described_class.legacy_parse(legacy_settings)
+ end
+
+ it 'does not alter config if legacy settings are specified' do
+ subject
+
+ expect(settings.artifacts['object_store']).to be_nil
+ expect(settings.lfs['object_store']['remote_directory']).to eq('some-bucket')
+ expect(settings.external_diffs['object_store']).to be_nil
+ end
+ end
+ end
+ end
+
+ describe '.legacy_parse' do
it 'sets correct default values' do
- settings = described_class.parse(nil)
+ settings = described_class.legacy_parse(nil)
expect(settings['enabled']).to be false
expect(settings['direct_upload']).to be false
@@ -20,7 +116,7 @@ RSpec.describe ObjectStoreSettings do
'remote_directory' => 'artifacts'
})
- settings = described_class.parse(original_settings)
+ settings = described_class.legacy_parse(original_settings)
expect(settings['enabled']).to be true
expect(settings['direct_upload']).to be false
diff --git a/spec/config/settings_spec.rb b/spec/config/settings_spec.rb
index 9db3d35cbe5..ed873478fc9 100644
--- a/spec/config/settings_spec.rb
+++ b/spec/config/settings_spec.rb
@@ -112,4 +112,26 @@ RSpec.describe Settings do
end
end
end
+
+ describe '.cron_for_usage_ping' do
+ it 'returns correct crontab for some manually calculated example' do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:uuid) { 'd9e2f4e8-db1f-4e51-b03d-f427e1965c4a'}
+
+ expect(described_class.send(:cron_for_usage_ping)).to eq('21 18 * * 4')
+ end
+
+ it 'returns min, hour, day in the valid range' do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:uuid) { SecureRandom.uuid }
+
+ 10.times do
+ cron = described_class.send(:cron_for_usage_ping).split(/\s/)
+
+ expect(cron[0].to_i).to be_between(0, 59)
+ expect(cron[1].to_i).to be_between(0, 23)
+ expect(cron[4].to_i).to be_between(0, 6)
+ end
+ end
+ end
end
diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb
index 8ab29a72477..7b8528009d8 100644
--- a/spec/controllers/admin/application_settings_controller_spec.rb
+++ b/spec/controllers/admin/application_settings_controller_spec.rb
@@ -128,6 +128,13 @@ RSpec.describe Admin::ApplicationSettingsController do
expect(ApplicationSetting.current.repository_storages_weighted_default).to eq(75)
end
+ it "updates default_branch_name setting" do
+ put :update, params: { application_setting: { default_branch_name: "example_branch_name" } }
+
+ expect(response).to redirect_to(general_admin_application_settings_path)
+ expect(ApplicationSetting.current.default_branch_name).to eq("example_branch_name")
+ end
+
context 'external policy classification settings' do
let(:settings) do
{
diff --git a/spec/controllers/admin/clusters_controller_spec.rb b/spec/controllers/admin/clusters_controller_spec.rb
index d899e86ae5f..2e0ee671d3f 100644
--- a/spec/controllers/admin/clusters_controller_spec.rb
+++ b/spec/controllers/admin/clusters_controller_spec.rb
@@ -171,6 +171,39 @@ RSpec.describe Admin::ClustersController do
end
end
+ it_behaves_like 'GET #metrics_dashboard for dashboard', 'Cluster health' do
+ let(:cluster) { create(:cluster, :instance, :provided_by_gcp) }
+
+ let(:metrics_dashboard_req_params) do
+ {
+ id: cluster.id
+ }
+ end
+ end
+
+ describe 'GET #prometheus_proxy' do
+ let(:user) { admin }
+ let(:proxyable) do
+ create(:cluster, :instance, :provided_by_gcp)
+ end
+
+ it_behaves_like 'metrics dashboard prometheus api proxy' do
+ context 'with anonymous user' do
+ let(:prometheus_body) { nil }
+
+ before do
+ sign_out(admin)
+ end
+
+ it 'returns 404' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+ end
+
describe 'POST #create_gcp' do
let(:legacy_abac_param) { 'true' }
let(:params) do
diff --git a/spec/controllers/admin/jobs_controller_spec.rb b/spec/controllers/admin/jobs_controller_spec.rb
new file mode 100644
index 00000000000..2d1482f40d4
--- /dev/null
+++ b/spec/controllers/admin/jobs_controller_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Admin::JobsController do
+ describe 'GET #index' do
+ context 'with an authenticated admin user' do
+ it 'paginates builds without a total count', :aggregate_failures do
+ stub_const("Admin::JobsController::BUILDS_PER_PAGE", 1)
+
+ sign_in(create(:admin))
+ create_list(:ci_build, 2)
+
+ get :index
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(assigns(:builds)).to be_a(Kaminari::PaginatableWithoutCount)
+ expect(assigns(:builds).count).to be(1)
+ end
+ end
+
+ context 'without admin access' do
+ it 'returns `not_found`' do
+ sign_in(create(:user))
+
+ get :index
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/admin/services_controller_spec.rb b/spec/controllers/admin/services_controller_spec.rb
index 2ad4989af4f..8e78cc75369 100644
--- a/spec/controllers/admin/services_controller_spec.rb
+++ b/spec/controllers/admin/services_controller_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe Admin::ServicesController do
end
describe 'GET #edit' do
- let!(:service) do
+ let(:service) do
create(:jira_service, :template)
end
@@ -19,6 +19,26 @@ RSpec.describe Admin::ServicesController do
expect(response).to have_gitlab_http_status(:ok)
end
+
+ context 'when integration does not exists' do
+ it 'redirects to the admin application integration page' do
+ get :edit, params: { id: 'invalid' }
+
+ expect(response).to redirect_to(admin_application_settings_services_path)
+ end
+ end
+
+ context 'when instance integration exists' do
+ before do
+ create(:jira_service, :instance)
+ end
+
+ it 'redirects to the admin application integration page' do
+ get :edit, params: { id: service.id }
+
+ expect(response).to redirect_to(admin_application_settings_services_path)
+ end
+ end
end
describe "#update" do
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 4002b7aca63..aec629ba330 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -968,4 +968,26 @@ RSpec.describe ApplicationController do
end
end
end
+
+ describe 'locale' do
+ let(:user) { create(:user, preferred_language: 'uk') }
+
+ controller(described_class) do
+ def index
+ :ok
+ end
+ end
+
+ before do
+ sign_in(user)
+
+ allow(Gitlab::I18n).to receive(:with_locale).and_call_original
+ end
+
+ it "sets user's locale" do
+ expect(Gitlab::I18n).to receive(:with_locale).with('uk')
+
+ get :index
+ end
+ end
end
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index aeb3f4dcb17..e7c0bc43e86 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -365,6 +365,56 @@ RSpec.describe AutocompleteController do
end
end
+ context 'GET deploy_keys_with_owners' do
+ let!(:deploy_key) { create(:deploy_key, user: user) }
+ let!(:deploy_keys_project) { create(:deploy_keys_project, :write_access, project: project, deploy_key: deploy_key) }
+
+ context 'unauthorized user' do
+ it 'returns a not found response' do
+ get(:deploy_keys_with_owners, params: { project_id: project.id })
+
+ expect(response).to have_gitlab_http_status(:redirect)
+ end
+ end
+
+ context 'when the user who can read the project is logged in' do
+ before do
+ sign_in(user)
+ end
+
+ it 'renders the deploy key in a json payload, with its owner' do
+ get(:deploy_keys_with_owners, params: { project_id: project.id })
+
+ expect(json_response.count).to eq(1)
+ expect(json_response.first['title']).to eq(deploy_key.title)
+ expect(json_response.first['owner']['id']).to eq(deploy_key.user.id)
+ end
+
+ context 'with an unknown project' do
+ it 'returns a not found response' do
+ get(:deploy_keys_with_owners, params: { project_id: 9999 })
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'and the user cannot read the owner of the key' do
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?).with(user, :read_user, deploy_key.user).and_return(false)
+ end
+
+ it 'returns a payload without owner' do
+ get(:deploy_keys_with_owners, params: { project_id: project.id })
+
+ expect(json_response.count).to eq(1)
+ expect(json_response.first['title']).to eq(deploy_key.title)
+ expect(json_response.first['owner']).to be_nil
+ end
+ end
+ end
+ end
+
context 'Get merge_request_target_branches' do
let!(:merge_request) { create(:merge_request, source_project: project, target_branch: 'feature') }
diff --git a/spec/controllers/concerns/controller_with_feature_category/config_spec.rb b/spec/controllers/concerns/controller_with_feature_category/config_spec.rb
new file mode 100644
index 00000000000..9b8ffd2baab
--- /dev/null
+++ b/spec/controllers/concerns/controller_with_feature_category/config_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require "fast_spec_helper"
+require "rspec-parameterized"
+require_relative "../../../../app/controllers/concerns/controller_with_feature_category/config"
+
+RSpec.describe ControllerWithFeatureCategory::Config do
+ describe "#matches?" do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:only_actions, :except_actions, :if_proc, :unless_proc, :test_action, :expected) do
+ nil | nil | nil | nil | "action" | true
+ [:included] | nil | nil | nil | "action" | false
+ [:included] | nil | nil | nil | "included" | true
+ nil | [:excluded] | nil | nil | "excluded" | false
+ nil | nil | true | nil | "action" | true
+ [:included] | nil | true | nil | "action" | false
+ [:included] | nil | true | nil | "included" | true
+ nil | [:excluded] | true | nil | "excluded" | false
+ nil | nil | false | nil | "action" | false
+ [:included] | nil | false | nil | "action" | false
+ [:included] | nil | false | nil | "included" | false
+ nil | [:excluded] | false | nil | "excluded" | false
+ nil | nil | nil | true | "action" | false
+ [:included] | nil | nil | true | "action" | false
+ [:included] | nil | nil | true | "included" | false
+ nil | [:excluded] | nil | true | "excluded" | false
+ nil | nil | nil | false | "action" | true
+ [:included] | nil | nil | false | "action" | false
+ [:included] | nil | nil | false | "included" | true
+ nil | [:excluded] | nil | false | "excluded" | false
+ nil | nil | true | false | "action" | true
+ [:included] | nil | true | false | "action" | false
+ [:included] | nil | true | false | "included" | true
+ nil | [:excluded] | true | false | "excluded" | false
+ nil | nil | false | true | "action" | false
+ [:included] | nil | false | true | "action" | false
+ [:included] | nil | false | true | "included" | false
+ nil | [:excluded] | false | true | "excluded" | false
+ end
+
+ with_them do
+ let(:config) do
+ if_to_proc = if_proc.nil? ? nil : -> (_) { if_proc }
+ unless_to_proc = unless_proc.nil? ? nil : -> (_) { unless_proc }
+
+ described_class.new(:category, only_actions, except_actions, if_to_proc, unless_to_proc)
+ end
+
+ specify { expect(config.matches?(test_action)).to be(expected) }
+ end
+ end
+end
diff --git a/spec/controllers/concerns/controller_with_feature_category_spec.rb b/spec/controllers/concerns/controller_with_feature_category_spec.rb
new file mode 100644
index 00000000000..e603a7d14c4
--- /dev/null
+++ b/spec/controllers/concerns/controller_with_feature_category_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require_relative "../../../app/controllers/concerns/controller_with_feature_category"
+require_relative "../../../app/controllers/concerns/controller_with_feature_category/config"
+
+RSpec.describe ControllerWithFeatureCategory do
+ describe ".feature_category_for_action" do
+ let(:base_controller) do
+ Class.new do
+ include ControllerWithFeatureCategory
+ end
+ end
+
+ let(:controller) do
+ Class.new(base_controller) do
+ feature_category :baz
+ feature_category :foo, except: %w(update edit)
+ feature_category :bar, only: %w(index show)
+ feature_category :quux, only: %w(destroy)
+ feature_category :quuz, only: %w(destroy)
+ end
+ end
+
+ let(:subclass) do
+ Class.new(controller) do
+ feature_category :qux, only: %w(index)
+ end
+ end
+
+ it "is nil when nothing was defined" do
+ expect(base_controller.feature_category_for_action("hello")).to be_nil
+ end
+
+ it "returns the expected category", :aggregate_failures do
+ expect(controller.feature_category_for_action("update")).to eq(:baz)
+ expect(controller.feature_category_for_action("hello")).to eq(:foo)
+ expect(controller.feature_category_for_action("index")).to eq(:bar)
+ end
+
+ it "returns the closest match for categories defined in subclasses" do
+ expect(subclass.feature_category_for_action("index")).to eq(:qux)
+ expect(subclass.feature_category_for_action("show")).to eq(:bar)
+ end
+
+ it "returns the last defined feature category when multiple match" do
+ expect(controller.feature_category_for_action("destroy")).to eq(:quuz)
+ end
+
+ it "raises an error when using including and excluding the same action" do
+ expect do
+ Class.new(base_controller) do
+ feature_category :hello, only: [:world], except: [:world]
+ end
+ end.to raise_error(%r(cannot configure both `only` and `except`))
+ end
+
+ it "raises an error when using unknown arguments" do
+ expect do
+ Class.new(base_controller) do
+ feature_category :hello, hello: :world
+ end
+ end.to raise_error(%r(unknown arguments))
+ end
+ end
+end
diff --git a/spec/controllers/concerns/metrics_dashboard_spec.rb b/spec/controllers/concerns/metrics_dashboard_spec.rb
index 39ddf687dca..f0c9874965e 100644
--- a/spec/controllers/concerns/metrics_dashboard_spec.rb
+++ b/spec/controllers/concerns/metrics_dashboard_spec.rb
@@ -76,6 +76,22 @@ RSpec.describe MetricsDashboard do
end
end
+ context 'when dashboard path includes encoded characters' do
+ let(:params) { { dashboard_path: 'dashboard%26copy.yml' } }
+
+ before do
+ allow(controller)
+ .to receive(:metrics_dashboard_params)
+ .and_return(params)
+ end
+
+ it 'decodes dashboard path' do
+ expect(::Gitlab::Metrics::Dashboard::Finder).to receive(:find).with(anything, anything, hash_including(dashboard_path: 'dashboard&copy.yml'))
+
+ json_response
+ end
+ end
+
context 'when parameters are provided and the list of all dashboards is required' do
before do
allow(controller).to receive(:include_all_dashboards?).and_return(true)
@@ -88,13 +104,28 @@ RSpec.describe MetricsDashboard do
context 'in all_dashboard list' do
let(:system_dashboard) { json_response['all_dashboards'].find { |dashboard| dashboard["system_dashboard"] == true } }
- let(:project_dashboard) { json_response['all_dashboards'].find { |dashboard| dashboard["system_dashboard"] == false } }
+
+ let(:project_dashboard) do
+ json_response['all_dashboards'].find do |dashboard|
+ dashboard['path'] == '.gitlab/dashboards/test.yml'
+ end
+ end
it 'includes project_blob_path only for project dashboards' do
expect(system_dashboard['project_blob_path']).to be_nil
expect(project_dashboard['project_blob_path']).to eq("/#{project.namespace.path}/#{project.name}/-/blob/master/.gitlab/dashboards/test.yml")
end
+ it 'allows editing only for project dashboards' do
+ expect(system_dashboard['can_edit']).to be(false)
+ expect(project_dashboard['can_edit']).to be(true)
+ end
+
+ it 'includes out_of_the_box_dashboard key' do
+ expect(system_dashboard['out_of_the_box_dashboard']).to be(true)
+ expect(project_dashboard['out_of_the_box_dashboard']).to be(false)
+ end
+
describe 'project permissions' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/controllers/concerns/renders_commits_spec.rb b/spec/controllers/concerns/renders_commits_spec.rb
index 0bffb39d608..7be5f75c19d 100644
--- a/spec/controllers/concerns/renders_commits_spec.rb
+++ b/spec/controllers/concerns/renders_commits_spec.rb
@@ -46,7 +46,7 @@ RSpec.describe RendersCommits do
it 'avoids N + 1' do
stub_const("MergeRequestDiff::COMMITS_SAFE_SIZE", 5)
- control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ control_count = ActiveRecord::QueryRecorder.new do
go
end.count
diff --git a/spec/controllers/concerns/sorting_preference_spec.rb b/spec/controllers/concerns/sorting_preference_spec.rb
index 4f9506d4675..c0091e8b694 100644
--- a/spec/controllers/concerns/sorting_preference_spec.rb
+++ b/spec/controllers/concerns/sorting_preference_spec.rb
@@ -75,7 +75,7 @@ RSpec.describe SortingPreference do
it 'sets the cookie with the right values and flags' do
subject
- expect(cookies['issue_sort']).to eq(value: 'popularity', secure: false, httponly: false)
+ expect(cookies['issue_sort']).to eq(expires: nil, value: 'popularity', secure: false, httponly: false)
end
end
@@ -86,7 +86,7 @@ RSpec.describe SortingPreference do
it 'sets the cookie with the right values and flags' do
subject
- expect(cookies['issue_sort']).to eq(value: 'created_asc', secure: false, httponly: false)
+ expect(cookies['issue_sort']).to eq(expires: nil, value: 'created_asc', secure: false, httponly: false)
end
end
end
diff --git a/spec/controllers/dashboard/projects_controller_spec.rb b/spec/controllers/dashboard/projects_controller_spec.rb
index ee043fde0ff..1e1d9519f78 100644
--- a/spec/controllers/dashboard/projects_controller_spec.rb
+++ b/spec/controllers/dashboard/projects_controller_spec.rb
@@ -5,13 +5,14 @@ require 'spec_helper'
RSpec.describe Dashboard::ProjectsController do
include ExternalAuthorizationServiceHelpers
+ let_it_be(:user) { create(:user) }
+
describe '#index' do
context 'user not logged in' do
it_behaves_like 'authenticates sessionless user', :index, :atom
end
context 'user logged in' do
- let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:project2) { create(:project) }
@@ -71,8 +72,6 @@ RSpec.describe Dashboard::ProjectsController do
context 'json requests' do
render_views
- let(:user) { create(:user) }
-
before do
sign_in(user)
end
@@ -114,16 +113,14 @@ RSpec.describe Dashboard::ProjectsController do
end
context 'atom requests' do
- let(:user) { create(:user) }
-
before do
sign_in(user)
end
describe '#index' do
- context 'project pagination' do
- let(:projects) { create_list(:project, 2, creator: user) }
+ let_it_be(:projects) { create_list(:project, 2, creator: user) }
+ context 'project pagination' do
before do
allow(Kaminari.config).to receive(:default_per_page).and_return(1)
@@ -138,6 +135,37 @@ RSpec.describe Dashboard::ProjectsController do
expect(assigns(:events).count).to eq(2)
end
end
+
+ describe 'rendering' do
+ include DesignManagementTestHelpers
+ render_views
+
+ let(:project) { projects.first }
+ let!(:design_event) { create(:design_event, project: project) }
+ let!(:wiki_page_event) { create(:wiki_page_event, project: project) }
+ let!(:issue_event) { create(:closed_issue_event, project: project) }
+ let(:design) { design_event.design }
+ let(:wiki_page) { wiki_page_event.wiki_page }
+ let(:issue) { issue_event.issue }
+
+ before do
+ enable_design_management
+ project.add_developer(user)
+ end
+
+ it 'renders all kinds of event without error', :aggregate_failures do
+ get :index, format: :atom
+
+ expect(assigns(:events)).to include(design_event, wiki_page_event, issue_event)
+ expect(response).to render_template('dashboard/projects/index')
+ expect(response.body).to include(
+ "uploaded design #{design.to_reference}",
+ "created wiki page #{wiki_page.title}",
+ "joined project #{project.full_name}",
+ "closed issue #{issue.to_reference}"
+ )
+ end
+ end
end
end
end
diff --git a/spec/controllers/dashboard/snippets_controller_spec.rb b/spec/controllers/dashboard/snippets_controller_spec.rb
index 3c316d07408..d981f738e70 100644
--- a/spec/controllers/dashboard/snippets_controller_spec.rb
+++ b/spec/controllers/dashboard/snippets_controller_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Dashboard::SnippetsController do
- let(:user) { create(:user) }
+ let_it_be(:user) { create(:user) }
before do
sign_in(user)
@@ -26,5 +26,7 @@ RSpec.describe Dashboard::SnippetsController do
get :index
end
+
+ it_behaves_like 'snippets sort order'
end
end
diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb
index f0aa351bee0..2e3328ae4d2 100644
--- a/spec/controllers/dashboard/todos_controller_spec.rb
+++ b/spec/controllers/dashboard/todos_controller_spec.rb
@@ -42,6 +42,15 @@ RSpec.describe Dashboard::TodosController do
expect(response).to have_gitlab_http_status(:ok)
end
+
+ context 'tracking visits' do
+ let_it_be(:authorized_project) { create(:project, :public) }
+
+ it_behaves_like 'tracking unique visits', :index do
+ let(:request_params) { { project_id: authorized_project.id } }
+ let(:target_id) { 'u_analytics_todos' }
+ end
+ end
end
context "with render_views" do
diff --git a/spec/controllers/dashboard_controller_spec.rb b/spec/controllers/dashboard_controller_spec.rb
index d27817c0a82..c838affa239 100644
--- a/spec/controllers/dashboard_controller_spec.rb
+++ b/spec/controllers/dashboard_controller_spec.rb
@@ -24,15 +24,20 @@ RSpec.describe DashboardController do
end
describe "GET activity as JSON" do
+ include DesignManagementTestHelpers
render_views
let(:user) { create(:user) }
let(:project) { create(:project, :public, issues_access_level: ProjectFeature::PRIVATE) }
+ let(:other_project) { create(:project, :public) }
before do
+ enable_design_management
create(:event, :created, project: project, target: create(:issue))
create(:wiki_page_event, :created, project: project)
create(:wiki_page_event, :updated, project: project)
+ create(:design_event, project: project)
+ create(:design_event, author: user, project: other_project)
sign_in(user)
@@ -42,12 +47,13 @@ RSpec.describe DashboardController do
context 'when user has permission to see the event' do
before do
project.add_developer(user)
+ other_project.add_developer(user)
end
it 'returns count' do
get :activity, params: { format: :json }
- expect(json_response['count']).to eq(3)
+ expect(json_response['count']).to eq(6)
end
end
diff --git a/spec/controllers/every_controller_spec.rb b/spec/controllers/every_controller_spec.rb
new file mode 100644
index 00000000000..4785ee9ed8f
--- /dev/null
+++ b/spec/controllers/every_controller_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe "Every controller" do
+ context "feature categories" do
+ let_it_be(:feature_categories) do
+ YAML.load_file(Rails.root.join('config', 'feature_categories.yml')).map(&:to_sym).to_set
+ end
+
+ let_it_be(:controller_actions) do
+ # This will return tuples of all controller actions defined in the routes
+ # Only for controllers inheriting ApplicationController
+ # Excluding controllers from gems (OAuth, Sidekiq)
+ Rails.application.routes.routes
+ .map { |route| route.required_defaults.presence }
+ .compact
+ .select { |route| route[:controller].present? && route[:action].present? }
+ .map { |route| [constantize_controller(route[:controller]), route[:action]] }
+ .reject { |route| route.first.nil? || !route.first.include?(ControllerWithFeatureCategory) }
+ end
+
+ let_it_be(:routes_without_category) do
+ controller_actions.map do |controller, action|
+ "#{controller}##{action}" unless controller.feature_category_for_action(action)
+ end.compact
+ end
+
+ it "has feature categories" do
+ pending("We'll work on defining categories for all controllers: "\
+ "https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/463")
+
+ expect(routes_without_category).to be_empty, "#{routes_without_category.first(10)} did not have a category"
+ end
+
+ it "completed controllers don't get new routes without categories" do
+ completed_controllers = [Projects::MergeRequestsController].map(&:to_s)
+
+ newly_introduced_missing_category = routes_without_category.select do |route|
+ completed_controllers.any? { |controller| route.start_with?(controller) }
+ end
+
+ expect(newly_introduced_missing_category).to be_empty
+ end
+
+ it "recognizes the feature categories" do
+ routes_unknown_category = controller_actions.map do |controller, action|
+ used_category = controller.feature_category_for_action(action)
+ next unless used_category
+ next if used_category == :not_owned
+
+ ["#{controller}##{action}", used_category] unless feature_categories.include?(used_category)
+ end.compact
+
+ expect(routes_unknown_category).to be_empty, "#{routes_unknown_category.first(10)} had an unknown category"
+ end
+
+ it "doesn't define or exclude categories on removed actions", :aggregate_failures do
+ controller_actions.group_by(&:first).each do |controller, controller_action|
+ existing_actions = controller_action.map(&:last)
+ used_actions = actions_defined_in_feature_category_config(controller)
+ non_existing_used_actions = used_actions - existing_actions
+
+ expect(non_existing_used_actions).to be_empty,
+ "#{controller} used #{non_existing_used_actions} to define feature category, but the route does not exist"
+ end
+ end
+ end
+
+ def constantize_controller(name)
+ "#{name.camelize}Controller".constantize
+ rescue NameError
+ nil # some controllers, like the omniauth ones are dynamic
+ end
+
+ def actions_defined_in_feature_category_config(controller)
+ feature_category_configs = controller.send(:class_attributes)[:feature_category_config]
+ feature_category_configs.map do |config|
+ Array(config.send(:only)) + Array(config.send(:except))
+ end.flatten.uniq.map(&:to_s)
+ end
+end
diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb
index 6765cf0990a..1593e1290c4 100644
--- a/spec/controllers/groups/clusters_controller_spec.rb
+++ b/spec/controllers/groups/clusters_controller_spec.rb
@@ -192,6 +192,46 @@ RSpec.describe Groups::ClustersController do
end
end
+ it_behaves_like 'GET #metrics_dashboard for dashboard', 'Cluster health' do
+ let(:cluster) { create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group]) }
+
+ let(:metrics_dashboard_req_params) do
+ {
+ id: cluster.id,
+ group_id: group.name
+ }
+ end
+ end
+
+ describe 'GET #prometheus_proxy' do
+ let(:proxyable) do
+ create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group])
+ end
+
+ it_behaves_like 'metrics dashboard prometheus api proxy' do
+ let(:proxyable_params) do
+ {
+ id: proxyable.id.to_s,
+ group_id: group.name
+ }
+ end
+
+ context 'with anonymous user' do
+ let(:prometheus_body) { nil }
+
+ before do
+ sign_out(user)
+ end
+
+ it 'returns 404' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+ end
+
describe 'POST create for new cluster' do
let(:legacy_abac_param) { 'true' }
let(:params) do
diff --git a/spec/controllers/groups/imports_controller_spec.rb b/spec/controllers/groups/imports_controller_spec.rb
index eb43a62b75b..7372c2e9575 100644
--- a/spec/controllers/groups/imports_controller_spec.rb
+++ b/spec/controllers/groups/imports_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::ImportsController do
+RSpec.describe Groups::ImportsController do
describe 'GET #show' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group, :private) }
diff --git a/spec/controllers/groups/runners_controller_spec.rb b/spec/controllers/groups/runners_controller_spec.rb
index 376cd569952..91ff0a53ec7 100644
--- a/spec/controllers/groups/runners_controller_spec.rb
+++ b/spec/controllers/groups/runners_controller_spec.rb
@@ -6,6 +6,9 @@ RSpec.describe Groups::RunnersController do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:runner) { create(:ci_runner, :group, groups: [group]) }
+ let(:project) { create(:project, group: group) }
+ let(:runner_project) { create(:ci_runner, :project, projects: [project]) }
+ let(:params_runner_project) { { group_id: group, id: runner_project } }
let(:params) { { group_id: group, id: runner } }
before do
@@ -24,6 +27,13 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:show)
end
+
+ it 'renders show with 200 status code project runner' do
+ get :show, params: { group_id: group, id: runner_project }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:show)
+ end
end
context 'when user is not owner' do
@@ -36,6 +46,12 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found)
end
+
+ it 'renders a 404 project runner' do
+ get :show, params: { group_id: group, id: runner_project }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
end
end
@@ -51,6 +67,13 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:edit)
end
+
+ it 'renders show with 200 status code project runner' do
+ get :edit, params: { group_id: group, id: runner_project }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:edit)
+ end
end
context 'when user is not owner' do
@@ -63,6 +86,12 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found)
end
+
+ it 'renders a 404 project runner' do
+ get :edit, params: { group_id: group, id: runner_project }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
end
end
@@ -82,6 +111,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:found)
expect(runner.reload.description).to eq(new_desc)
end
+
+ it 'updates the project runner, ticks the queue, and redirects project runner' do
+ new_desc = runner_project.description.swapcase
+
+ expect do
+ post :update, params: params_runner_project.merge(runner: { description: new_desc } )
+ end.to change { runner_project.ensure_runner_queue_value }
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(runner_project.reload.description).to eq(new_desc)
+ end
end
context 'when user is not an owner' do
@@ -99,6 +139,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found)
expect(runner.reload.description).to eq(old_desc)
end
+
+ it 'rejects the update and responds 404 project runner' do
+ old_desc = runner_project.description
+
+ expect do
+ post :update, params: params_runner_project.merge(runner: { description: old_desc.swapcase } )
+ end.not_to change { runner_project.ensure_runner_queue_value }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(runner_project.reload.description).to eq(old_desc)
+ end
end
end
@@ -114,6 +165,31 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:found)
expect(Ci::Runner.find_by(id: runner.id)).to be_nil
end
+
+ it 'destroys the project runner and redirects' do
+ delete :destroy, params: params_runner_project
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(Ci::Runner.find_by(id: runner_project.id)).to be_nil
+ end
+ end
+
+ context 'when user is an owner and runner in multiple projects' do
+ let(:project_2) { create(:project, group: group) }
+ let(:runner_project_2) { create(:ci_runner, :project, projects: [project, project_2]) }
+ let(:params_runner_project_2) { { group_id: group, id: runner_project_2 } }
+
+ before do
+ group.add_owner(user)
+ end
+
+ it 'does not destroy the project runner' do
+ delete :destroy, params: params_runner_project_2
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(flash[:alert]).to eq('Runner was not deleted because it is assigned to multiple projects.')
+ expect(Ci::Runner.find_by(id: runner_project_2.id)).to be_present
+ end
end
context 'when user is not an owner' do
@@ -127,6 +203,13 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found)
expect(Ci::Runner.find_by(id: runner.id)).to be_present
end
+
+ it 'responds 404 and does not destroy the project runner' do
+ delete :destroy, params: params_runner_project
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(Ci::Runner.find_by(id: runner_project.id)).to be_present
+ end
end
end
@@ -146,6 +229,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:found)
expect(runner.reload.active).to eq(true)
end
+
+ it 'marks the project runner as active, ticks the queue, and redirects' do
+ runner_project.update(active: false)
+
+ expect do
+ post :resume, params: params_runner_project
+ end.to change { runner_project.ensure_runner_queue_value }
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(runner_project.reload.active).to eq(true)
+ end
end
context 'when user is not an owner' do
@@ -163,6 +257,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found)
expect(runner.reload.active).to eq(false)
end
+
+ it 'responds 404 and does not activate the project runner' do
+ runner_project.update(active: false)
+
+ expect do
+ post :resume, params: params_runner_project
+ end.not_to change { runner_project.ensure_runner_queue_value }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(runner_project.reload.active).to eq(false)
+ end
end
end
@@ -182,6 +287,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:found)
expect(runner.reload.active).to eq(false)
end
+
+ it 'marks the project runner as inactive, ticks the queue, and redirects' do
+ runner_project.update(active: true)
+
+ expect do
+ post :pause, params: params_runner_project
+ end.to change { runner_project.ensure_runner_queue_value }
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(runner_project.reload.active).to eq(false)
+ end
end
context 'when user is not an owner' do
@@ -199,6 +315,17 @@ RSpec.describe Groups::RunnersController do
expect(response).to have_gitlab_http_status(:not_found)
expect(runner.reload.active).to eq(true)
end
+
+ it 'responds 404 and does not update the project runner or queue' do
+ runner_project.update(active: true)
+
+ expect do
+ post :pause, params: params
+ end.not_to change { runner_project.ensure_runner_queue_value }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(runner_project.reload.active).to eq(true)
+ end
end
end
end
diff --git a/spec/controllers/groups/settings/ci_cd_controller_spec.rb b/spec/controllers/groups/settings/ci_cd_controller_spec.rb
index 55c19de4aa1..f11bb66caab 100644
--- a/spec/controllers/groups/settings/ci_cd_controller_spec.rb
+++ b/spec/controllers/groups/settings/ci_cd_controller_spec.rb
@@ -5,8 +5,15 @@ require 'spec_helper'
RSpec.describe Groups::Settings::CiCdController do
include ExternalAuthorizationServiceHelpers
- let(:group) { create(:group) }
- let(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:sub_group) { create(:group, parent: group) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, group: group) }
+ let_it_be(:project_2) { create(:project, group: sub_group) }
+ let_it_be(:runner_group) { create(:ci_runner, :group, groups: [group]) }
+ let_it_be(:runner_project_1) { create(:ci_runner, :project, projects: [project])}
+ let_it_be(:runner_project_2) { create(:ci_runner, :project, projects: [project_2])}
+ let_it_be(:runner_project_3) { create(:ci_runner, :project, projects: [project, project_2])}
before do
sign_in(user)
@@ -18,11 +25,12 @@ RSpec.describe Groups::Settings::CiCdController do
group.add_owner(user)
end
- it 'renders show with 200 status code' do
+ it 'renders show with 200 status code and correct runners' do
get :show, params: { group_id: group }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:show)
+ expect(assigns(:group_runners)).to match_array([runner_group, runner_project_1, runner_project_2, runner_project_3])
end
end
@@ -35,6 +43,7 @@ RSpec.describe Groups::Settings::CiCdController do
get :show, params: { group_id: group }
expect(response).to have_gitlab_http_status(:not_found)
+ expect(assigns(:group_runners)).to be_nil
end
end
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index dce7105c073..469e58c94e7 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -52,8 +52,6 @@ RSpec.describe GroupsController do
expect(assigns(:events).map(&:id)).to contain_exactly(event.id)
end
end
-
- it_behaves_like 'namespace storage limit alert'
end
describe 'GET #show' do
@@ -941,7 +939,7 @@ RSpec.describe GroupsController do
allow(Gitlab::ApplicationRateLimiter)
.to receive(:increment)
- .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_export][:threshold] + 1)
+ .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_export][:threshold].call + 1)
end
it 'throttles the endpoint' do
@@ -1015,7 +1013,7 @@ RSpec.describe GroupsController do
allow(Gitlab::ApplicationRateLimiter)
.to receive(:increment)
- .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_download_export][:threshold] + 1)
+ .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_download_export][:threshold].call + 1)
end
it 'throttles the endpoint' do
@@ -1138,6 +1136,36 @@ RSpec.describe GroupsController do
it_behaves_like 'disabled when using an external authorization service'
end
+ describe "GET #activity as JSON" do
+ include DesignManagementTestHelpers
+ render_views
+
+ let(:project) { create(:project, :public, group: group) }
+ let(:other_project) { create(:project, :public, group: group) }
+
+ def get_activity
+ get :activity, params: { format: :json, id: group.to_param }
+ end
+
+ before do
+ enable_design_management
+ issue = create(:issue, project: project)
+ create(:event, :created, project: project, target: issue)
+ create(:design_event, project: project)
+ create(:design_event, project: other_project)
+
+ sign_in(user)
+
+ request.cookies[:event_filter] = 'all'
+ end
+
+ it 'returns count' do
+ get_activity
+
+ expect(json_response['count']).to eq(3)
+ end
+ end
+
describe 'GET #issues' do
subject { get :issues, params: { id: group.to_param } }
diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb
index ec38a635c2d..0427715d1ac 100644
--- a/spec/controllers/import/bitbucket_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_controller_spec.rb
@@ -58,12 +58,12 @@ RSpec.describe Import::BitbucketController do
before do
@repo = double(name: 'vim', slug: 'vim', owner: 'asd', full_name: 'asd/vim', clone_url: 'http://test.host/demo/url.git', 'valid?' => true)
@invalid_repo = double(name: 'mercurialrepo', slug: 'mercurialrepo', owner: 'asd', full_name: 'asd/mercurialrepo', clone_url: 'http://test.host/demo/mercurialrepo.git', 'valid?' => false)
+ allow(controller).to receive(:provider_url).and_return('http://demobitbucket.org')
assign_session_tokens
- stub_feature_flags(new_import_ui: false)
end
- it_behaves_like 'import controller with new_import_ui feature flag' do
+ it_behaves_like 'import controller status' do
before do
allow(controller).to receive(:provider_url).and_return('http://demobitbucket.org')
end
@@ -75,44 +75,16 @@ RSpec.describe Import::BitbucketController do
let(:client_repos_field) { :repos }
end
- context 'with new_import_ui feature flag enabled' do
- before do
- stub_feature_flags(new_import_ui: true)
- allow(controller).to receive(:provider_url).and_return('http://demobitbucket.org')
- end
-
- it 'returns invalid repos' do
- allow_any_instance_of(Bitbucket::Client).to receive(:repos).and_return([@repo, @invalid_repo])
-
- get :status, format: :json
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['incompatible_repos'].length).to eq(1)
- expect(json_response.dig("incompatible_repos", 0, "id")).to eq(@invalid_repo.full_name)
- expect(json_response['provider_repos'].length).to eq(1)
- expect(json_response.dig("provider_repos", 0, "id")).to eq(@repo.full_name)
- end
- end
-
- it "assigns variables" do
- @project = create(:project, import_type: 'bitbucket', creator_id: user.id)
- allow_any_instance_of(Bitbucket::Client).to receive(:repos).and_return([@repo])
+ it 'returns invalid repos' do
+ allow_any_instance_of(Bitbucket::Client).to receive(:repos).and_return([@repo, @invalid_repo])
- get :status
-
- expect(assigns(:already_added_projects)).to eq([@project])
- expect(assigns(:repos)).to eq([@repo])
- expect(assigns(:incompatible_repos)).to eq([])
- end
+ get :status, format: :json
- it "does not show already added project" do
- @project = create(:project, import_type: 'bitbucket', creator_id: user.id, import_source: 'asd/vim')
- allow_any_instance_of(Bitbucket::Client).to receive(:repos).and_return([@repo])
-
- get :status
-
- expect(assigns(:already_added_projects)).to eq([@project])
- expect(assigns(:repos)).to eq([])
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['incompatible_repos'].length).to eq(1)
+ expect(json_response.dig("incompatible_repos", 0, "id")).to eq(@invalid_repo.full_name)
+ expect(json_response['provider_repos'].length).to eq(1)
+ expect(json_response.dig("provider_repos", 0, "id")).to eq(@repo.full_name)
end
context 'when filtering' do
diff --git a/spec/controllers/import/bitbucket_server_controller_spec.rb b/spec/controllers/import/bitbucket_server_controller_spec.rb
index af471b478fa..bb80de6425f 100644
--- a/spec/controllers/import/bitbucket_server_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_server_controller_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Import::BitbucketServerController do
let(:user) { create(:user) }
let(:project_key) { 'test-project' }
let(:repo_slug) { 'some-repo' }
+ let(:repo_id) { "#{project_key}/#{repo_slug}" }
let(:client) { instance_double(BitbucketServer::Client) }
def assign_session_tokens
@@ -46,7 +47,7 @@ RSpec.describe Import::BitbucketServerController do
.to receive(:new).with(project_key, repo_slug, anything, project_name, user.namespace, user, anything)
.and_return(double(execute: project))
- post :create, params: { project: project_key, repository: repo_slug }, format: :json
+ post :create, params: { repo_id: repo_id }, format: :json
expect(response).to have_gitlab_http_status(:ok)
end
@@ -59,20 +60,20 @@ RSpec.describe Import::BitbucketServerController do
.to receive(:new).with(project_key, repo_slug, anything, project_name, user.namespace, user, anything)
.and_return(double(execute: project))
- post :create, params: { project: project_key, repository: repo_slug, format: :json }
+ post :create, params: { repo_id: repo_id }, format: :json
expect(response).to have_gitlab_http_status(:ok)
end
end
it 'returns an error when an invalid project key is used' do
- post :create, params: { project: 'some&project' }
+ post :create, params: { repo_id: 'some&project/repo' }
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
it 'returns an error when an invalid repository slug is used' do
- post :create, params: { project: 'some-project', repository: 'try*this' }
+ post :create, params: { repo_id: 'some-project/try*this' }
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
@@ -80,7 +81,7 @@ RSpec.describe Import::BitbucketServerController do
it 'returns an error when the project cannot be found' do
allow(client).to receive(:repo).with(project_key, repo_slug).and_return(nil)
- post :create, params: { project: project_key, repository: repo_slug }, format: :json
+ post :create, params: { repo_id: repo_id }, format: :json
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
@@ -90,15 +91,15 @@ RSpec.describe Import::BitbucketServerController do
.to receive(:new).with(project_key, repo_slug, anything, project_name, user.namespace, user, anything)
.and_return(double(execute: build(:project)))
- post :create, params: { project: project_key, repository: repo_slug }, format: :json
+ post :create, params: { repo_id: repo_id }, format: :json
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
it "returns an error when the server can't be contacted" do
- expect(client).to receive(:repo).with(project_key, repo_slug).and_raise(::BitbucketServer::Connection::ConnectionError)
+ allow(client).to receive(:repo).with(project_key, repo_slug).and_raise(::BitbucketServer::Connection::ConnectionError)
- post :create, params: { project: project_key, repository: repo_slug }, format: :json
+ post :create, params: { repo_id: repo_id }, format: :json
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
@@ -123,7 +124,9 @@ RSpec.describe Import::BitbucketServerController do
end
it 'sets the session variables' do
- post :configure, params: { personal_access_token: token, bitbucket_username: username, bitbucket_server_url: url }
+ allow(controller).to receive(:allow_local_requests?).and_return(true)
+
+ post :configure, params: { personal_access_token: token, bitbucket_server_username: username, bitbucket_server_url: url }
expect(session[:bitbucket_server_url]).to eq(url)
expect(session[:bitbucket_server_username]).to eq(username)
@@ -145,28 +148,21 @@ RSpec.describe Import::BitbucketServerController do
@invalid_repo = double(slug: 'invalid', project_key: 'foobar', full_name: 'asd/foobar', "valid?" => false, browse_url: 'http://bad-repo', name: 'invalid')
@created_repo = double(slug: 'created', project_key: 'existing', full_name: 'group/created', "valid?" => true, browse_url: 'http://existing')
assign_session_tokens
- stub_feature_flags(new_import_ui: false)
end
- context 'with new_import_ui feature flag enabled' do
- before do
- stub_feature_flags(new_import_ui: true)
- end
-
- it 'returns invalid repos' do
- allow(client).to receive(:repos).with(filter: nil, limit: 25, page_offset: 0).and_return([@repo, @invalid_repo])
+ it 'returns invalid repos' do
+ allow(client).to receive(:repos).with(filter: nil, limit: 25, page_offset: 0).and_return([@repo, @invalid_repo])
- get :status, format: :json
+ get :status, format: :json
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['incompatible_repos'].length).to eq(1)
- expect(json_response.dig("incompatible_repos", 0, "id")).to eq(@invalid_repo.full_name)
- expect(json_response['provider_repos'].length).to eq(1)
- expect(json_response.dig("provider_repos", 0, "id")).to eq(@repo.full_name)
- end
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['incompatible_repos'].length).to eq(1)
+ expect(json_response.dig("incompatible_repos", 0, "id")).to eq(@invalid_repo.full_name)
+ expect(json_response['provider_repos'].length).to eq(1)
+ expect(json_response.dig("provider_repos", 0, "id")).to eq(@repo.full_name)
end
- it_behaves_like 'import controller with new_import_ui feature flag' do
+ it_behaves_like 'import controller status' do
let(:repo) { @repo }
let(:repo_id) { @repo.full_name }
let(:import_source) { @repo.browse_url }
@@ -174,47 +170,14 @@ RSpec.describe Import::BitbucketServerController do
let(:client_repos_field) { :repos }
end
- it 'assigns repository categories' do
- created_project = create(:project, :import_finished, import_type: 'bitbucket_server', creator_id: user.id, import_source: @created_repo.browse_url)
-
- expect(repos).to receive(:partition).and_return([[@repo, @created_repo], [@invalid_repo]])
- expect(repos).to receive(:current_page).and_return(1)
- expect(repos).to receive(:next_page).and_return(2)
- expect(repos).to receive(:prev_page).and_return(nil)
- expect(client).to receive(:repos).and_return(repos)
-
- get :status
-
- expect(assigns(:already_added_projects)).to eq([created_project])
- expect(assigns(:repos)).to eq([@repo])
- expect(assigns(:incompatible_repos)).to eq([@invalid_repo])
- end
-
context 'when filtering' do
let(:filter) { 'test' }
it 'passes filter param to bitbucket client' do
- expect(repos).to receive(:partition).and_return([[@repo, @created_repo], [@invalid_repo]])
- expect(client).to receive(:repos).with(filter: filter, limit: 25, page_offset: 0).and_return(repos)
+ expect(client).to receive(:repos).with(filter: filter, limit: 25, page_offset: 0).and_return([@repo])
get :status, params: { filter: filter }, as: :json
end
end
end
-
- describe 'GET jobs' do
- before do
- assign_session_tokens
- end
-
- it 'returns a list of imported projects' do
- created_project = create(:project, import_type: 'bitbucket_server', creator_id: user.id)
-
- get :jobs
-
- expect(json_response.count).to eq(1)
- expect(json_response.first['id']).to eq(created_project.id)
- expect(json_response.first['import_status']).to eq('none')
- end
- end
end
diff --git a/spec/controllers/import/fogbugz_controller_spec.rb b/spec/controllers/import/fogbugz_controller_spec.rb
index aabbcb30358..376c089df78 100644
--- a/spec/controllers/import/fogbugz_controller_spec.rb
+++ b/spec/controllers/import/fogbugz_controller_spec.rb
@@ -82,36 +82,15 @@ RSpec.describe Import::FogbugzController do
before do
@repo = OpenStruct.new(id: 'demo', name: 'vim')
stub_client(valid?: true)
- stub_feature_flags(new_import_ui: false)
end
- it_behaves_like 'import controller with new_import_ui feature flag' do
+ it_behaves_like 'import controller status' do
let(:repo) { @repo }
let(:repo_id) { @repo.id }
let(:import_source) { @repo.name }
let(:provider_name) { 'fogbugz' }
let(:client_repos_field) { :repos }
end
-
- it 'assigns variables' do
- @project = create(:project, import_type: 'fogbugz', creator_id: user.id)
- stub_client(repos: [@repo])
-
- get :status
-
- expect(assigns(:already_added_projects)).to eq([@project])
- expect(assigns(:repos)).to eq([@repo])
- end
-
- it 'does not show already added project' do
- @project = create(:project, import_type: 'fogbugz', creator_id: user.id, import_source: 'vim')
- stub_client(repos: [@repo])
-
- get :status
-
- expect(assigns(:already_added_projects)).to eq([@project])
- expect(assigns(:repos)).to eq([])
- end
end
describe 'POST create' do
diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb
index 1cd0593f762..42c4348dac2 100644
--- a/spec/controllers/import/gitlab_controller_spec.rb
+++ b/spec/controllers/import/gitlab_controller_spec.rb
@@ -36,36 +36,15 @@ RSpec.describe Import::GitlabController do
before do
@repo = OpenStruct.new(id: 1, path: 'vim', path_with_namespace: 'asd/vim', web_url: 'https://gitlab.com/asd/vim')
assign_session_token
- stub_feature_flags(new_import_ui: false)
end
- it_behaves_like 'import controller with new_import_ui feature flag' do
+ it_behaves_like 'import controller status' do
let(:repo) { @repo }
let(:repo_id) { @repo.id }
let(:import_source) { @repo.path_with_namespace }
let(:provider_name) { 'gitlab' }
let(:client_repos_field) { :projects }
end
-
- it "assigns variables" do
- @project = create(:project, import_type: 'gitlab', creator_id: user.id)
- stub_client(projects: [@repo])
-
- get :status
-
- expect(assigns(:already_added_projects)).to eq([@project])
- expect(assigns(:repos)).to eq([@repo])
- end
-
- it "does not show already added project" do
- @project = create(:project, import_type: 'gitlab', creator_id: user.id, import_source: 'asd/vim')
- stub_client(projects: [@repo])
-
- get :status
-
- expect(assigns(:already_added_projects)).to eq([@project])
- expect(assigns(:repos)).to eq([])
- end
end
describe "POST create" do
diff --git a/spec/controllers/instance_statistics/cohorts_controller_spec.rb b/spec/controllers/instance_statistics/cohorts_controller_spec.rb
index b92fcb2575c..c16ac0dced1 100644
--- a/spec/controllers/instance_statistics/cohorts_controller_spec.rb
+++ b/spec/controllers/instance_statistics/cohorts_controller_spec.rb
@@ -18,4 +18,11 @@ RSpec.describe InstanceStatistics::CohortsController do
expect(response).to have_gitlab_http_status(:not_found)
end
+
+ describe 'GET #index' do
+ it_behaves_like 'tracking unique visits', :index do
+ let(:request_params) { {} }
+ let(:target_id) { 'i_analytics_cohorts' }
+ end
+ end
end
diff --git a/spec/controllers/instance_statistics/dev_ops_score_controller_spec.rb b/spec/controllers/instance_statistics/dev_ops_score_controller_spec.rb
index d729682bef0..c9677a64eef 100644
--- a/spec/controllers/instance_statistics/dev_ops_score_controller_spec.rb
+++ b/spec/controllers/instance_statistics/dev_ops_score_controller_spec.rb
@@ -4,4 +4,17 @@ require 'spec_helper'
RSpec.describe InstanceStatistics::DevOpsScoreController do
it_behaves_like 'instance statistics availability'
+
+ describe 'GET #index' do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ it_behaves_like 'tracking unique visits', :index do
+ let(:request_params) { {} }
+ let(:target_id) { 'i_analytics_dev_ops_score' }
+ end
+ end
end
diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb
index f2821bb67e8..a9e4073780d 100644
--- a/spec/controllers/invites_controller_spec.rb
+++ b/spec/controllers/invites_controller_spec.rb
@@ -4,21 +4,44 @@ require 'spec_helper'
RSpec.describe InvitesController do
let(:token) { '123456' }
- let(:user) { create(:user) }
- let(:member) { create(:project_member, invite_token: token, invite_email: 'test@abc.com', user: user) }
+ let_it_be(:user) { create(:user) }
+ let(:member) { create(:project_member, :invited, invite_token: token, invite_email: user.email) }
+ let(:project_members) { member.source.users }
before do
controller.instance_variable_set(:@member, member)
sign_in(user)
end
- describe 'GET #accept' do
+ describe 'GET #show' do
+ it 'accepts user if invite email matches signed in user' do
+ expect do
+ get :show, params: { id: token }
+ end.to change { project_members.include?(user) }.from(false).to(true)
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(flash[:notice]).to include 'You have been granted'
+ end
+
+ it 'forces re-confirmation if email does not match signed in user' do
+ member.invite_email = 'bogus@email.com'
+
+ expect do
+ get :show, params: { id: token }
+ end.not_to change { project_members.include?(user) }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(flash[:notice]).to be_nil
+ end
+ end
+
+ describe 'POST #accept' do
it 'accepts user' do
- get :accept, params: { id: token }
- member.reload
+ expect do
+ post :accept, params: { id: token }
+ end.to change { project_members.include?(user) }.from(false).to(true)
expect(response).to have_gitlab_http_status(:found)
- expect(member.user).to eq(user)
expect(flash[:notice]).to include 'You have been granted'
end
end
@@ -26,8 +49,8 @@ RSpec.describe InvitesController do
describe 'GET #decline' do
it 'declines user' do
get :decline, params: { id: token }
- expect {member.reload}.to raise_error ActiveRecord::RecordNotFound
+ expect { member.reload }.to raise_error ActiveRecord::RecordNotFound
expect(response).to have_gitlab_http_status(:found)
expect(flash[:notice]).to include 'You have declined the invitation to join'
end
diff --git a/spec/controllers/oauth/applications_controller_spec.rb b/spec/controllers/oauth/applications_controller_spec.rb
index f20204b6718..38f46ee7b15 100644
--- a/spec/controllers/oauth/applications_controller_spec.rb
+++ b/spec/controllers/oauth/applications_controller_spec.rb
@@ -121,6 +121,22 @@ RSpec.describe Oauth::ApplicationsController do
end
end
+ describe 'locale' do
+ let(:user) { create(:user, preferred_language: 'uk') }
+
+ before do
+ sign_in(user)
+
+ allow(Gitlab::I18n).to receive(:with_locale).and_call_original
+ end
+
+ it "sets user's locale" do
+ expect(Gitlab::I18n).to receive(:with_locale).with('uk')
+
+ get :new
+ end
+ end
+
def disable_user_oauth
allow(Gitlab::CurrentSettings.current_application_settings).to receive(:user_oauth_applications?).and_return(false)
end
diff --git a/spec/controllers/profiles/keys_controller_spec.rb b/spec/controllers/profiles/keys_controller_spec.rb
index 258ed62262a..66f6135df1e 100644
--- a/spec/controllers/profiles/keys_controller_spec.rb
+++ b/spec/controllers/profiles/keys_controller_spec.rb
@@ -20,69 +20,4 @@ RSpec.describe Profiles::KeysController do
expect(Key.last.expires_at).to be_like_time(expires_at)
end
end
-
- describe "#get_keys" do
- describe "non existent user" do
- it "does not generally work" do
- get :get_keys, params: { username: 'not-existent' }
-
- expect(response).not_to be_successful
- end
- end
-
- describe "user with no keys" do
- it "does generally work" do
- get :get_keys, params: { username: user.username }
-
- expect(response).to be_successful
- end
-
- it "renders all keys separated with a new line" do
- get :get_keys, params: { username: user.username }
-
- expect(response.body).to eq("")
- end
-
- it "responds with text/plain content type" do
- get :get_keys, params: { username: user.username }
- expect(response.content_type).to eq("text/plain")
- end
- end
-
- describe "user with keys" do
- let!(:key) { create(:key, user: user) }
- let!(:another_key) { create(:another_key, user: user) }
- let!(:deploy_key) { create(:deploy_key, user: user) }
-
- it "does generally work" do
- get :get_keys, params: { username: user.username }
-
- expect(response).to be_successful
- end
-
- it "renders all non deploy keys separated with a new line" do
- get :get_keys, params: { username: user.username }
-
- expect(response.body).not_to eq('')
- expect(response.body).to eq(user.all_ssh_keys.join("\n"))
-
- expect(response.body).to include(key.key.sub(' dummy@gitlab.com', ''))
- expect(response.body).to include(another_key.key.sub(' dummy@gitlab.com', ''))
-
- expect(response.body).not_to include(deploy_key.key)
- end
-
- it "does not render the comment of the key" do
- get :get_keys, params: { username: user.username }
-
- expect(response.body).not_to match(/dummy@gitlab.com/)
- end
-
- it "responds with text/plain content type" do
- get :get_keys, params: { username: user.username }
-
- expect(response.content_type).to eq("text/plain")
- end
- end
- end
end
diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb
index 5645e25b741..da4faad2a39 100644
--- a/spec/controllers/projects/clusters_controller_spec.rb
+++ b/spec/controllers/projects/clusters_controller_spec.rb
@@ -200,6 +200,48 @@ RSpec.describe Projects::ClustersController do
end
end
+ describe 'GET #prometheus_proxy' do
+ let(:proxyable) do
+ create(:cluster, :provided_by_gcp, projects: [project])
+ end
+
+ it_behaves_like 'metrics dashboard prometheus api proxy' do
+ let(:proxyable_params) do
+ {
+ id: proxyable.id.to_s,
+ namespace_id: project.namespace.full_path,
+ project_id: project.name
+ }
+ end
+
+ context 'with anonymous user' do
+ let(:prometheus_body) { nil }
+
+ before do
+ sign_out(user)
+ end
+
+ it 'redirects to signin page' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+ end
+ end
+
+ it_behaves_like 'GET #metrics_dashboard for dashboard', 'Cluster health' do
+ let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
+
+ let(:metrics_dashboard_req_params) do
+ {
+ id: cluster.id,
+ namespace_id: project.namespace.full_path,
+ project_id: project.name
+ }
+ end
+ end
+
describe 'POST create for new cluster' do
let(:legacy_abac_param) { 'true' }
let(:params) do
diff --git a/spec/controllers/projects/cycle_analytics_controller_spec.rb b/spec/controllers/projects/cycle_analytics_controller_spec.rb
index 8feb964cdde..ec853b74b9b 100644
--- a/spec/controllers/projects/cycle_analytics_controller_spec.rb
+++ b/spec/controllers/projects/cycle_analytics_controller_spec.rb
@@ -25,6 +25,13 @@ RSpec.describe Projects::CycleAnalyticsController do
end
end
+ context 'tracking visits to html page' do
+ it_behaves_like 'tracking unique visits', :show do
+ let(:request_params) { { namespace_id: project.namespace, project_id: project } }
+ let(:target_id) { 'p_analytics_valuestream' }
+ end
+ end
+
describe 'cycle analytics not set up flag' do
context 'with no data' do
it 'is true' do
diff --git a/spec/controllers/projects/deployments_controller_spec.rb b/spec/controllers/projects/deployments_controller_spec.rb
index 85dd86d91e9..c6532e83441 100644
--- a/spec/controllers/projects/deployments_controller_spec.rb
+++ b/spec/controllers/projects/deployments_controller_spec.rb
@@ -36,6 +36,52 @@ RSpec.describe Projects::DeploymentsController do
expect(response).to be_ok
expect(response).to match_response_schema('deployments')
end
+
+ context 'anonymous user' do
+ let(:anonymous_user) { create(:user) }
+
+ before do
+ sign_in(anonymous_user)
+ end
+
+ context 'project and metrics dashboard are public' do
+ before do
+ project.update!(
+ visibility_level: Gitlab::VisibilityLevel::PUBLIC,
+ project_feature_attributes: {
+ metrics_dashboard_access_level: Gitlab::VisibilityLevel::PUBLIC
+ }
+ )
+ end
+
+ it 'returns a list with deployments information' do
+ create(:deployment, :success, environment: environment)
+
+ get :index, params: deployment_params
+
+ expect(response).to be_ok
+ end
+ end
+
+ context 'project and metrics dashboard are private' do
+ before do
+ project.update!(
+ visibility_level: Gitlab::VisibilityLevel::PRIVATE,
+ project_feature_attributes: {
+ metrics_dashboard_access_level: Gitlab::VisibilityLevel::PRIVATE
+ }
+ )
+ end
+
+ it 'responds with not found' do
+ create(:deployment, :success, environment: environment)
+
+ get :index, params: deployment_params
+
+ expect(response).to be_not_found
+ end
+ end
+ end
end
describe 'GET #metrics' do
diff --git a/spec/controllers/projects/discussions_controller_spec.rb b/spec/controllers/projects/discussions_controller_spec.rb
index f2efd40afdb..f9d16e761cb 100644
--- a/spec/controllers/projects/discussions_controller_spec.rb
+++ b/spec/controllers/projects/discussions_controller_spec.rb
@@ -182,7 +182,8 @@ RSpec.describe Projects::DiscussionsController do
it "unresolves the discussion" do
delete :unresolve, params: request_params
- expect(note.reload.discussion.resolved?).to be false
+ # discussion is memoized and reload doesn't clear the memoization
+ expect(Note.find(note.id).discussion.resolved?).to be false
end
it "returns status 200" do
diff --git a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb
index 17952aa0683..68d50cf19f0 100644
--- a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb
+++ b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb
@@ -3,215 +3,73 @@
require 'spec_helper'
RSpec.describe Projects::Environments::PrometheusApiController do
- let_it_be(:project) { create(:project) }
- let_it_be(:environment) { create(:environment, project: project) }
let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:proxyable) { create(:environment, project: project) }
before do
project.add_reporter(user)
sign_in(user)
end
- describe 'GET #proxy' do
- let(:prometheus_proxy_service) { instance_double(Prometheus::ProxyService) }
-
- let(:expected_params) do
- ActionController::Parameters.new(
- environment_params(
- proxy_path: 'query',
- controller: 'projects/environments/prometheus_api',
- action: 'proxy'
- )
- ).permit!
- end
-
- context 'with valid requests' do
- before do
- allow(Prometheus::ProxyService).to receive(:new)
- .with(environment, 'GET', 'query', expected_params)
- .and_return(prometheus_proxy_service)
-
- allow(prometheus_proxy_service).to receive(:execute)
- .and_return(service_result)
+ describe 'GET #prometheus_proxy' do
+ it_behaves_like 'metrics dashboard prometheus api proxy' do
+ let(:proxyable_params) do
+ {
+ id: proxyable.id.to_s,
+ namespace_id: project.namespace.full_path,
+ project_id: project.name
+ }
end
- context 'with success result' do
- let(:service_result) { { status: :success, body: prometheus_body } }
+ context 'with variables' do
let(:prometheus_body) { '{"status":"success"}' }
- let(:prometheus_json_body) { Gitlab::Json.parse(prometheus_body) }
+ let(:pod_name) { "pod1" }
- it 'returns prometheus response' do
- get :proxy, params: environment_params
-
- expect(Prometheus::ProxyService).to have_received(:new)
- .with(environment, 'GET', 'query', expected_params)
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response).to eq(prometheus_json_body)
+ before do
+ expected_params[:query] = %{up{pod_name="#{pod_name}"}}
+ expected_params[:variables] = { 'pod_name' => pod_name }
end
- context 'with format string' do
- before do
- expected_params[:query] = %{up{environment="#{environment.slug}"}}
- end
-
- it 'replaces variables with values' do
- get :proxy, params: environment_params.merge(query: 'up{environment="{{ci_environment_slug}}"}')
-
- expect(Prometheus::ProxyService).to have_received(:new)
- .with(environment, 'GET', 'query', expected_params)
- end
-
- context 'with nil query' do
- let(:params_without_query) do
- environment_params.except(:query)
- end
-
- before do
- expected_params.delete(:query)
- end
-
- it 'does not raise error' do
- get :proxy, params: params_without_query
+ it 'replaces variables with values' do
+ get :prometheus_proxy, params: prometheus_proxy_params.merge(
+ query: 'up{pod_name="{{pod_name}}"}', variables: { 'pod_name' => pod_name }
+ )
- expect(Prometheus::ProxyService).to have_received(:new)
- .with(environment, 'GET', 'query', expected_params)
- end
- end
+ expect(response).to have_gitlab_http_status(:success)
+ expect(Prometheus::ProxyService).to have_received(:new)
+ .with(proxyable, 'GET', 'query', expected_params)
end
- context 'with variables' do
- let(:pod_name) { "pod1" }
-
- before do
- expected_params[:query] = %{up{pod_name="#{pod_name}"}}
- expected_params[:variables] = { 'pod_name' => pod_name }
- end
-
- it 'replaces variables with values' do
- get :proxy, params: environment_params.merge(
- query: 'up{pod_name="{{pod_name}}"}', variables: { 'pod_name' => pod_name }
+ context 'with invalid variables' do
+ let(:params_with_invalid_variables) do
+ prometheus_proxy_params.merge(
+ query: 'up{pod_name="{{pod_name}}"}', variables: ['a']
)
-
- expect(response).to have_gitlab_http_status(:success)
- expect(Prometheus::ProxyService).to have_received(:new)
- .with(environment, 'GET', 'query', expected_params)
- end
-
- context 'with invalid variables' do
- let(:params_with_invalid_variables) do
- environment_params.merge(
- query: 'up{pod_name="{{pod_name}}"}', variables: ['a']
- )
- end
-
- it 'returns 400' do
- get :proxy, params: params_with_invalid_variables
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(Prometheus::ProxyService).not_to receive(:new)
- end
- end
- end
- end
-
- context 'with nil result' do
- let(:service_result) { nil }
-
- it 'returns 204 no_content' do
- get :proxy, params: environment_params
-
- expect(json_response['status']).to eq(_('processing'))
- expect(json_response['message']).to eq(_('Not ready yet. Try again later.'))
- expect(response).to have_gitlab_http_status(:no_content)
- end
- end
-
- context 'with 404 result' do
- let(:service_result) { { http_status: 404, status: :success, body: '{"body": "value"}' } }
-
- it 'returns body' do
- get :proxy, params: environment_params
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response['body']).to eq('value')
- end
- end
-
- context 'with error result' do
- context 'with http_status' do
- let(:service_result) do
- { http_status: :service_unavailable, status: :error, message: 'error message' }
- end
-
- it 'sets the http response status code' do
- get :proxy, params: environment_params
-
- expect(response).to have_gitlab_http_status(:service_unavailable)
- expect(json_response['status']).to eq('error')
- expect(json_response['message']).to eq('error message')
end
- end
-
- context 'without http_status' do
- let(:service_result) { { status: :error, message: 'error message' } }
- it 'returns bad_request' do
- get :proxy, params: environment_params
+ it 'returns 400' do
+ get :prometheus_proxy, params: params_with_invalid_variables
expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['status']).to eq('error')
- expect(json_response['message']).to eq('error message')
+ expect(Prometheus::ProxyService).not_to receive(:new)
end
end
end
- end
- context 'with inappropriate requests' do
context 'with anonymous user' do
+ let(:prometheus_body) { nil }
+
before do
sign_out(user)
end
it 'redirects to signin page' do
- get :proxy, params: environment_params
+ get :prometheus_proxy, params: prometheus_proxy_params
expect(response).to redirect_to(new_user_session_path)
end
end
-
- context 'without correct permissions' do
- before do
- project.team.truncate
- end
-
- it 'returns 404' do
- get :proxy, params: environment_params
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
end
-
- context 'with invalid environment id' do
- let(:other_environment) { create(:environment) }
-
- it 'returns 404' do
- get :proxy, params: environment_params(id: other_environment.id)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
-
- private
-
- def environment_params(params = {})
- {
- id: environment.id.to_s,
- namespace_id: project.namespace.full_path,
- project_id: project.name,
- proxy_path: 'query',
- query: '1'
- }.merge(params)
end
end
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index cca4b597f4c..85ec1f7396d 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -546,28 +546,20 @@ RSpec.describe Projects::EnvironmentsController do
end
describe 'GET #metrics_dashboard' do
- shared_examples_for 'correctly formatted response' do |status_code|
- it 'returns a json object with the correct keys' do
- get :metrics_dashboard, params: environment_params(dashboard_params)
-
- # Exlcude `all_dashboards` to handle separately.
- found_keys = json_response.keys - ['all_dashboards']
-
- expect(response).to have_gitlab_http_status(status_code)
- expect(found_keys).to contain_exactly(*expected_keys)
- end
- end
+ let(:metrics_dashboard_req_params) { environment_params(dashboard_params) }
shared_examples_for '200 response' do
- let(:expected_keys) { %w(dashboard status metrics_data) }
-
- it_behaves_like 'correctly formatted response', :ok
+ it_behaves_like 'GET #metrics_dashboard correctly formatted response' do
+ let(:expected_keys) { %w(dashboard status metrics_data) }
+ let(:status_code) { :ok }
+ end
end
shared_examples_for 'error response' do |status_code|
- let(:expected_keys) { %w(message status) }
-
- it_behaves_like 'correctly formatted response', status_code
+ it_behaves_like 'GET #metrics_dashboard correctly formatted response' do
+ let(:expected_keys) { %w(message status) }
+ let(:status_code) { status_code }
+ end
end
shared_examples_for 'includes all dashboards' do
@@ -581,29 +573,14 @@ RSpec.describe Projects::EnvironmentsController do
end
shared_examples_for 'the default dashboard' do
- it_behaves_like '200 response'
it_behaves_like 'includes all dashboards'
-
- it 'is the default dashboard' do
- get :metrics_dashboard, params: environment_params(dashboard_params)
-
- expect(json_response['dashboard']['dashboard']).to eq('Environment metrics')
- end
+ it_behaves_like 'GET #metrics_dashboard for dashboard', 'Environment metrics'
end
shared_examples_for 'the specified dashboard' do |expected_dashboard|
- it_behaves_like '200 response'
it_behaves_like 'includes all dashboards'
- it 'has the correct name' do
- get :metrics_dashboard, params: environment_params(dashboard_params)
-
- dashboard_name = json_response['dashboard']['dashboard']
-
- # 'Environment metrics' is the default dashboard.
- expect(dashboard_name).not_to eq('Environment metrics')
- expect(dashboard_name).to eq(expected_dashboard)
- end
+ it_behaves_like 'GET #metrics_dashboard for dashboard', expected_dashboard
context 'when the dashboard cannot not be processed' do
before do
diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb
index 12cef6bea09..49def8f80b0 100644
--- a/spec/controllers/projects/graphs_controller_spec.rb
+++ b/spec/controllers/projects/graphs_controller_spec.rb
@@ -80,6 +80,15 @@ RSpec.describe Projects::GraphsController do
expect(assigns[:daily_coverage_options]).to be_nil
end
end
+
+ it_behaves_like 'tracking unique visits', :charts do
+ before do
+ sign_in(user)
+ end
+
+ let(:request_params) { { namespace_id: project.namespace.path, project_id: project.path, id: 'master' } }
+ let(:target_id) { 'p_analytics_repo' }
+ end
end
context 'when languages were previously detected' do
diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb
index 29cfd1c352e..029b4210f19 100644
--- a/spec/controllers/projects/imports_controller_spec.rb
+++ b/spec/controllers/projects/imports_controller_spec.rb
@@ -8,33 +8,15 @@ RSpec.describe Projects::ImportsController do
before do
sign_in(user)
- project.add_maintainer(user)
end
describe 'GET #show' do
- context 'when repository does not exists' do
- it 'renders template' do
- get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
-
- expect(response).to render_template :show
- end
-
- it 'sets flash.now if params is present' do
- get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: { to: '/', notice_now: 'Started' } }
-
- expect(flash.now[:notice]).to eq 'Started'
+ context 'when the user has maintainer rights' do
+ before do
+ project.add_maintainer(user)
end
- end
-
- 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
- import_state.update(status: :started)
- end
+ context 'when repository does not exists' do
it 'renders template' do
get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
@@ -42,82 +24,138 @@ RSpec.describe Projects::ImportsController do
end
it 'sets flash.now if params is present' do
- get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: { to: '/', notice_now: 'In progress' } }
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: { to: '/', notice_now: 'Started' } }
- expect(flash.now[:notice]).to eq 'In progress'
+ expect(flash.now[:notice]).to eq 'Started'
end
end
- context 'when import failed' do
- before do
- import_state.update(status: :failed)
- end
+ 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 }
- it 'redirects to new_namespace_project_import_path' do
- get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
+ context 'when import is in progress' do
+ before do
+ import_state.update(status: :started)
+ end
- expect(response).to redirect_to new_project_import_path(project)
- end
- end
+ it 'renders template' do
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
- context 'when import finished' do
- before do
- import_state.update(status: :finished)
+ expect(response).to render_template :show
+ end
+
+ it 'sets flash.now if params is present' do
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: { to: '/', notice_now: 'In progress' } }
+
+ expect(flash.now[:notice]).to eq 'In progress'
+ end
end
- context 'when project is a fork' do
- it 'redirects to namespace_project_path' do
- allow_any_instance_of(Project).to receive(:forked?).and_return(true)
+ context 'when import failed' do
+ before do
+ import_state.update(status: :failed)
+ end
+ it 'redirects to new_namespace_project_import_path' do
get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
- expect(flash[:notice]).to eq 'The project was successfully forked.'
- expect(response).to redirect_to project_path(project)
+ expect(response).to redirect_to new_project_import_path(project)
end
end
- context 'when project is external' do
- it 'redirects to namespace_project_path' do
- get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
+ context 'when import finished' do
+ before do
+ import_state.update(status: :finished)
+ end
- expect(flash[:notice]).to eq 'The project was successfully imported.'
- expect(response).to redirect_to project_path(project)
+ context 'when project is a fork' do
+ it 'redirects to namespace_project_path' do
+ allow_any_instance_of(Project).to receive(:forked?).and_return(true)
+
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
+
+ expect(flash[:notice]).to eq 'The project was successfully forked.'
+ expect(response).to redirect_to project_path(project)
+ end
end
- end
- context 'when continue params is present' do
- let(:params) do
- {
- to: project_path(project),
- notice: 'Finished'
- }
+ context 'when project is external' do
+ it 'redirects to namespace_project_path' do
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
+
+ expect(flash[:notice]).to eq 'The project was successfully imported.'
+ expect(response).to redirect_to project_path(project)
+ end
end
- it 'redirects to internal params[:to]' do
- get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: params }
+ context 'when continue params is present' do
+ let(:params) do
+ {
+ to: project_path(project),
+ notice: 'Finished'
+ }
+ end
+
+ it 'redirects to internal params[:to]' do
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: params }
+
+ expect(flash[:notice]).to eq params[:notice]
+ expect(response).to redirect_to params[:to]
+ end
- expect(flash[:notice]).to eq params[:notice]
- expect(response).to redirect_to params[:to]
+ it 'does not redirect to external params[:to]' do
+ params[:to] = "//google.com"
+
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: params }
+ expect(response).not_to redirect_to params[:to]
+ end
end
+ end
- it 'does not redirect to external params[:to]' do
- params[:to] = "//google.com"
+ context 'when import never happened' do
+ before do
+ import_state.update(status: :none)
+ end
- get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: params }
- expect(response).not_to redirect_to params[:to]
+ it 'redirects to namespace_project_path' do
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
+
+ expect(response).to redirect_to project_path(project)
end
end
end
+ end
+
+ context 'when project is in group' do
+ let(:project) { create(:project_empty_repo, import_url: 'https://github.com/vim/vim.git', namespace: group) }
+
+ context 'when user has developer access to group and import is in progress' do
+ let(:import_state) { project.import_state }
- context 'when import never happened' do
before do
- import_state.update(status: :none)
+ group.add_developer(user)
+ import_state.update!(status: :started)
end
- it 'redirects to namespace_project_path' do
- get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
+ context 'when group allows developers to create projects' do
+ let(:group) { create(:group, project_creation_level: Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) }
- expect(response).to redirect_to project_path(project)
+ it 'renders template' do
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
+
+ expect(response).to render_template :show
+ end
+ end
+
+ context 'when group prohibits developers to create projects' do
+ let(:group) { create(:group, project_creation_level: Gitlab::Access::MAINTAINER_PROJECT_ACCESS) }
+
+ it 'returns 404 response' do
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
end
end
end
@@ -128,6 +166,7 @@ RSpec.describe Projects::ImportsController do
let(:project) { create(:project) }
before do
+ project.add_maintainer(user)
allow(RepositoryImportWorker).to receive(:perform_async)
post :create, params: { project: params, namespace_id: project.namespace.to_param, project_id: project }
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index bcd1a53bd47..f9580c79390 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -237,7 +237,7 @@ RSpec.describe Projects::IssuesController do
context 'external issue tracker' do
let!(:service) do
- create(:custom_issue_tracker_service, project: project, title: 'Custom Issue Tracker', new_issue_url: 'http://test.com')
+ create(:custom_issue_tracker_service, project: project, new_issue_url: 'http://test.com')
end
before do
@@ -1564,6 +1564,43 @@ RSpec.describe Projects::IssuesController do
end
end
+ describe 'GET service_desk' do
+ let_it_be(:project) { create(:project_empty_repo, :public) }
+ let_it_be(:support_bot) { User.support_bot }
+ let_it_be(:other_user) { create(:user) }
+ let_it_be(:service_desk_issue_1) { create(:issue, project: project, author: support_bot) }
+ let_it_be(:service_desk_issue_2) { create(:issue, project: project, author: support_bot, assignees: [other_user]) }
+ let_it_be(:other_user_issue) { create(:issue, project: project, author: other_user) }
+
+ def get_service_desk(extra_params = {})
+ get :service_desk, params: extra_params.merge(namespace_id: project.namespace, project_id: project)
+ end
+
+ it 'adds an author filter for the support bot user' do
+ get_service_desk
+
+ expect(assigns(:issues)).to contain_exactly(service_desk_issue_1, service_desk_issue_2)
+ end
+
+ it 'does not allow any other author to be set' do
+ get_service_desk(author_username: other_user.username)
+
+ expect(assigns(:issues)).to contain_exactly(service_desk_issue_1, service_desk_issue_2)
+ end
+
+ it 'supports other filters' do
+ get_service_desk(assignee_username: other_user.username)
+
+ expect(assigns(:issues)).to contain_exactly(service_desk_issue_2)
+ end
+
+ it 'allows an assignee to be specified by id' do
+ get_service_desk(assignee_id: other_user.id)
+
+ expect(assigns(:users)).to contain_exactly(other_user, support_bot)
+ end
+ end
+
describe 'GET #discussions' do
let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) }
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index 44dcb0caab2..818b1c30b37 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -646,109 +646,6 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
end
- describe 'GET legacy trace.json' do
- before do
- stub_feature_flags(job_log_json: false)
- get_trace
- end
-
- context 'when job has a trace artifact' do
- let(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) }
-
- it 'returns a trace' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['id']).to eq job.id
- expect(json_response['status']).to eq job.status
- expect(json_response['state']).to be_present
- expect(json_response['append']).not_to be_nil
- expect(json_response['truncated']).not_to be_nil
- expect(json_response['size']).to be_present
- expect(json_response['total']).to be_present
- expect(json_response['html']).to eq(job.trace.html)
- end
- end
-
- context 'when job has a trace' do
- let(:job) { create(:ci_build, :trace_live, pipeline: pipeline) }
-
- it 'returns a trace' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['id']).to eq job.id
- expect(json_response['status']).to eq job.status
- expect(json_response['html']).to eq('<span>BUILD TRACE</span>')
- end
- end
-
- context 'when job has no traces' do
- let(:job) { create(:ci_build, pipeline: pipeline) }
-
- it 'returns no traces' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['id']).to eq job.id
- expect(json_response['status']).to eq job.status
- expect(json_response['html']).to be_nil
- end
- end
-
- context 'when job has a trace with ANSI sequence and Unicode' do
- let(:job) { create(:ci_build, :unicode_trace_live, pipeline: pipeline) }
-
- it 'returns a trace with Unicode' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['id']).to eq job.id
- expect(json_response['status']).to eq job.status
- expect(json_response['html']).to include("ヾ(´༎ຶД༎ຶ`)ノ")
- end
- end
-
- context 'when trace artifact is in ObjectStorage' do
- let(:url) { 'http://object-storage/trace' }
- let(:file_path) { expand_fixture_path('trace/sample_trace') }
- let!(:job) { create(:ci_build, :success, :trace_artifact, pipeline: pipeline) }
-
- before do
- allow_any_instance_of(JobArtifactUploader).to receive(:file_storage?) { false }
- allow_any_instance_of(JobArtifactUploader).to receive(:url) { url }
- allow_any_instance_of(JobArtifactUploader).to receive(:size) { File.size(file_path) }
- end
-
- context 'when there are no network issues' do
- before do
- stub_remote_url_206(url, file_path)
-
- get_trace
- end
-
- it 'returns a trace' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['id']).to eq job.id
- expect(json_response['status']).to eq job.status
- expect(json_response['html']).to eq(job.trace.html)
- end
- end
-
- context 'when there is a network issue' do
- before do
- stub_remote_url_500(url)
- end
-
- it 'returns a trace' do
- expect { get_trace }.to raise_error(Gitlab::HttpIO::FailedToGetChunkError)
- end
- end
- end
-
- def get_trace
- get :trace,
- params: {
- namespace_id: project.namespace,
- project_id: project,
- id: job.id
- },
- format: :json
- end
- end
-
describe 'GET status.json' do
let(:job) { create(:ci_build, pipeline: pipeline) }
let(:status) { job.detailed_status(double('user')) }
diff --git a/spec/controllers/projects/logs_controller_spec.rb b/spec/controllers/projects/logs_controller_spec.rb
index 1eb5a6fcc12..0f34e536064 100644
--- a/spec/controllers/projects/logs_controller_spec.rb
+++ b/spec/controllers/projects/logs_controller_spec.rb
@@ -104,6 +104,34 @@ RSpec.describe Projects::LogsController do
expect(response.headers['Poll-Interval']).to eq('3000')
end
+ context 'with gitlab managed apps logs' do
+ it 'uses cluster finder services to select cluster', :aggregate_failures do
+ cluster_list = [cluster]
+ service_params = { params: ActionController::Parameters.new(pod_name: pod_name).permit! }
+ request_params = {
+ namespace_id: project.namespace,
+ project_id: project,
+ cluster_id: cluster.id,
+ pod_name: pod_name,
+ format: :json
+ }
+
+ expect_next_instance_of(ClusterAncestorsFinder, project, user) do |finder|
+ expect(finder).to receive(:execute).and_return(cluster_list)
+ expect(cluster_list).to receive(:find).and_call_original
+ end
+
+ expect_next_instance_of(service, cluster, Gitlab::Kubernetes::Helm::NAMESPACE, service_params) do |instance|
+ expect(instance).to receive(:execute).and_return(service_result)
+ end
+
+ get endpoint, params: request_params
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(json_response).to eq(service_result_json)
+ end
+ end
+
context 'when service is processing' do
let(:service_result) { nil }
diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
index 02b4c2d1da9..217447c2ad6 100644
--- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
@@ -91,6 +91,17 @@ RSpec.describe Projects::MergeRequests::DiffsController do
end
end
+ shared_examples "diff note on-demand position creation" do
+ it "updates diff discussion positions" do
+ service = double("service")
+
+ expect(Discussions::CaptureDiffNotePositionsService).to receive(:new).with(merge_request).and_return(service)
+ expect(service).to receive(:execute)
+
+ go
+ end
+ end
+
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
@@ -146,6 +157,7 @@ RSpec.describe Projects::MergeRequests::DiffsController do
it_behaves_like 'persisted preferred diff view cookie'
it_behaves_like 'cached diff collection'
+ it_behaves_like 'diff note on-demand position creation'
end
describe 'GET diffs_metadata' do
diff --git a/spec/controllers/projects/merge_requests/drafts_controller_spec.rb b/spec/controllers/projects/merge_requests/drafts_controller_spec.rb
index 7d74e872d29..af39d4dec72 100644
--- a/spec/controllers/projects/merge_requests/drafts_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/drafts_controller_spec.rb
@@ -331,8 +331,10 @@ RSpec.describe Projects::MergeRequests::DraftsController do
notes = merge_request.notes.reload
expect(notes.pluck(:note)).to include(*drafts.map(&:note))
- expect(note.discussion.notes.last.note).to eq(draft_reply.note)
- expect(diff_note.discussion.notes.last.note).to eq(diff_draft_reply.note)
+
+ # discussion is memoized and reload doesn't clear the memoization
+ expect(Note.find(note.id).discussion.notes.last.note).to eq(draft_reply.note)
+ expect(Note.find(diff_note.id).discussion.notes.last.note).to eq(diff_draft_reply.note)
end
it 'can publish just a single draft note' do
@@ -376,7 +378,8 @@ RSpec.describe Projects::MergeRequests::DraftsController do
post :publish, params: params
- discussion = note.discussion
+ # discussion is memoized and reload doesn't clear the memoization
+ discussion = Note.find(note.id).discussion
expect(discussion.notes.last.note).to eq(draft_reply.note)
expect(discussion.resolved?).to eq(false)
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 382593fd7cb..4327e0bbb7a 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -442,7 +442,7 @@ RSpec.describe Projects::MergeRequestsController do
merge_request.update(squash: false)
merge_with_sha(squash: '1')
- expect(merge_request.reload.squash).to be_truthy
+ expect(merge_request.reload.squash_on_merge?).to be_truthy
end
end
@@ -451,7 +451,7 @@ RSpec.describe Projects::MergeRequestsController do
merge_request.update(squash: true)
merge_with_sha(squash: '0')
- expect(merge_request.reload.squash).to be_falsey
+ expect(merge_request.reload.squash_on_merge?).to be_falsey
end
end
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index b3a83723189..9728fad417e 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -38,9 +38,9 @@ RSpec.describe Projects::NotesController do
end
it 'passes last_fetched_at from headers to NotesFinder and MergeIntoNotesService' do
- last_fetched_at = 3.hours.ago.to_i
+ last_fetched_at = Time.zone.at(3.hours.ago.to_i) # remove nanoseconds
- request.headers['X-Last-Fetched-At'] = last_fetched_at
+ request.headers['X-Last-Fetched-At'] = microseconds(last_fetched_at)
expect(NotesFinder).to receive(:new)
.with(anything, hash_including(last_fetched_at: last_fetched_at))
@@ -84,6 +84,81 @@ RSpec.describe Projects::NotesController do
end
end
+ context 'for multiple pages of notes', :aggregate_failures do
+ # 3 pages worth: 1 normal page, 1 oversized due to clashing updated_at,
+ # and a final, short page
+ let!(:page_1) { create_list(:note, 2, noteable: issue, project: project, updated_at: 3.days.ago) }
+ let!(:page_2) { create_list(:note, 3, noteable: issue, project: project, updated_at: 2.days.ago) }
+ let!(:page_3) { create_list(:note, 2, noteable: issue, project: project, updated_at: 1.day.ago) }
+
+ # Include a resource event in the middle page as well
+ let!(:resource_event) { create(:resource_state_event, issue: issue, user: user, created_at: 2.days.ago) }
+
+ let(:page_1_boundary) { microseconds(page_1.last.updated_at + NotesFinder::FETCH_OVERLAP) }
+ let(:page_2_boundary) { microseconds(page_2.last.updated_at + NotesFinder::FETCH_OVERLAP) }
+
+ around do |example|
+ Timecop.freeze do
+ example.run
+ end
+ end
+
+ before do
+ stub_const('Gitlab::UpdatedNotesPaginator::LIMIT', 2)
+ end
+
+ context 'feature flag enabled' do
+ before do
+ stub_feature_flags(paginated_notes: true)
+ end
+
+ it 'returns the first page of notes' do
+ get :index, params: request_params
+
+ expect(json_response['notes'].count).to eq(page_1.count)
+ expect(json_response['more']).to be_truthy
+ expect(json_response['last_fetched_at']).to eq(page_1_boundary)
+ expect(response.headers['Poll-Interval'].to_i).to eq(1)
+ end
+
+ it 'returns the second page of notes' do
+ request.headers['X-Last-Fetched-At'] = page_1_boundary
+
+ get :index, params: request_params
+
+ expect(json_response['notes'].count).to eq(page_2.count + 1) # resource event
+ expect(json_response['more']).to be_truthy
+ expect(json_response['last_fetched_at']).to eq(page_2_boundary)
+ expect(response.headers['Poll-Interval'].to_i).to eq(1)
+ end
+
+ it 'returns the final page of notes' do
+ request.headers['X-Last-Fetched-At'] = page_2_boundary
+
+ get :index, params: request_params
+
+ expect(json_response['notes'].count).to eq(page_3.count)
+ expect(json_response['more']).to be_falsy
+ expect(json_response['last_fetched_at']).to eq(microseconds(Time.zone.now))
+ expect(response.headers['Poll-Interval'].to_i).to be > 1
+ end
+ end
+
+ context 'feature flag disabled' do
+ before do
+ stub_feature_flags(paginated_notes: false)
+ end
+
+ it 'returns all notes' do
+ get :index, params: request_params
+
+ expect(json_response['notes'].count).to eq((page_1 + page_2 + page_3).size + 1)
+ expect(json_response['more']).to be_falsy
+ expect(json_response['last_fetched_at']).to eq(microseconds(Time.zone.now))
+ end
+ end
+ end
+
context 'for a discussion note' do
let(:project) { create(:project, :repository) }
let!(:note) { create(:discussion_note_on_merge_request, project: project) }
@@ -870,4 +945,9 @@ RSpec.describe Projects::NotesController do
end
end
end
+
+ # Convert a time to an integer number of microseconds
+ def microseconds(time)
+ (time.to_i * 1_000_000) + time.usec
+ end
end
diff --git a/spec/controllers/projects/stages_controller_spec.rb b/spec/controllers/projects/pipelines/stages_controller_spec.rb
index dcf8607ae18..6e8c08d95a1 100644
--- a/spec/controllers/projects/stages_controller_spec.rb
+++ b/spec/controllers/projects/pipelines/stages_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Projects::StagesController do
+RSpec.describe Projects::Pipelines::StagesController do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
@@ -60,7 +60,7 @@ RSpec.describe Projects::StagesController do
post :play_manual, params: {
namespace_id: project.namespace,
project_id: project,
- id: pipeline.id,
+ pipeline_id: pipeline.id,
stage_name: stage_name
}, format: :json
end
diff --git a/spec/controllers/projects/pipelines/tests_controller_spec.rb b/spec/controllers/projects/pipelines/tests_controller_spec.rb
new file mode 100644
index 00000000000..e2abd1238c5
--- /dev/null
+++ b/spec/controllers/projects/pipelines/tests_controller_spec.rb
@@ -0,0 +1,112 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::Pipelines::TestsController do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public, :repository) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ before do
+ sign_in(user)
+ end
+
+ describe 'GET #summary.json' do
+ context 'when pipeline has build report results' do
+ let(:pipeline) { create(:ci_pipeline, :with_report_results, project: project) }
+
+ it 'renders test report summary data' do
+ get_tests_summary_json
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['total_count']).to eq(2)
+ end
+ end
+
+ context 'when pipeline does not have build report results' do
+ it 'renders test report summary data' do
+ get_tests_summary_json
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['total_count']).to eq(0)
+ end
+ end
+
+ context 'when feature is disabled' do
+ before do
+ stub_feature_flags(build_report_summary: false)
+ end
+
+ it 'returns 404' do
+ get_tests_summary_json
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(response.body).to be_empty
+ end
+ end
+ end
+
+ describe 'GET #show.json' do
+ context 'when pipeline has build report results' do
+ let(:pipeline) { create(:ci_pipeline, :with_report_results, project: project) }
+ let(:suite_name) { 'test' }
+ let(:build_ids) { pipeline.latest_builds.pluck(:id) }
+
+ it 'renders test suite data' do
+ get_tests_show_json(build_ids)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['name']).to eq('test')
+ end
+ end
+
+ context 'when pipeline does not have build report results' do
+ let(:pipeline) { create(:ci_empty_pipeline) }
+ let(:suite_name) { 'test' }
+
+ it 'renders 404' do
+ get_tests_show_json([])
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(response.body).to be_empty
+ end
+ end
+
+ context 'when feature is disabled' do
+ let(:suite_name) { 'test' }
+
+ before do
+ stub_feature_flags(build_report_summary: false)
+ end
+
+ it 'returns 404' do
+ get_tests_show_json([])
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(response.body).to be_empty
+ end
+ end
+ end
+
+ def get_tests_summary_json
+ get :summary,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ pipeline_id: pipeline.id
+ },
+ format: :json
+ end
+
+ def get_tests_show_json(build_ids)
+ get :show,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ pipeline_id: pipeline.id,
+ suite_name: suite_name,
+ build_ids: build_ids
+ },
+ format: :json
+ end
+end
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index ca09d2b1428..872f0e97b09 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -37,16 +37,13 @@ RSpec.describe Projects::PipelinesController do
expect(json_response).to include('pipelines')
expect(json_response['pipelines'].count).to eq 6
expect(json_response['count']['all']).to eq '6'
- expect(json_response['count']['running']).to eq '2'
- expect(json_response['count']['pending']).to eq '1'
- expect(json_response['count']['finished']).to eq '3'
json_response.dig('pipelines', 0, 'details', 'stages').tap do |stages|
expect(stages.count).to eq 3
end
end
- it 'does not execute N+1 queries' do
+ it 'executes N+1 queries' do
get_pipelines_index_json
control_count = ActiveRecord::QueryRecorder.new do
@@ -56,10 +53,31 @@ RSpec.describe Projects::PipelinesController do
create_all_pipeline_types
# There appears to be one extra query for Pipelines#has_warnings? for some reason
- expect { get_pipelines_index_json }.not_to exceed_query_limit(control_count + 1)
+ expect { get_pipelines_index_json }.not_to exceed_query_limit(control_count + 7)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['pipelines'].count).to eq 12
end
+
+ context 'with build_report_summary turned off' do
+ before do
+ stub_feature_flags(build_report_summary: false)
+ end
+
+ it 'does not execute N+1 queries' do
+ get_pipelines_index_json
+
+ control_count = ActiveRecord::QueryRecorder.new do
+ get_pipelines_index_json
+ end.count
+
+ create_all_pipeline_types
+
+ # There appears to be one extra query for Pipelines#has_warnings? for some reason
+ expect { get_pipelines_index_json }.not_to exceed_query_limit(control_count + 1)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['pipelines'].count).to eq 12
+ end
+ end
end
it 'does not include coverage data for the pipelines' do
@@ -77,9 +95,9 @@ RSpec.describe Projects::PipelinesController do
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
- # ListCommitsByOid, RepositoryExists, HasLocalBranches
+ # ListCommitsByOid, RepositoryExists, HasLocalBranches, ListCommitsByRefNames
expect { get_pipelines_index_json }
- .to change { Gitlab::GitalyClient.get_request_count }.by(3)
+ .to change { Gitlab::GitalyClient.get_request_count }.by(4)
end
end
@@ -101,23 +119,27 @@ RSpec.describe Projects::PipelinesController do
end
end
- context 'filter by scope' do
- it 'returns matched pipelines' do
- get_pipelines_index_json(scope: 'running')
+ context 'when user tries to access legacy scope via URL' do
+ it 'redirects to all pipelines with that status instead' do
+ get_pipelines_index_html(scope: 'running')
- check_pipeline_response(returned: 2, all: 6, running: 2, pending: 1, finished: 3)
+ expect(response).to redirect_to(project_pipelines_path(project, status: 'running', format: :html))
end
+ end
+ context 'filter by scope' do
context 'scope is branches or tags' do
before do
create(:ci_pipeline, :failed, project: project, ref: 'v1.0.0', tag: true)
+ create(:ci_pipeline, :failed, project: project, ref: 'master', tag: false)
+ create(:ci_pipeline, :failed, project: project, ref: 'feature', tag: false)
end
context 'when scope is branches' do
it 'returns matched pipelines' do
get_pipelines_index_json(scope: 'branches')
- check_pipeline_response(returned: 1, all: 7, running: 2, pending: 1, finished: 4)
+ check_pipeline_response(returned: 2, all: 9)
end
end
@@ -125,7 +147,7 @@ RSpec.describe Projects::PipelinesController do
it 'returns matched pipelines' do
get_pipelines_index_json(scope: 'tags')
- check_pipeline_response(returned: 1, all: 7, running: 2, pending: 1, finished: 4)
+ check_pipeline_response(returned: 1, all: 9)
end
end
end
@@ -138,7 +160,7 @@ RSpec.describe Projects::PipelinesController do
it 'returns matched pipelines' do
get_pipelines_index_json(username: user.username)
- check_pipeline_response(returned: 1, all: 1, running: 1, pending: 0, finished: 0)
+ check_pipeline_response(returned: 1, all: 1)
end
end
@@ -146,7 +168,7 @@ RSpec.describe Projects::PipelinesController do
it 'returns empty' do
get_pipelines_index_json(username: 'invalid-username')
- check_pipeline_response(returned: 0, all: 0, running: 0, pending: 0, finished: 0)
+ check_pipeline_response(returned: 0, all: 0)
end
end
end
@@ -158,7 +180,7 @@ RSpec.describe Projects::PipelinesController do
it 'returns matched pipelines' do
get_pipelines_index_json(ref: 'branch-1')
- check_pipeline_response(returned: 1, all: 1, running: 1, pending: 0, finished: 0)
+ check_pipeline_response(returned: 1, all: 1)
end
end
@@ -166,7 +188,7 @@ RSpec.describe Projects::PipelinesController do
it 'returns empty list' do
get_pipelines_index_json(ref: 'invalid-ref')
- check_pipeline_response(returned: 0, all: 0, running: 0, pending: 0, finished: 0)
+ check_pipeline_response(returned: 0, all: 0)
end
end
end
@@ -176,15 +198,7 @@ RSpec.describe Projects::PipelinesController do
it 'returns matched pipelines' do
get_pipelines_index_json(status: 'success')
- check_pipeline_response(returned: 1, all: 1, running: 0, pending: 0, finished: 1)
- end
-
- context 'when filter by unrelated scope' do
- it 'returns empty list' do
- get_pipelines_index_json(status: 'success', scope: 'running')
-
- check_pipeline_response(returned: 0, all: 1, running: 0, pending: 0, finished: 1)
- end
+ check_pipeline_response(returned: 1, all: 1)
end
end
@@ -192,7 +206,7 @@ RSpec.describe Projects::PipelinesController do
it 'returns empty list' do
get_pipelines_index_json(status: 'manual')
- check_pipeline_response(returned: 0, all: 0, running: 0, pending: 0, finished: 0)
+ check_pipeline_response(returned: 0, all: 0)
end
end
@@ -200,11 +214,19 @@ RSpec.describe Projects::PipelinesController do
it 'returns all list' do
get_pipelines_index_json(status: 'invalid-status')
- check_pipeline_response(returned: 6, all: 6, running: 2, pending: 1, finished: 3)
+ check_pipeline_response(returned: 6, all: 6)
end
end
end
+ def get_pipelines_index_html(params = {})
+ get :index, params: {
+ namespace_id: project.namespace,
+ project_id: project
+ }.merge(params),
+ format: :html
+ end
+
def get_pipelines_index_json(params = {})
get :index, params: {
namespace_id: project.namespace,
@@ -234,7 +256,8 @@ RSpec.describe Projects::PipelinesController do
user = create(:user)
pipeline = create(:ci_empty_pipeline, status: status,
project: project,
- sha: sha,
+ sha: sha.id,
+ ref: sha.id.first(8),
user: user,
merge_request: merge_request)
@@ -260,15 +283,12 @@ RSpec.describe Projects::PipelinesController do
)
end
- def check_pipeline_response(returned:, all:, running:, pending:, finished:)
+ def check_pipeline_response(returned:, all:)
aggregate_failures do
expect(response).to match_response_schema('pipeline')
expect(json_response['pipelines'].count).to eq returned
expect(json_response['count']['all'].to_i).to eq all
- expect(json_response['count']['running'].to_i).to eq running
- expect(json_response['count']['pending'].to_i).to eq pending
- expect(json_response['count']['finished'].to_i).to eq finished
end
end
end
@@ -689,6 +709,15 @@ RSpec.describe Projects::PipelinesController do
end
end
+ describe 'GET #charts' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ it_behaves_like 'tracking unique visits', :charts do
+ let(:request_params) { { namespace_id: project.namespace, project_id: project, id: pipeline.id } }
+ let(:target_id) { 'p_analytics_pipelines' }
+ end
+ end
+
describe 'POST create' do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb
index 7457e4c5023..40a220d57a7 100644
--- a/spec/controllers/projects/project_members_controller_spec.rb
+++ b/spec/controllers/projects/project_members_controller_spec.rb
@@ -106,6 +106,29 @@ RSpec.describe Projects::ProjectMembersController do
expect(response).to redirect_to(project_project_members_path(project))
end
end
+
+ context 'adding project bot' do
+ let_it_be(:project_bot) { create(:user, :project_bot) }
+
+ before do
+ project.add_maintainer(user)
+
+ unrelated_project = create(:project)
+ unrelated_project.add_maintainer(project_bot)
+ end
+
+ it 'returns error' do
+ post :create, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ user_ids: project_bot.id,
+ access_level: Gitlab::Access::GUEST
+ }
+
+ expect(flash[:alert]).to include('project bots cannot be added to other groups / projects')
+ expect(response).to redirect_to(project_project_members_path(project))
+ end
+ end
end
describe 'PUT update' do
diff --git a/spec/controllers/projects/refs_controller_spec.rb b/spec/controllers/projects/refs_controller_spec.rb
index a6a4aff7ce9..d10351feb9e 100644
--- a/spec/controllers/projects/refs_controller_spec.rb
+++ b/spec/controllers/projects/refs_controller_spec.rb
@@ -41,19 +41,12 @@ RSpec.describe Projects::RefsController do
expect { xhr_get }.not_to raise_error
end
- it 'renders 404 for non-JS requests' do
+ it 'renders 404 for HTML requests' do
xhr_get
expect(response).to be_not_found
end
- it 'renders JS' do
- expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
-
- xhr_get(:js)
- expect(response).to be_successful
- end
-
context 'when json is requested' do
it 'renders JSON' do
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb
index 96c38c1b726..45beccfeef5 100644
--- a/spec/controllers/projects/releases_controller_spec.rb
+++ b/spec/controllers/projects/releases_controller_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Projects::ReleasesController do
+ include AccessMatchersForController
+
let!(:project) { create(:project, :repository, :public) }
let_it_be(:private_project) { create(:project, :repository, :private) }
let_it_be(:developer) { create(:user) }
@@ -118,6 +120,15 @@ RSpec.describe Projects::ReleasesController do
end
end
+ describe 'GET #new' do
+ let(:request) do
+ get :new, params: { namespace_id: project.namespace, project_id: project }
+ end
+
+ it { expect { request }.to be_denied_for(:reporter).of(project) }
+ it { expect { request }.to be_allowed_for(:developer).of(project) }
+ end
+
describe 'GET #edit' do
subject do
get :edit, params: { namespace_id: project.namespace, project_id: project, tag: tag }
diff --git a/spec/controllers/projects/service_desk_controller_spec.rb b/spec/controllers/projects/service_desk_controller_spec.rb
new file mode 100644
index 00000000000..1c4d6665414
--- /dev/null
+++ b/spec/controllers/projects/service_desk_controller_spec.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::ServiceDeskController do
+ let_it_be(:project) do
+ create(:project, :private, :custom_repo, service_desk_enabled: true,
+ files: { '.gitlab/issue_templates/service_desk.md' => 'template' })
+ end
+
+ let_it_be(:user) { create(:user) }
+
+ before do
+ allow(Gitlab::IncomingEmail).to receive(:enabled?) { true }
+ allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
+
+ project.add_maintainer(user)
+ sign_in(user)
+ end
+
+ describe 'GET service desk properties' do
+ it 'returns service_desk JSON data' do
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }, format: :json
+
+ expect(json_response["service_desk_address"]).to match(/\A[^@]+@[^@]+\z/)
+ expect(json_response["service_desk_enabled"]).to be_truthy
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ context 'when user is not project maintainer' do
+ let(:guest) { create(:user) }
+
+ it 'renders 404' do
+ project.add_guest(guest)
+ sign_in(guest)
+
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }, format: :json
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when issue template is present' do
+ it 'returns template_file_missing as false' do
+ create(:service_desk_setting, project: project, issue_template_key: 'service_desk')
+
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }, format: :json
+
+ response_hash = Gitlab::Json.parse(response.body)
+ expect(response_hash['template_file_missing']).to eq(false)
+ end
+ end
+
+ context 'when issue template file becomes outdated' do
+ it 'returns template_file_missing as true' do
+ service = ServiceDeskSetting.new(project_id: project.id, issue_template_key: 'deleted')
+ service.save!(validate: false)
+
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }, format: :json
+
+ expect(json_response['template_file_missing']).to eq(true)
+ end
+ end
+ end
+
+ describe 'PUT service desk properties' do
+ it 'toggles services desk incoming email' do
+ project.update!(service_desk_enabled: false)
+
+ put :update, params: { namespace_id: project.namespace.to_param,
+ project_id: project,
+ service_desk_enabled: true }, format: :json
+
+ expect(json_response["service_desk_address"]).to be_present
+ expect(json_response["service_desk_enabled"]).to be_truthy
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'sets issue_template_key' do
+ put :update, params: { namespace_id: project.namespace.to_param,
+ project_id: project,
+ issue_template_key: 'service_desk' }, format: :json
+
+ settings = project.service_desk_setting
+ expect(settings).to be_present
+ expect(settings.issue_template_key).to eq('service_desk')
+ expect(json_response['template_file_missing']).to eq(false)
+ expect(json_response['issue_template_key']).to eq('service_desk')
+ end
+
+ it 'returns an error when update of service desk settings fails' do
+ put :update, params: { namespace_id: project.namespace.to_param,
+ project_id: project,
+ issue_template_key: 'invalid key' }, format: :json
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ expect(json_response['message']).to eq('Issue template key is empty or does not exist')
+ end
+
+ context 'when user cannot admin the project' do
+ let(:other_user) { create(:user) }
+
+ it 'renders 404' do
+ sign_in(other_user)
+ put :update, params: { namespace_id: project.namespace.to_param, project_id: project, service_desk_enabled: true }, format: :json
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index 04c74dfdefe..e8a23dcfafb 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -137,7 +137,7 @@ RSpec.describe Projects::ServicesController do
let(:params) { project_params(service: service_params) }
let(:message) { 'Jira activated.' }
- let(:redirect_url) { project_settings_integrations_path(project) }
+ let(:redirect_url) { edit_project_service_path(project, service) }
before do
put :update, params: params
@@ -179,6 +179,23 @@ RSpec.describe Projects::ServicesController do
it_behaves_like 'service update'
end
+
+ context 'wehn param `inherit_from_id` is set to empty string' do
+ let(:service_params) { { inherit_from_id: '' } }
+
+ it 'sets inherit_from_id to nil' do
+ expect(service.reload.inherit_from_id).to eq(nil)
+ end
+ end
+
+ context 'wehn param `inherit_from_id` is set to some value' do
+ let(:instance_service) { create(:jira_service, :instance) }
+ let(:service_params) { { inherit_from_id: instance_service.id } }
+
+ it 'sets inherit_from_id to value' do
+ expect(service.reload.inherit_from_id).to eq(instance_service.id)
+ end
+ end
end
describe 'as JSON' do
diff --git a/spec/controllers/projects/settings/operations_controller_spec.rb b/spec/controllers/projects/settings/operations_controller_spec.rb
index 6b440e910ad..d4f3c5d0c9b 100644
--- a/spec/controllers/projects/settings/operations_controller_spec.rb
+++ b/spec/controllers/projects/settings/operations_controller_spec.rb
@@ -151,7 +151,8 @@ RSpec.describe Projects::Settings::OperationsController do
incident_management_setting_attributes: {
create_issue: 'false',
send_email: 'false',
- issue_template_key: 'some-other-template'
+ issue_template_key: 'some-other-template',
+ pagerduty_active: 'true'
}
}
end
@@ -159,7 +160,6 @@ RSpec.describe Projects::Settings::OperationsController do
it_behaves_like 'PATCHable'
context 'updating each incident management setting' do
- let(:project) { create(:project) }
let(:new_incident_management_settings) { {} }
before do
@@ -185,6 +185,98 @@ RSpec.describe Projects::Settings::OperationsController do
it_behaves_like 'a gitlab tracking event', { issue_template_key: nil }, 'disabled_issue_template_on_alerts'
it_behaves_like 'a gitlab tracking event', { send_email: '1' }, 'enabled_sending_emails'
it_behaves_like 'a gitlab tracking event', { send_email: '0' }, 'disabled_sending_emails'
+ it_behaves_like 'a gitlab tracking event', { pagerduty_active: '1' }, 'enabled_pagerduty_webhook'
+ it_behaves_like 'a gitlab tracking event', { pagerduty_active: '0' }, 'disabled_pagerduty_webhook'
+ end
+ end
+
+ describe 'POST #reset_pagerduty_token' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ context 'with existing incident management setting has active PagerDuty webhook' do
+ let!(:incident_management_setting) do
+ create(:project_incident_management_setting, project: project, pagerduty_active: true)
+ end
+
+ let!(:old_token) { incident_management_setting.pagerduty_token }
+
+ it 'returns newly reset token' do
+ reset_pagerduty_token
+
+ new_token = incident_management_setting.reload.pagerduty_token
+ new_webhook_url = project_incidents_pagerduty_url(project, token: new_token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['pagerduty_webhook_url']).to eq(new_webhook_url)
+ expect(json_response['pagerduty_token']).to eq(new_token)
+ expect(old_token).not_to eq(new_token)
+ end
+ end
+
+ context 'without existing incident management setting' do
+ it 'does not reset a token' do
+ reset_pagerduty_token
+
+ new_webhook_url = project_incidents_pagerduty_url(project, token: nil)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['pagerduty_webhook_url']).to eq(new_webhook_url)
+ expect(project.incident_management_setting.pagerduty_token).to be_nil
+ end
+ end
+
+ context 'when update fails' do
+ let(:operations_update_service) { spy(:operations_update_service) }
+ let(:pagerduty_token_params) do
+ { incident_management_setting_attributes: { regenerate_token: true } }
+ end
+
+ before do
+ expect(::Projects::Operations::UpdateService)
+ .to receive(:new).with(project, user, pagerduty_token_params)
+ .and_return(operations_update_service)
+ expect(operations_update_service).to receive(:execute)
+ .and_return(status: :error)
+ end
+
+ it 'returns unprocessable_entity' do
+ reset_pagerduty_token
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ expect(json_response).to be_empty
+ end
+ end
+
+ context 'with insufficient permissions' do
+ before do
+ project.add_reporter(user)
+ end
+
+ it 'returns 404' do
+ reset_pagerduty_token
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'as an anonymous user' do
+ before do
+ sign_out(user)
+ end
+
+ it 'returns a redirect' do
+ reset_pagerduty_token
+
+ expect(response).to have_gitlab_http_status(:redirect)
+ end
+ end
+
+ private
+
+ def reset_pagerduty_token
+ post :reset_pagerduty_token, params: project_params(project), format: :json
end
end
end
@@ -296,9 +388,7 @@ RSpec.describe Projects::Settings::OperationsController do
end
end
- describe 'POST reset_alerting_token' do
- let(:project) { create(:project) }
-
+ describe 'POST #reset_alerting_token' do
before do
project.add_maintainer(user)
end
diff --git a/spec/controllers/projects/snippets/blobs_controller_spec.rb b/spec/controllers/projects/snippets/blobs_controller_spec.rb
new file mode 100644
index 00000000000..ca656705e07
--- /dev/null
+++ b/spec/controllers/projects/snippets/blobs_controller_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::Snippets::BlobsController do
+ using RSpec::Parameterized::TableSyntax
+ include SnippetHelpers
+
+ let_it_be(:author) { create(:user) }
+ let_it_be(:developer) { create(:user) }
+ let_it_be(:other_user) { create(:user) }
+
+ let(:visibility) { :public }
+ let(:project_visibility) { :public }
+ let(:project) { create(:project, project_visibility) }
+ let(:snippet) { create(:project_snippet, visibility, :repository, project: project, author: author) }
+
+ before do
+ project.add_maintainer(author)
+ project.add_developer(developer)
+ end
+
+ describe 'GET #raw' do
+ let(:filepath) { 'file1' }
+ let(:ref) { TestEnv::BRANCH_SHA['snippet/single-file'] }
+ let(:inline) { nil }
+
+ subject do
+ get(:raw,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ snippet_id: snippet,
+ path: filepath,
+ ref: ref,
+ inline: inline
+ })
+ end
+
+ context 'with a snippet without a repository' do
+ let(:snippet) { create(:project_snippet, visibility, project: project, author: author) }
+
+ it_behaves_like 'raw snippet without repository', :not_found
+ end
+
+ where(:project_visibility_level, :snippet_visibility_level, :user, :status) do
+ :public | :public | :author | :ok
+ :public | :public | :developer | :ok
+ :public | :public | :other_user | :ok
+ :public | :public | nil | :ok
+
+ :public | :private | :author | :ok
+ :public | :private | :developer | :ok
+ :public | :private | :other_user | :not_found
+ :public | :private | nil | :not_found
+
+ :private | :public | :author | :ok
+ :private | :public | :developer | :ok
+ :private | :public | :other_user | :not_found
+ :private | :public | nil | :redirect
+
+ :private | :private | :author | :ok
+ :private | :private | :developer | :ok
+ :private | :private | :other_user | :not_found
+ :private | :private | nil | :redirect
+ end
+
+ with_them do
+ let(:visibility) { snippet_visibility_level }
+ let(:project_visibility) { project_visibility_level }
+
+ before do
+ sign_in_as(user)
+
+ subject
+ end
+
+ it 'responds with correct status' do
+ expect(response).to have_gitlab_http_status(status)
+ end
+ end
+
+ it_behaves_like 'raw snippet blob'
+ end
+end
diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb
index 8bbfaa8d327..6fcb24da3cd 100644
--- a/spec/controllers/projects/snippets_controller_spec.rb
+++ b/spec/controllers/projects/snippets_controller_spec.rb
@@ -15,14 +15,18 @@ RSpec.describe Projects::SnippetsController do
end
describe 'GET #index' do
+ let(:base_params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project
+ }
+ end
+
+ subject { get :index, params: base_params }
+
it_behaves_like 'paginated collection' do
let(:collection) { project.snippets }
- let(:params) do
- {
- namespace_id: project.namespace,
- project_id: project
- }
- end
+ let(:params) { base_params }
before do
create(:project_snippet, :public, project: project, author: user)
@@ -35,7 +39,11 @@ RSpec.describe Projects::SnippetsController do
.to receive(:new).with(nil, project: project)
.and_return(service)
- get :index, params: { namespace_id: project.namespace, project_id: project }
+ subject
+ end
+
+ it_behaves_like 'snippets sort order' do
+ let(:params) { base_params }
end
context 'when the project snippet is private' do
@@ -43,7 +51,7 @@ RSpec.describe Projects::SnippetsController do
context 'when anonymous' do
it 'does not include the private snippet' do
- get :index, params: { namespace_id: project.namespace, project_id: project }
+ subject
expect(assigns(:snippets)).not_to include(project_snippet)
expect(response).to have_gitlab_http_status(:ok)
@@ -56,7 +64,7 @@ RSpec.describe Projects::SnippetsController do
end
it 'renders the snippet' do
- get :index, params: { namespace_id: project.namespace, project_id: project }
+ subject
expect(assigns(:snippets)).to include(project_snippet)
expect(response).to have_gitlab_http_status(:ok)
@@ -69,7 +77,7 @@ RSpec.describe Projects::SnippetsController do
end
it 'renders the snippet' do
- get :index, params: { namespace_id: project.namespace, project_id: project }
+ subject
expect(assigns(:snippets)).to include(project_snippet)
expect(response).to have_gitlab_http_status(:ok)
diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb
index f6ec04d4dd7..8e4e275bdbe 100644
--- a/spec/controllers/projects/tree_controller_spec.rb
+++ b/spec/controllers/projects/tree_controller_spec.rb
@@ -89,34 +89,6 @@ RSpec.describe Projects::TreeController do
end
end
- describe "GET show" do
- context 'lfs_blob_ids instance variable' do
- let(:id) { 'master' }
-
- context 'with vue tree view enabled' do
- before do
- get(:show, params: { namespace_id: project.namespace.to_param, project_id: project, id: id })
- end
-
- it 'is not set' do
- expect(assigns[:lfs_blob_ids]).to be_nil
- end
- end
-
- context 'with vue tree view disabled' do
- before do
- stub_feature_flags(vue_file_list: false)
-
- get(:show, params: { namespace_id: project.namespace.to_param, project_id: project, id: id })
- end
-
- it 'is set' do
- expect(assigns[:lfs_blob_ids]).not_to be_nil
- end
- end
- end
- end
-
describe 'GET show with whitespace in ref' do
render_views
diff --git a/spec/controllers/projects/wikis_controller_spec.rb b/spec/controllers/projects/wikis_controller_spec.rb
index 4e58822b613..7243588681d 100644
--- a/spec/controllers/projects/wikis_controller_spec.rb
+++ b/spec/controllers/projects/wikis_controller_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe Projects::WikisController do
it_behaves_like 'wiki controller actions' do
- let(:container) { create(:project, :public, :repository, namespace: user.namespace) }
+ let(:container) { create(:project, :public, namespace: user.namespace) }
let(:routing_params) { { namespace_id: container.namespace, project_id: container } }
end
end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 8aae9ef85be..e59493827ba 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe ProjectsController do
include ExternalAuthorizationServiceHelpers
include ProjectForksHelper
- let(:project) { create(:project) }
+ let(:project) { create(:project, service_desk_enabled: false) }
let(:public_project) { create(:project, :public) }
let(:user) { create(:user) }
let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') }
@@ -86,11 +86,13 @@ RSpec.describe ProjectsController do
end
describe "GET #activity as JSON" do
+ include DesignManagementTestHelpers
render_views
let(:project) { create(:project, :public, issues_access_level: ProjectFeature::PRIVATE) }
before do
+ enable_design_management
create(:event, :created, project: project, target: create(:issue))
sign_in(user)
@@ -103,11 +105,32 @@ RSpec.describe ProjectsController do
project.add_developer(user)
end
- it 'returns count' do
+ def get_activity(project)
get :activity, params: { namespace_id: project.namespace, id: project, format: :json }
+ end
+
+ it 'returns count' do
+ get_activity(project)
expect(json_response['count']).to eq(1)
end
+
+ context 'design events are visible' do
+ include DesignManagementTestHelpers
+ let(:other_project) { create(:project, namespace: user.namespace) }
+
+ before do
+ enable_design_management
+ create(:design_event, project: project)
+ request.cookies[:event_filter] = EventFilter::DESIGNS
+ end
+
+ it 'returns correct count' do
+ get_activity(project)
+
+ expect(json_response['count']).to eq(1)
+ end
+ end
end
context 'when user has no permission to see the event' do
@@ -350,45 +373,6 @@ RSpec.describe ProjectsController do
.not_to exceed_query_limit(2).for_query(expected_query)
end
end
-
- context 'lfs_blob_ids instance variable' do
- let(:project) { create(:project, :public, :repository) }
-
- before do
- sign_in(user)
- end
-
- context 'with vue tree view enabled' do
- before do
- get :show, params: { namespace_id: project.namespace, id: project }
- end
-
- it 'is not set' do
- expect(assigns[:lfs_blob_ids]).to be_nil
- end
- end
-
- context 'with vue tree view disabled' do
- before do
- stub_feature_flags(vue_file_list: false)
-
- get :show, params: { namespace_id: project.namespace, id: project }
- end
-
- it 'is set' do
- expect(assigns[:lfs_blob_ids]).not_to be_nil
- end
- end
- end
-
- context 'namespace storage limit' do
- let_it_be(:project) { create(:project, :public, :repository ) }
- let(:namespace) { project.namespace }
-
- subject { get :show, params: { namespace_id: namespace, id: project } }
-
- it_behaves_like 'namespace storage limit alert'
- end
end
describe 'GET edit' do
@@ -1192,7 +1176,7 @@ RSpec.describe ProjectsController do
before do
allow(Gitlab::ApplicationRateLimiter)
.to receive(:increment)
- .and_return(Gitlab::ApplicationRateLimiter.rate_limits["project_#{action}".to_sym][:threshold] + 1)
+ .and_return(Gitlab::ApplicationRateLimiter.rate_limits["project_#{action}".to_sym][:threshold].call + 1)
end
it 'prevents requesting project export' do
@@ -1259,7 +1243,7 @@ RSpec.describe ProjectsController do
before do
allow(Gitlab::ApplicationRateLimiter)
.to receive(:increment)
- .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:project_download_export][:threshold] + 1)
+ .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:project_download_export][:threshold].call + 1)
end
it 'prevents requesting project export' do
@@ -1400,6 +1384,27 @@ RSpec.describe ProjectsController do
end
end
+ it 'updates Service Desk attributes' do
+ project.add_maintainer(user)
+ sign_in(user)
+ allow(Gitlab::IncomingEmail).to receive(:enabled?) { true }
+ allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
+ params = {
+ service_desk_enabled: true
+ }
+
+ put :update,
+ params: {
+ namespace_id: project.namespace,
+ id: project,
+ project: params
+ }
+ project.reload
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(project.service_desk_enabled).to eq(true)
+ 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/registrations/experience_levels_controller_spec.rb b/spec/controllers/registrations/experience_levels_controller_spec.rb
index 1fc728f5de8..5a217a3a684 100644
--- a/spec/controllers/registrations/experience_levels_controller_spec.rb
+++ b/spec/controllers/registrations/experience_levels_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Registrations::ExperienceLevelsController do
+RSpec.describe Registrations::ExperienceLevelsController do
let_it_be(:namespace) { create(:group, path: 'group-path' ) }
let_it_be(:user) { create(:user) }
@@ -99,34 +99,51 @@ describe Registrations::ExperienceLevelsController do
end
describe 'applying the chosen level' do
- context "when an 'onboarding_issues_settings' cookie does not exist" do
- let(:params) { super().merge(experience_level: :novice) }
-
- it 'does not change the cookie' do
- expect { subject }.not_to change { cookies[:onboarding_issues_settings] }
- end
- end
-
- context "when an 'onboarding_issues_settings' cookie does exist" do
+ context 'when a "Learn GitLab" project is available' do
before do
- request.cookies[:onboarding_issues_settings] = '{}'
+ allow_next_instance_of(LearnGitlab) do |learn_gitlab|
+ allow(learn_gitlab).to receive(:available?).and_return(true)
+ allow(learn_gitlab).to receive(:label).and_return(double(id: 1))
+ end
end
context 'when novice' do
let(:params) { super().merge(experience_level: :novice) }
- it "adds a 'hideAdvanced' setting to the cookie" do
- expect { subject }.to change { Gitlab::Json.parse(cookies[:onboarding_issues_settings])['hideAdvanced'] }.from(nil).to(true)
+ it 'adds a BoardLabel' do
+ expect_next_instance_of(Boards::UpdateService) do |service|
+ expect(service).to receive(:execute)
+ end
+
+ subject
end
end
context 'when experienced' do
let(:params) { super().merge(experience_level: :experienced) }
- it 'does not change the cookie' do
- expect { subject }.not_to change { cookies[:onboarding_issues_settings] }
+ it 'does not add a BoardLabel' do
+ expect(Boards::UpdateService).not_to receive(:new)
+
+ subject
+ end
+ end
+ end
+
+ context 'when no "Learn GitLab" project exists' do
+ let(:params) { super().merge(experience_level: :novice) }
+
+ before do
+ allow_next_instance_of(LearnGitlab) do |learn_gitlab|
+ allow(learn_gitlab).to receive(:available?).and_return(false)
end
end
+
+ it 'does not add a BoardLabel' do
+ expect(Boards::UpdateService).not_to receive(:new)
+
+ subject
+ end
end
end
end
diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb
index bae6bd07b67..0849fb00e73 100644
--- a/spec/controllers/search_controller_spec.rb
+++ b/spec/controllers/search_controller_spec.rb
@@ -211,4 +211,9 @@ RSpec.describe SearchController do
end.to raise_error(ActionController::ParameterMissing)
end
end
+
+ describe 'GET #autocomplete' do
+ it_behaves_like 'when the user cannot read cross project', :autocomplete, { term: 'hello' }
+ it_behaves_like 'with external authorization service enabled', :autocomplete, { term: 'hello' }
+ end
end
diff --git a/spec/controllers/snippets/blobs_controller_spec.rb b/spec/controllers/snippets/blobs_controller_spec.rb
new file mode 100644
index 00000000000..b9f58587a58
--- /dev/null
+++ b/spec/controllers/snippets/blobs_controller_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Snippets::BlobsController do
+ using RSpec::Parameterized::TableSyntax
+ include SnippetHelpers
+
+ describe 'GET #raw' do
+ let_it_be(:author) { create(:user) }
+ let_it_be(:other_user) { create(:user) }
+
+ let(:visibility) { :public }
+ let(:snippet) { create(:personal_snippet, visibility, :repository, author: author) }
+ let(:filepath) { 'file1' }
+ let(:ref) { TestEnv::BRANCH_SHA['snippet/single-file'] }
+ let(:inline) { nil }
+
+ subject do
+ get(:raw,
+ params: {
+ snippet_id: snippet,
+ path: filepath,
+ ref: ref,
+ inline: inline
+ })
+ end
+
+ where(:snippet_visibility_level, :user, :status) do
+ :public | :author | :ok
+ :public | :other_user | :ok
+ :public | nil | :ok
+
+ :private | :author | :ok
+ :private | :other_user | :not_found
+ :private | nil | :redirect
+ end
+
+ with_them do
+ let(:visibility) { snippet_visibility_level }
+
+ before do
+ sign_in_as(user)
+
+ subject
+ end
+
+ it 'responds with correct status' do
+ expect(response).to have_gitlab_http_status(status)
+ end
+ end
+
+ it_behaves_like 'raw snippet blob'
+
+ context 'with a snippet without a repository' do
+ let(:snippet) { create(:personal_snippet, visibility, author: author) }
+
+ it_behaves_like 'raw snippet without repository', :redirect
+ end
+ end
+end
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index 70df1faf7dd..92370b3381a 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -6,6 +6,8 @@ RSpec.describe SnippetsController do
let_it_be(:user) { create(:user) }
describe 'GET #index' do
+ let(:base_params) { { username: user.username } }
+
context 'when username parameter is present' do
it_behaves_like 'paginated collection' do
let(:collection) { Snippet.all }
@@ -38,6 +40,10 @@ RSpec.describe SnippetsController do
expect(response).to redirect_to(dashboard_snippets_path)
end
end
+
+ it_behaves_like 'snippets sort order' do
+ let(:params) { base_params }
+ end
end
describe 'GET #new' do
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index bec4b24484a..99c3b82bd0d 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -114,6 +114,71 @@ RSpec.describe UsersController do
end
end
+ describe "#ssh_keys" do
+ describe "non existent user" do
+ it "does not generally work" do
+ get :ssh_keys, params: { username: 'not-existent' }
+
+ expect(response).not_to be_successful
+ end
+ end
+
+ describe "user with no keys" do
+ it "does generally work" do
+ get :ssh_keys, params: { username: user.username }
+
+ expect(response).to be_successful
+ end
+
+ it "renders all keys separated with a new line" do
+ get :ssh_keys, params: { username: user.username }
+
+ expect(response.body).to eq("")
+ end
+
+ it "responds with text/plain content type" do
+ get :ssh_keys, params: { username: user.username }
+ expect(response.content_type).to eq("text/plain")
+ end
+ end
+
+ describe "user with keys" do
+ let!(:key) { create(:key, user: user) }
+ let!(:another_key) { create(:another_key, user: user) }
+ let!(:deploy_key) { create(:deploy_key, user: user) }
+
+ it "does generally work" do
+ get :ssh_keys, params: { username: user.username }
+
+ expect(response).to be_successful
+ end
+
+ it "renders all non deploy keys separated with a new line" do
+ get :ssh_keys, params: { username: user.username }
+
+ expect(response.body).not_to eq('')
+ expect(response.body).to eq(user.all_ssh_keys.join("\n"))
+
+ expect(response.body).to include(key.key.sub(' dummy@gitlab.com', ''))
+ expect(response.body).to include(another_key.key.sub(' dummy@gitlab.com', ''))
+
+ expect(response.body).not_to include(deploy_key.key)
+ end
+
+ it "does not render the comment of the key" do
+ get :ssh_keys, params: { username: user.username }
+
+ expect(response.body).not_to match(/dummy@gitlab.com/)
+ end
+
+ it "responds with text/plain content type" do
+ get :ssh_keys, params: { username: user.username }
+
+ expect(response.content_type).to eq("text/plain")
+ end
+ end
+ end
+
describe 'GET #calendar' do
context 'for user' do
let(:project) { create(:project) }
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index 95d7981b85c..f31da943957 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -8,9 +8,10 @@ RSpec.describe 'Database schema' do
let(:connection) { ActiveRecord::Base.connection }
let(:tables) { connection.tables }
+ let(:columns_name_with_jsonb) { retrieve_columns_name_with_jsonb }
- # Use if you are certain that this column should not have a foreign key
- # EE: edit the ee/spec/db/schema_support.rb
+ # List of columns historically missing a FK, don't add more columns
+ # See: https://docs.gitlab.com/ce/development/foreign_keys.html#naming-foreign-keys
IGNORED_FK_COLUMNS = {
abuse_reports: %w[reporter_id user_id],
application_settings: %w[performance_bar_allowed_group_id slack_app_id snowplow_app_id eks_account_id eks_access_key_id],
@@ -62,6 +63,7 @@ RSpec.describe 'Database schema' do
oauth_access_tokens: %w[resource_owner_id application_id],
oauth_applications: %w[owner_id],
open_project_tracker_data: %w[closed_status_id],
+ product_analytics_events_experimental: %w[event_id txn_id user_id],
project_group_links: %w[group_id],
project_statistics: %w[namespace_id],
projects: %w[creator_id ci_id mirror_user_id],
@@ -118,7 +120,11 @@ RSpec.describe 'Database schema' do
let(:ignored_columns) { ignored_fk_columns(table) }
it 'do have the foreign keys' do
- expect(column_names_with_id - ignored_columns).to contain_exactly(*foreign_keys_columns)
+ expect(column_names_with_id - ignored_columns).to match_array(foreign_keys_columns)
+ end
+
+ it 'and having foreign key are not in the ignore list' do
+ expect(ignored_columns).to match_array(ignored_columns - foreign_keys)
end
end
end
@@ -169,8 +175,84 @@ RSpec.describe 'Database schema' do
end
end
+ # These pre-existing columns does not use a schema validation yet
+ IGNORED_JSONB_COLUMNS = {
+ "ApplicationSetting" => %w[repository_storages_weighted],
+ "AlertManagement::Alert" => %w[payload],
+ "Ci::BuildMetadata" => %w[config_options config_variables],
+ "Geo::Event" => %w[payload],
+ "GeoNodeStatus" => %w[status],
+ "Operations::FeatureFlagScope" => %w[strategies],
+ "Operations::FeatureFlags::Strategy" => %w[parameters],
+ "Packages::Composer::Metadatum" => %w[composer_json],
+ "Releases::Evidence" => %w[summary]
+ }.freeze
+
+ # We are skipping GEO models for now as it adds up complexity
+ describe 'for jsonb columns' do
+ it 'uses json schema validator' do
+ columns_name_with_jsonb.each do |hash|
+ next if models_by_table_name[hash["table_name"]].nil?
+
+ models_by_table_name[hash["table_name"]].each do |model|
+ jsonb_columns = [hash["column_name"]] - ignored_jsonb_columns(model.name)
+
+ expect(model).to validate_jsonb_schema(jsonb_columns)
+ end
+ end
+ end
+ end
+
+ context 'existence of Postgres schemas' do
+ def get_schemas
+ sql = <<~SQL
+ SELECT schema_name FROM
+ information_schema.schemata
+ WHERE
+ NOT schema_name ~* '^pg_' AND NOT schema_name = 'information_schema'
+ AND catalog_name = current_database()
+ SQL
+
+ ApplicationRecord.connection.select_all(sql).map do |row|
+ row['schema_name']
+ end
+ end
+
+ it 'we have a public schema' do
+ expect(get_schemas).to include('public')
+ end
+
+ Gitlab::Database::EXTRA_SCHEMAS.each do |schema|
+ it "we have a '#{schema}' schema'" do
+ expect(get_schemas).to include(schema.to_s)
+ end
+ end
+
+ it 'we do not have unexpected schemas' do
+ expect(get_schemas.size).to eq(Gitlab::Database::EXTRA_SCHEMAS.size + 1)
+ end
+ end
+
private
+ def retrieve_columns_name_with_jsonb
+ sql = <<~SQL
+ SELECT table_name, column_name, data_type
+ FROM information_schema.columns
+ WHERE table_catalog = '#{ApplicationRecord.connection_config[:database]}'
+ AND table_schema = 'public'
+ AND table_name NOT LIKE 'pg_%'
+ AND data_type = 'jsonb'
+ ORDER BY table_name, column_name, data_type
+ SQL
+
+ ApplicationRecord.connection.select_all(sql).to_a
+ end
+
+ def models_by_table_name
+ @models_by_table_name ||= ApplicationRecord.descendants.reject(&:abstract_class).group_by(&:table_name)
+ end
+
def ignored_fk_columns(column)
IGNORED_FK_COLUMNS.fetch(column, [])
end
@@ -178,4 +260,8 @@ RSpec.describe 'Database schema' do
def ignored_limit_enums(model)
IGNORED_LIMIT_ENUMS.fetch(model, [])
end
+
+ def ignored_jsonb_columns(model)
+ IGNORED_JSONB_COLUMNS.fetch(model, [])
+ end
end
diff --git a/spec/factories/alert_management/alerts.rb b/spec/factories/alert_management/alerts.rb
index 8724a626d77..ef511aa54b8 100644
--- a/spec/factories/alert_management/alerts.rb
+++ b/spec/factories/alert_management/alerts.rb
@@ -16,7 +16,9 @@ FactoryBot.define do
end
trait :with_issue do
- issue
+ after(:create) do |alert|
+ create(:issue, alert_management_alert: alert, project: alert.project)
+ end
end
trait :with_assignee do |alert|
@@ -73,10 +75,30 @@ FactoryBot.define do
without_ended_at
end
- trait :low_severity do
+ trait :critical do
+ severity { 'critical' }
+ end
+
+ trait :high do
+ severity { 'high' }
+ end
+
+ trait :medium do
+ severity { 'medium' }
+ end
+
+ trait :low do
severity { 'low' }
end
+ trait :info do
+ severity { 'info' }
+ end
+
+ trait :unknown do
+ severity { 'unknown' }
+ end
+
trait :prometheus do
monitoring_tool { Gitlab::AlertManagement::AlertParams::MONITORING_TOOLS[:prometheus] }
end
@@ -89,7 +111,7 @@ FactoryBot.define do
with_monitoring_tool
with_host
with_description
- low_severity
+ low
end
end
end
diff --git a/spec/factories/approvals.rb b/spec/factories/approvals.rb
new file mode 100644
index 00000000000..91e6fa54894
--- /dev/null
+++ b/spec/factories/approvals.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+# Read about factories at https://github.com/thoughtbot/factory_bot
+
+FactoryBot.define do
+ factory :approval do
+ merge_request
+ user
+ end
+end
diff --git a/spec/factories/background_migration_jobs.rb b/spec/factories/background_migration_jobs.rb
new file mode 100644
index 00000000000..048e4f4fcf2
--- /dev/null
+++ b/spec/factories/background_migration_jobs.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :background_migration_job, class: '::Gitlab::Database::BackgroundMigrationJob' do
+ class_name { 'TestJob' }
+ status { :pending }
+ arguments { [] }
+
+ trait :succeeded do
+ status { :succeeded }
+ end
+ end
+end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 9403967aa0a..b3815b53c2b 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -302,6 +302,18 @@ FactoryBot.define do
end
end
+ trait :report_results do
+ after(:build) do |build|
+ build.report_results << build(:ci_build_report_result)
+ end
+ end
+
+ trait :codequality_report do
+ after(:build) do |build|
+ build.job_artifacts << create(:ci_job_artifact, :codequality, job: build)
+ end
+ end
+
trait :test_reports do
after(:build) do |build|
build.job_artifacts << create(:ci_job_artifact, :junit, job: build)
diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb
index 85cdeaca12c..5bd5ab7d67a 100644
--- a/spec/factories/ci/pipelines.rb
+++ b/spec/factories/ci/pipelines.rb
@@ -65,6 +65,22 @@ FactoryBot.define do
add_attribute(:protected) { true }
end
+ trait :with_report_results do
+ status { :success }
+
+ after(:build) do |pipeline, evaluator|
+ pipeline.builds << build(:ci_build, :report_results, pipeline: pipeline, project: pipeline.project)
+ end
+ end
+
+ trait :with_codequality_report do
+ status { :success }
+
+ after(:build) do |pipeline, evaluator|
+ pipeline.builds << build(:ci_build, :codequality_report, pipeline: pipeline, project: pipeline.project)
+ end
+ end
+
trait :with_test_reports do
status { :success }
diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb
index c49c26f06e5..46aa640780b 100644
--- a/spec/factories/clusters/applications/helm.rb
+++ b/spec/factories/clusters/applications/helm.rb
@@ -172,5 +172,13 @@ FactoryBot.define do
cluster factory: %i(cluster provided_by_gcp)
end
end
+
+ factory :clusters_applications_cilium, class: 'Clusters::Applications::Cilium' do
+ cluster factory: %i(cluster with_installed_helm provided_by_gcp)
+
+ trait :no_helm_installed do
+ cluster factory: %i(cluster provided_by_gcp)
+ end
+ end
end
end
diff --git a/spec/factories/clusters/clusters.rb b/spec/factories/clusters/clusters.rb
index 7d0aaa45e40..0daf624ae7e 100644
--- a/spec/factories/clusters/clusters.rb
+++ b/spec/factories/clusters/clusters.rb
@@ -101,6 +101,7 @@ FactoryBot.define do
application_knative factory: %i(clusters_applications_knative installed)
application_elastic_stack factory: %i(clusters_applications_elastic_stack installed)
application_fluentd factory: %i(clusters_applications_fluentd installed)
+ application_cilium factory: %i(clusters_applications_cilium installed)
end
trait :with_domain do
diff --git a/spec/factories/custom_emoji.rb b/spec/factories/custom_emoji.rb
new file mode 100644
index 00000000000..2d185794ac9
--- /dev/null
+++ b/spec/factories/custom_emoji.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :custom_emoji, class: 'CustomEmoji' do
+ sequence(:name) { |n| "custom_emoji#{n}" }
+ namespace
+ file { fixture_file_upload(Rails.root.join('spec/fixtures/dk.png')) }
+ end
+end
diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb
index 42046464213..2aab9764560 100644
--- a/spec/factories/deployments.rb
+++ b/spec/factories/deployments.rb
@@ -7,7 +7,7 @@ FactoryBot.define do
tag { false }
user { nil }
project { nil }
- deployable { association :ci_build, environment: environment.name, project: environment.project }
+ deployable { association :ci_build, environment: environment.name, pipeline: association(:ci_pipeline, project: environment.project) }
environment factory: :environment
after(:build) do |deployment, evaluator|
diff --git a/spec/factories/events.rb b/spec/factories/events.rb
index 60bb3044191..ecbda5fbfd3 100644
--- a/spec/factories/events.rb
+++ b/spec/factories/events.rb
@@ -21,7 +21,7 @@ FactoryBot.define do
factory :closed_issue_event do
action { :closed }
- target factory: :closed_issue
+ target { association(:closed_issue, project: project) }
end
factory :wiki_page_event do
diff --git a/spec/factories/go_module_commits.rb b/spec/factories/go_module_commits.rb
new file mode 100644
index 00000000000..e42ef6696d1
--- /dev/null
+++ b/spec/factories/go_module_commits.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :go_module_commit, class: 'Commit' do
+ skip_create
+
+ transient do
+ files { { 'foo.txt' => 'content' } }
+ message { 'Message' }
+ project { create(:project, :repository) }
+
+ service do
+ Files::MultiService.new(
+ project,
+ project.owner,
+ commit_message: message,
+ start_branch: project.repository.root_ref || 'master',
+ branch_name: project.repository.root_ref || 'master',
+ actions: files.map do |path, content|
+ { action: :create, file_path: path, content: content }
+ end
+ )
+ end
+
+ tag { nil }
+ tag_message { nil }
+
+ commit do
+ r = service.execute
+
+ raise "operation failed: #{r}" unless r[:status] == :success
+
+ commit = project.repository.commit_by(oid: r[:result])
+
+ if tag
+ r = Tags::CreateService.new(project, project.owner).execute(tag, commit.sha, tag_message)
+
+ raise "operation failed: #{r}" unless r[:status] == :success
+ end
+
+ commit
+ end
+ end
+
+ trait :files do
+ transient do
+ files { raise ArgumentError.new("files is required") }
+ message { 'Add files' }
+ end
+ end
+
+ trait :package do
+ transient do
+ path { raise ArgumentError.new("path is required") }
+ message { 'Add package' }
+ files { { "#{path}/b.go" => "package b\nfunc Bye() { println(\"Goodbye world!\") }\n" } }
+ end
+ end
+
+ trait :module do
+ transient do
+ name { nil }
+ message { 'Add module' }
+ host_prefix { "#{::Gitlab.config.gitlab.host}/#{project.path_with_namespace}" }
+
+ url { name ? "#{host_prefix}/#{name}" : host_prefix }
+ path { name.to_s + '/' }
+
+ files do
+ {
+ "#{path}go.mod" => "module #{url}\n",
+ "#{path}a.go" => "package a\nfunc Hi() { println(\"Hello world!\") }\n"
+ }
+ end
+ end
+ end
+
+ initialize_with do
+ commit
+ end
+ end
+end
diff --git a/spec/factories/go_module_versions.rb b/spec/factories/go_module_versions.rb
new file mode 100644
index 00000000000..b0a96197350
--- /dev/null
+++ b/spec/factories/go_module_versions.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :go_module_version, class: 'Packages::Go::ModuleVersion' do
+ skip_create
+
+ initialize_with do
+ p = attributes[:params]
+ s = Packages::SemVer.parse(p.semver, prefixed: true)
+
+ raise ArgumentError.new("invalid sematic version: '#{p.semver}''") if !s && p.semver
+
+ new(p.mod, p.type, p.commit, name: p.name, semver: s, ref: p.ref)
+ end
+
+ mod { create :go_module }
+ type { :commit }
+ commit { mod.project.repository.head_commit }
+ name { nil }
+ semver { nil }
+ ref { nil }
+
+ params { OpenStruct.new(mod: mod, type: type, commit: commit, name: name, semver: semver, ref: ref) }
+
+ trait :tagged do
+ ref { mod.project.repository.find_tag(name) }
+ commit { ref.dereferenced_target }
+ name do
+ # This provides a sane default value, but in reality the caller should
+ # specify `name:`
+
+ # Find 'latest' semver tag (does not actually use semver precedence rules)
+ mod.project.repository.tags
+ .filter { |t| Packages::SemVer.match?(t.name, prefixed: true) }
+ .map { |t| Packages::SemVer.parse(t.name, prefixed: true) }
+ .max { |a, b| "#{a}" <=> "#{b}" }
+ .to_s
+ end
+
+ params { OpenStruct.new(mod: mod, type: :ref, commit: commit, semver: name, ref: ref) }
+ end
+
+ trait :pseudo do
+ transient do
+ prefix do
+ # This provides a sane default value, but in reality the caller should
+ # specify `prefix:`
+
+ # This does not take into account that `commit` may be before the
+ # latest tag.
+
+ # Find 'latest' semver tag (does not actually use semver precedence rules)
+ v = mod.project.repository.tags
+ .filter { |t| Packages::SemVer.match?(t.name, prefixed: true) }
+ .map { |t| Packages::SemVer.parse(t.name, prefixed: true) }
+ .max { |a, b| "#{a}" <=> "#{b}" }
+
+ # Default if no semver tags exist
+ next 'v0.0.0' unless v
+
+ # Valid pseudo-versions are:
+ # vX.0.0-yyyymmddhhmmss-sha1337beef0, when no earlier tagged commit exists for X
+ # vX.Y.Z-pre.0.yyyymmddhhmmss-sha1337beef0, when most recent prior tag is vX.Y.Z-pre
+ # vX.Y.(Z+1)-0.yyyymmddhhmmss-sha1337beef0, when most recent prior tag is vX.Y.Z
+
+ v = v.with(patch: v.patch + 1) unless v.prerelease
+ "#{v}.0"
+ end
+ end
+
+ type { :pseudo }
+ name { "#{prefix}#{commit.committed_date.strftime('%Y%m%d%H%M%S')}-#{commit.sha[0..11]}" }
+
+ params { OpenStruct.new(mod: mod, type: :pseudo, commit: commit, name: name, semver: name) }
+ end
+ end
+end
diff --git a/spec/factories/go_modules.rb b/spec/factories/go_modules.rb
new file mode 100644
index 00000000000..fdbacf48d3b
--- /dev/null
+++ b/spec/factories/go_modules.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :go_module, class: 'Packages::Go::Module' do
+ initialize_with { new(attributes[:project], attributes[:name], attributes[:path]) }
+ skip_create
+
+ project { create :project, :repository }
+
+ path { '' }
+ name { "#{Settings.build_gitlab_go_url}/#{project.full_path}#{path.empty? ? '' : '/'}#{path}" }
+ end
+end
diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb
index d51c437f83a..60d427dde00 100644
--- a/spec/factories/groups.rb
+++ b/spec/factories/groups.rb
@@ -51,5 +51,13 @@ FactoryBot.define do
trait :owner_subgroup_creation_only do
subgroup_creation_level { ::Gitlab::Access::OWNER_SUBGROUP_ACCESS }
end
+
+ trait :shared_runners_disabled do
+ shared_runners_enabled { false }
+ end
+
+ trait :allow_descendants_override_disabled_shared_runners do
+ allow_descendants_override_disabled_shared_runners { true }
+ end
end
end
diff --git a/spec/factories/namespaces.rb b/spec/factories/namespaces.rb
index 09dbe16ef9e..f4d5848e878 100644
--- a/spec/factories/namespaces.rb
+++ b/spec/factories/namespaces.rb
@@ -29,5 +29,35 @@ FactoryBot.define do
trait :with_root_storage_statistics do
association :root_storage_statistics, factory: :namespace_root_storage_statistics
end
+
+ # Construct a hierarchy underneath the namespace.
+ # Each namespace will have `children` amount of children,
+ # and `depth` levels of descendants.
+ trait :with_hierarchy do
+ transient do
+ children { 4 }
+ depth { 4 }
+ end
+
+ after(:create) do |namespace, evaluator|
+ def create_graph(parent: nil, children: 4, depth: 4)
+ return unless depth > 1
+
+ children.times do
+ factory_name = parent.model_name.singular
+ child = FactoryBot.create(factory_name, parent: parent)
+ create_graph(parent: child, children: children, depth: depth - 1)
+ end
+
+ parent
+ end
+
+ create_graph(
+ parent: namespace,
+ children: evaluator.children,
+ depth: evaluator.depth
+ )
+ end
+ end
end
end
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 52e91f31ec1..4b1f3194ce5 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -159,6 +159,11 @@ FactoryBot.define do
system { true }
end
+ trait :with_system_note_metadata do
+ system
+ system_note_metadata
+ end
+
trait :downvote do
note { "thumbsdown" }
end
diff --git a/spec/factories/packages.rb b/spec/factories/packages.rb
new file mode 100644
index 00000000000..562269a67bc
--- /dev/null
+++ b/spec/factories/packages.rb
@@ -0,0 +1,355 @@
+# frozen_string_literal: true
+FactoryBot.define do
+ factory :package, class: 'Packages::Package' do
+ project
+ name { 'my/company/app/my-app' }
+ sequence(:version) { |n| "1.#{n}-SNAPSHOT" }
+ package_type { :maven }
+
+ factory :maven_package do
+ maven_metadatum
+
+ after :build do |package|
+ package.maven_metadatum.path = "#{package.name}/#{package.version}"
+ end
+
+ after :create do |package|
+ create :package_file, :xml, package: package
+ create :package_file, :jar, package: package
+ create :package_file, :pom, package: package
+ end
+ end
+
+ factory :npm_package do
+ sequence(:name) { |n| "@#{project.root_namespace.path}/package-#{n}"}
+ version { '1.0.0' }
+ package_type { :npm }
+
+ after :create do |package|
+ create :package_file, :npm, package: package
+ end
+
+ trait :with_build do
+ after :create do |package|
+ user = package.project.creator
+ pipeline = create(:ci_pipeline, user: user)
+ create(:ci_build, user: user, pipeline: pipeline)
+ create :package_build_info, package: package, pipeline: pipeline
+ end
+ end
+ end
+
+ factory :nuget_package do
+ sequence(:name) { |n| "NugetPackage#{n}"}
+ sequence(:version) { |n| "1.0.#{n}" }
+ package_type { :nuget }
+
+ after :create do |package|
+ create :package_file, :nuget, package: package, file_name: "#{package.name}.#{package.version}.nupkg"
+ end
+
+ trait(:with_metadatum) do
+ after :build do |pkg|
+ pkg.nuget_metadatum = build(:nuget_metadatum)
+ end
+ end
+ end
+
+ factory :pypi_package do
+ pypi_metadatum
+
+ sequence(:name) { |n| "pypi-package-#{n}"}
+ sequence(:version) { |n| "1.0.#{n}" }
+ package_type { :pypi }
+
+ after :create do |package|
+ create :package_file, :pypi, package: package, file_name: "#{package.name}-#{package.version}.tar.gz"
+ end
+ end
+
+ factory :composer_package do
+ sequence(:name) { |n| "composer-package-#{n}"}
+ sequence(:version) { |n| "1.0.#{n}" }
+ package_type { :composer }
+
+ transient do
+ sha { project.repository.find_branch('master').target }
+ json { { name: name, version: version } }
+ end
+
+ trait(:with_metadatum) do
+ after :create do |package, evaluator|
+ create :composer_metadatum, package: package, target_sha: evaluator.sha, composer_json: evaluator.json
+ end
+ end
+ end
+
+ factory :conan_package do
+ conan_metadatum
+
+ transient do
+ without_package_files { false }
+ end
+
+ after :build do |package|
+ package.conan_metadatum.package_username = Packages::Conan::Metadatum.package_username_from(
+ full_path: package.project.full_path
+ )
+ end
+
+ sequence(:name) { |n| "package-#{n}" }
+ version { '1.0.0' }
+ package_type { :conan }
+
+ after :create do |package, evaluator|
+ unless evaluator.without_package_files
+ create :conan_package_file, :conan_recipe_file, package: package
+ create :conan_package_file, :conan_recipe_manifest, package: package
+ create :conan_package_file, :conan_package_info, package: package
+ create :conan_package_file, :conan_package_manifest, package: package
+ create :conan_package_file, :conan_package, package: package
+ end
+ end
+
+ trait(:without_loaded_metadatum) do
+ conan_metadatum { build(:conan_metadatum, package: nil) }
+ end
+ end
+ end
+
+ factory :composer_metadatum, class: 'Packages::Composer::Metadatum' do
+ package { create(:composer_package) }
+
+ target_sha { '123' }
+ composer_json { { name: 'foo' } }
+ end
+
+ factory :package_build_info, class: 'Packages::BuildInfo' do
+ package
+ end
+
+ factory :package_file, class: 'Packages::PackageFile' do
+ package
+
+ file_name { 'somefile.txt' }
+
+ transient do
+ file_fixture { 'spec/fixtures/packages/conan/recipe_files/conanfile.py' }
+ end
+
+ after(:build) do |package_file, evaluator|
+ package_file.file = fixture_file_upload(evaluator.file_fixture)
+ end
+
+ factory :conan_package_file do
+ package { create(:conan_package, without_package_files: true) }
+
+ transient do
+ without_loaded_metadatum { false }
+ end
+
+ trait(:conan_recipe_file) do
+ after :create do |package_file, evaluator|
+ unless evaluator.without_loaded_metadatum
+ create :conan_file_metadatum, :recipe_file, package_file: package_file
+ end
+ end
+
+ file_fixture { 'spec/fixtures/packages/conan/recipe_files/conanfile.py' }
+ file_name { 'conanfile.py' }
+ file_sha1 { 'be93151dc23ac34a82752444556fe79b32c7a1ad' }
+ file_md5 { '12345abcde' }
+ size { 400.kilobytes }
+ end
+
+ trait(:conan_recipe_manifest) do
+ after :create do |package_file, evaluator|
+ unless evaluator.without_loaded_metadatum
+ create :conan_file_metadatum, :recipe_file, package_file: package_file
+ end
+ end
+
+ file_fixture { 'spec/fixtures/packages/conan/recipe_files/conanmanifest.txt' }
+ file_name { 'conanmanifest.txt' }
+ file_sha1 { 'be93151dc23ac34a82752444556fe79b32c7a1ad' }
+ file_md5 { '12345abcde' }
+ size { 400.kilobytes }
+ end
+
+ trait(:conan_package_manifest) do
+ after :create do |package_file, evaluator|
+ unless evaluator.without_loaded_metadatum
+ create :conan_file_metadatum, :package_file, package_file: package_file
+ end
+ end
+
+ file_fixture { 'spec/fixtures/packages/conan/package_files/conanmanifest.txt' }
+ file_name { 'conanmanifest.txt' }
+ file_sha1 { 'be93151dc23ac34a82752444556fe79b32c7a1ad' }
+ file_md5 { '12345abcde' }
+ size { 400.kilobytes }
+ end
+
+ trait(:conan_package_info) do
+ after :create do |package_file, evaluator|
+ unless evaluator.without_loaded_metadatum
+ create :conan_file_metadatum, :package_file, package_file: package_file
+ end
+ end
+
+ file_fixture { 'spec/fixtures/packages/conan/package_files/conaninfo.txt' }
+ file_name { 'conaninfo.txt' }
+ file_sha1 { 'be93151dc23ac34a82752444556fe79b32c7a1ad' }
+ file_md5 { '12345abcde' }
+ size { 400.kilobytes }
+ end
+
+ trait(:conan_package) do
+ after :create do |package_file, evaluator|
+ unless evaluator.without_loaded_metadatum
+ create :conan_file_metadatum, :package_file, package_file: package_file
+ end
+ end
+
+ file_fixture { 'spec/fixtures/packages/conan/package_files/conan_package.tgz' }
+ file_name { 'conan_package.tgz' }
+ file_sha1 { 'be93151dc23ac34a82752444556fe79b32c7a1ad' }
+ file_md5 { '12345abcde' }
+ size { 400.kilobytes }
+ end
+ end
+
+ trait(:jar) do
+ file_fixture { 'spec/fixtures/packages/maven/my-app-1.0-20180724.124855-1.jar' }
+ file_name { 'my-app-1.0-20180724.124855-1.jar' }
+ file_sha1 { '4f0bfa298744d505383fbb57c554d4f5c12d88b3' }
+ size { 100.kilobytes }
+ end
+
+ trait(:pom) do
+ file_fixture { 'spec/fixtures/packages/maven/my-app-1.0-20180724.124855-1.pom' }
+ file_name { 'my-app-1.0-20180724.124855-1.pom' }
+ file_sha1 { '19c975abd49e5102ca6c74a619f21e0cf0351c57' }
+ size { 200.kilobytes }
+ end
+
+ trait(:xml) do
+ file_fixture { 'spec/fixtures/packages/maven/maven-metadata.xml' }
+ file_name { 'maven-metadata.xml' }
+ file_sha1 { '42b1bdc80de64953b6876f5a8c644f20204011b0' }
+ size { 300.kilobytes }
+ end
+
+ trait(:npm) do
+ file_fixture { 'spec/fixtures/packages/npm/foo-1.0.1.tgz' }
+ file_name { 'foo-1.0.1.tgz' }
+ file_sha1 { 'be93151dc23ac34a82752444556fe79b32c7a1ad' }
+ verified_at { Date.current }
+ verification_checksum { '4437b5775e61455588a7e5187a2e5c58c680694260bbe5501c235ec690d17f83' }
+ size { 400.kilobytes }
+ end
+
+ trait(:nuget) do
+ package
+ file_fixture { 'spec/fixtures/packages/nuget/package.nupkg' }
+ file_name { 'package.nupkg' }
+ file_sha1 { '5fe852b2a6abd96c22c11fa1ff2fb19d9ce58b57' }
+ size { 300.kilobytes }
+ end
+
+ trait(:pypi) do
+ package
+ file_fixture { 'spec/fixtures/packages/pypi/sample-project.tar.gz' }
+ file_name { 'sample-project-1.0.0.tar.gz' }
+ file_sha1 { '2c0cfbed075d3fae226f051f0cc771b533e01aff' }
+ file_md5 { '0a7392d24f42f83068fa3767c5310052' }
+ file_sha256 { '440e5e148a25331bbd7991575f7d54933c0ebf6cc735a18ee5066ac1381bb590' }
+ size { 1149.bytes }
+ end
+
+ trait(:object_storage) do
+ file_store { Packages::PackageFileUploader::Store::REMOTE }
+ end
+
+ trait(:checksummed) do
+ verification_checksum { 'abc' }
+ end
+
+ trait(:checksum_failure) do
+ verification_failure { 'Could not calculate the checksum' }
+ end
+
+ factory :package_file_with_file, traits: [:jar]
+ end
+
+ factory :maven_metadatum, class: 'Packages::Maven::Metadatum' do
+ association :package, package_type: :maven
+ path { 'my/company/app/my-app/1.0-SNAPSHOT' }
+ app_group { 'my.company.app' }
+ app_name { 'my-app' }
+ app_version { '1.0-SNAPSHOT' }
+ end
+
+ factory :conan_metadatum, class: 'Packages::Conan::Metadatum' do
+ association :package, factory: [:conan_package, :without_loaded_metadatum], without_package_files: true
+ package_username { 'username' }
+ package_channel { 'stable' }
+ end
+
+ factory :pypi_metadatum, class: 'Packages::Pypi::Metadatum' do
+ association :package, package_type: :pypi
+ required_python { '>=2.7' }
+ end
+
+ factory :nuget_metadatum, class: 'Packages::Nuget::Metadatum' do
+ package { create(:nuget_package) }
+
+ license_url { 'http://www.gitlab.com' }
+ project_url { 'http://www.gitlab.com' }
+ icon_url { 'http://www.gitlab.com' }
+ end
+
+ factory :conan_file_metadatum, class: 'Packages::Conan::FileMetadatum' do
+ package_file { create(:conan_package_file, :conan_recipe_file, without_loaded_metadatum: true) }
+ recipe_revision { '0' }
+ conan_file_type { 'recipe_file' }
+
+ trait(:recipe_file) do
+ conan_file_type { 'recipe_file' }
+ end
+
+ trait(:package_file) do
+ package_file { create(:conan_package_file, :conan_package, without_loaded_metadatum: true) }
+ conan_file_type { 'package_file' }
+ package_revision { '0' }
+ conan_package_reference { '123456789' }
+ end
+ end
+
+ factory :packages_dependency, class: 'Packages::Dependency' do
+ sequence(:name) { |n| "@test/package-#{n}"}
+ sequence(:version_pattern) { |n| "~6.2.#{n}" }
+ end
+
+ factory :packages_dependency_link, class: 'Packages::DependencyLink' do
+ package { create(:nuget_package) }
+ dependency { create(:packages_dependency) }
+ dependency_type { :dependencies }
+
+ trait(:with_nuget_metadatum) do
+ after :build do |link|
+ link.nuget_metadatum = build(:nuget_dependency_link_metadatum)
+ end
+ end
+ end
+
+ factory :nuget_dependency_link_metadatum, class: 'Packages::Nuget::DependencyLinkMetadatum' do
+ dependency_link { create(:packages_dependency_link) }
+ target_framework { '.NETStandard2.0' }
+ end
+
+ factory :packages_tag, class: 'Packages::Tag' do
+ package
+ sequence(:name) { |n| "tag-#{n}"}
+ end
+end
diff --git a/spec/factories/product_analytics_event.rb b/spec/factories/product_analytics_event.rb
new file mode 100644
index 00000000000..168b255f6ca
--- /dev/null
+++ b/spec/factories/product_analytics_event.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :product_analytics_event do
+ project
+ platform { 'web' }
+ collector_tstamp { DateTime.now }
+ dvce_created_tstamp { DateTime.now }
+ event_id { SecureRandom.uuid }
+ name_tracker { 'sp' }
+ v_tracker { 'js-2.14.0' }
+ v_collector { 'GitLab 13.1.0-pre' }
+ v_etl { 'GitLab 13.1.0-pre' }
+ domain_userid { SecureRandom.uuid }
+ domain_sessionidx { 4 }
+ page_url { 'http://localhost:3333/products/123' }
+ br_lang { 'en-US' }
+ br_cookies { true }
+ br_colordepth { '24' }
+ os_timezone { 'America/Los_Angeles' }
+ doc_charset { 'UTF-8' }
+ domain_sessionid { SecureRandom.uuid }
+ end
+end
diff --git a/spec/factories/project_repository_storage_moves.rb b/spec/factories/project_repository_storage_moves.rb
index b35d5e1d535..ea0b34e0338 100644
--- a/spec/factories/project_repository_storage_moves.rb
+++ b/spec/factories/project_repository_storage_moves.rb
@@ -14,5 +14,13 @@ FactoryBot.define do
trait :started do
state { ProjectRepositoryStorageMove.state_machines[:state].states[:started].value }
end
+
+ trait :finished do
+ state { ProjectRepositoryStorageMove.state_machines[:state].states[:finished].value }
+ end
+
+ trait :failed do
+ state { ProjectRepositoryStorageMove.state_machines[:state].states[:failed].value }
+ end
end
end
diff --git a/spec/factories/project_statistics.rb b/spec/factories/project_statistics.rb
index f084d9d5cbf..78e80a92b3a 100644
--- a/spec/factories/project_statistics.rb
+++ b/spec/factories/project_statistics.rb
@@ -21,6 +21,7 @@ FactoryBot.define do
project_statistics.lfs_objects_size = evaluator.size_multiplier * 3
project_statistics.build_artifacts_size = evaluator.size_multiplier * 4
project_statistics.packages_size = evaluator.size_multiplier * 5
+ project_statistics.snippets_size = evaluator.size_multiplier * 6
end
end
end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 4affab295b8..e4b53186ea8 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -181,6 +181,7 @@ FactoryBot.define do
transient do
create_templates { nil }
+ create_branch { nil }
end
after :create do |project, evaluator|
@@ -206,6 +207,16 @@ FactoryBot.define do
message: 'test 2',
branch_name: 'master')
end
+
+ if evaluator.create_branch
+ project.repository.create_file(
+ project.creator,
+ 'README.md',
+ "README on branch #{evaluator.create_branch}",
+ message: 'Add README.md',
+ branch_name: evaluator.create_branch)
+
+ end
end
end
@@ -305,6 +316,14 @@ FactoryBot.define do
end
end
+ trait :service_desk_disabled do
+ service_desk_enabled { nil }
+ end
+
+ trait(:service_desk_enabled) do
+ service_desk_enabled { true }
+ end
+
# Project with empty repository
#
# This is a case when you just created a project
@@ -363,4 +382,11 @@ FactoryBot.define do
)
end
end
+
+ factory :project_with_design, parent: :project do
+ after(:create) do |project|
+ issue = create(:issue, project: project)
+ create(:design, project: project, issue: issue)
+ end
+ end
end
diff --git a/spec/factories/service_desk_settings.rb b/spec/factories/service_desk_settings.rb
new file mode 100644
index 00000000000..abdbc5f1ea0
--- /dev/null
+++ b/spec/factories/service_desk_settings.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :service_desk_setting do
+ project
+ end
+end
diff --git a/spec/factories/services.rb b/spec/factories/services.rb
index fd97f6abb85..9a521336fee 100644
--- a/spec/factories/services.rb
+++ b/spec/factories/services.rb
@@ -77,18 +77,27 @@ FactoryBot.define do
username { 'jira_username' }
password { 'jira_password' }
jira_issue_transition_id { '56-1' }
+ issues_enabled { false }
+ project_key { nil }
end
after(:build) do |service, evaluator|
if evaluator.create_data
create(:jira_tracker_data, service: service,
url: evaluator.url, api_url: evaluator.api_url, jira_issue_transition_id: evaluator.jira_issue_transition_id,
- username: evaluator.username, password: evaluator.password
+ username: evaluator.username, password: evaluator.password, issues_enabled: evaluator.issues_enabled,
+ project_key: evaluator.project_key
)
end
end
end
+ factory :confluence_service do
+ project
+ active { true }
+ confluence_url { 'https://example.atlassian.net/wiki' }
+ end
+
factory :bugzilla_service do
project
active { true }
diff --git a/spec/factories/snippet_statistics.rb b/spec/factories/snippet_statistics.rb
new file mode 100644
index 00000000000..ab2d9525466
--- /dev/null
+++ b/spec/factories/snippet_statistics.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :snippet_statistics do
+ snippet
+
+ initialize_with do
+ # statistics are automatically created when a snippet is created
+ snippet&.statistics || new
+ end
+
+ transient do
+ with_data { false }
+ size_multiplier { 1 }
+ end
+
+ after(:build) do |snippet_statistics, evaluator|
+ if evaluator.with_data
+ snippet_statistics.repository_size = evaluator.size_multiplier
+ snippet_statistics.commit_count = evaluator.size_multiplier * 2
+ snippet_statistics.file_count = evaluator.size_multiplier * 3
+ end
+ end
+ end
+end
diff --git a/spec/factories/terraform/state.rb b/spec/factories/terraform/state.rb
index 74950ccf93e..46784581180 100644
--- a/spec/factories/terraform/state.rb
+++ b/spec/factories/terraform/state.rb
@@ -9,5 +9,11 @@ FactoryBot.define do
trait :with_file do
file { fixture_file_upload('spec/fixtures/terraform/terraform.tfstate', 'application/json') }
end
+
+ trait :locked do
+ sequence(:lock_xid) { |n| "lock-#{n}" }
+ locked_at { Time.current }
+ locked_by_user { create(:user) }
+ end
end
end
diff --git a/spec/factories/usage_data.rb b/spec/factories/usage_data.rb
index c0c5b1103fe..518b5b5e76a 100644
--- a/spec/factories/usage_data.rb
+++ b/spec/factories/usage_data.rb
@@ -5,7 +5,8 @@ FactoryBot.define do
skip_create # non-model factories (i.e. without #save)
initialize_with do
- projects = create_list(:project, 4)
+ projects = create_list(:project, 3)
+ projects << create(:project, :repository)
create(:board, project: projects[0])
create(:jira_service, project: projects[0])
create(:jira_service, :without_properties_callback, project: projects[1])
@@ -46,7 +47,7 @@ FactoryBot.define do
create(:sentry_issue, issue: projects[0].issues[0])
# Incident Labeled Issues
- incident_label_attrs = IncidentManagement::CreateIssueService::INCIDENT_LABEL
+ incident_label_attrs = IncidentManagement::CreateIncidentLabelService::LABEL_PROPERTIES
incident_label = create(:label, project: projects[0], **incident_label_attrs)
create(:labeled_issue, project: projects[0], labels: [incident_label])
incident_group = create(:group)
@@ -83,12 +84,23 @@ FactoryBot.define do
create(:clusters_applications_knative, :installed, cluster: gcp_cluster)
create(:clusters_applications_elastic_stack, :installed, cluster: gcp_cluster)
create(:clusters_applications_jupyter, :installed, cluster: gcp_cluster)
+ create(:clusters_applications_cilium, :installed, cluster: gcp_cluster)
create(:grafana_integration, project: projects[0], enabled: true)
create(:grafana_integration, project: projects[1], enabled: true)
create(:grafana_integration, project: projects[2], enabled: false)
ProjectFeature.first.update_attribute('repository_access_level', 0)
+
+ # Create fresh & a month (28-days SMAU) old data
+ env = create(:environment, project: projects[3])
+ [2, 29].each do |n|
+ deployment_options = { created_at: n.days.ago, project: env.project, environment: env }
+ create(:deployment, :failed, deployment_options)
+ create(:deployment, :success, deployment_options)
+ create_list(:project_snippet, 2, project: projects[0], created_at: n.days.ago)
+ create(:personal_snippet, created_at: n.days.ago)
+ end
end
end
end
diff --git a/spec/features/admin/admin_sees_project_statistics_spec.rb b/spec/features/admin/admin_sees_project_statistics_spec.rb
index 6e8211a9b4e..d94889b825a 100644
--- a/spec/features/admin/admin_sees_project_statistics_spec.rb
+++ b/spec/features/admin/admin_sees_project_statistics_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe "Admin > Admin sees project statistics" do
let(:project) { create(:project, :repository) }
it "shows project statistics" do
- expect(page).to have_content("Storage: 0 Bytes (Repository: 0 Bytes / Wikis: 0 Bytes / Build Artifacts: 0 Bytes / LFS: 0 Bytes)")
+ expect(page).to have_content("Storage: 0 Bytes (Repository: 0 Bytes / Wikis: 0 Bytes / Build Artifacts: 0 Bytes / LFS: 0 Bytes / Snippets: 0 Bytes)")
end
end
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index 369f91c6faa..6cd18f2755c 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe "Admin::Users" do
end
describe "view extra user information" do
- it 'shows the user popover on hover', :js, :quarantine do
+ it 'shows the user popover on hover', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/11290' do
expect(page).not_to have_selector('#__BV_popover_1__')
first_user_link = page.first('.js-user-link')
@@ -512,14 +512,14 @@ RSpec.describe "Admin::Users" do
end
it "lists group projects" do
- within(:css, '.append-bottom-default + .card') do
+ within(:css, '.gl-mb-3 + .card') do
expect(page).to have_content 'Group projects'
expect(page).to have_link group.name, href: admin_group_path(group)
end
end
it 'allows navigation to the group details' do
- within(:css, '.append-bottom-default + .card') do
+ within(:css, '.gl-mb-3 + .card') do
click_link group.name
end
within(:css, 'h3.page-title') do
@@ -529,7 +529,7 @@ RSpec.describe "Admin::Users" do
end
it 'shows the group access level' do
- within(:css, '.append-bottom-default + .card') do
+ within(:css, '.gl-mb-3 + .card') do
expect(page).to have_content 'Developer'
end
end
diff --git a/spec/features/admin/services/admin_visits_service_templates_spec.rb b/spec/features/admin/services/admin_visits_service_templates_spec.rb
new file mode 100644
index 00000000000..8e02538ece0
--- /dev/null
+++ b/spec/features/admin/services/admin_visits_service_templates_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Admin visits service templates' do
+ let(:admin) { create(:user, :admin) }
+ let(:slack_service) { Service.templates.find { |s| s.type == 'SlackService' } }
+
+ before do
+ sign_in(admin)
+
+ visit(admin_application_settings_services_path)
+ end
+
+ context 'without instance-level integration' do
+ it 'shows a link to service template' do
+ expect(page).to have_link('Slack', href: edit_admin_application_settings_service_path(slack_service.id))
+ expect(page).not_to have_link('Slack', href: edit_admin_application_settings_integration_path(slack_service))
+ end
+ end
+
+ context 'with instance-level integration' do
+ let_it_be(:slack_instance_integration) { create(:slack_service, instance: true, project: nil) }
+
+ it 'shows a link to instance-level integration' do
+ expect(page).not_to have_link('Slack', href: edit_admin_application_settings_service_path(slack_service.id))
+ expect(page).to have_link('Slack', href: edit_admin_application_settings_integration_path(slack_service))
+ end
+ end
+end
diff --git a/spec/features/clusters/cluster_health_dashboard_spec.rb b/spec/features/clusters/cluster_health_dashboard_spec.rb
new file mode 100644
index 00000000000..e9e3b48e9c0
--- /dev/null
+++ b/spec/features/clusters/cluster_health_dashboard_spec.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Cluster Health board', :js, :kubeclient, :use_clean_rails_memory_store_caching, :sidekiq_inline do
+ include KubernetesHelpers
+ include PrometheusHelpers
+
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:clusterable) { create(:project) }
+ let_it_be(:cluster) { create(:cluster, :provided_by_gcp, :project, projects: [clusterable]) }
+ let_it_be(:cluster_path) { project_cluster_path(clusterable, cluster) }
+
+ before do
+ clusterable.add_maintainer(current_user)
+
+ sign_in(current_user)
+ end
+
+ it 'shows cluster board section within the page' do
+ visit cluster_path
+
+ expect(page).to have_text('Health')
+
+ click_link 'Health'
+
+ expect(page).to have_css('.cluster-health-graphs')
+ end
+
+ context 'no prometheus installed' do
+ it 'shows install prometheus message' do
+ visit cluster_path
+
+ click_link 'Health'
+
+ expect(page).to have_text('you must first install Prometheus in the Applications tab')
+ end
+ end
+
+ context 'when there is cluster with installed prometheus' do
+ before do
+ create(:clusters_applications_prometheus, :installed, cluster: cluster)
+ stub_kubeclient_discover(cluster.platform.api_url)
+ end
+
+ context 'waiting for data' do
+ before do
+ stub_empty_response
+ end
+
+ it 'shows container and waiting for data message' do
+ visit cluster_path
+
+ click_link 'Health'
+
+ wait_for_requests
+
+ expect(page).to have_css('.prometheus-graphs')
+ expect(page).to have_text('Waiting for performance data')
+ end
+ end
+
+ context 'connected, prometheus returns data' do
+ before do
+ stub_connected
+ end
+
+ it 'renders charts' do
+ visit cluster_path
+
+ click_link 'Health'
+
+ wait_for_requests
+
+ expect(page).to have_css('.prometheus-graphs')
+ expect(page).to have_css('.prometheus-graph')
+ expect(page).to have_css('.prometheus-graph-title')
+ expect(page).to have_css('[_echarts_instance_]')
+ expect(page).to have_content('Avg')
+ end
+ end
+
+ def stub_empty_response
+ stub_prometheus_request(/prometheus-prometheus-server/, status: 204, body: {})
+ stub_prometheus_request(/prometheus\/api\/v1/, status: 204, body: {})
+ end
+
+ def stub_connected
+ stub_prometheus_request(/prometheus-prometheus-server/, body: prometheus_values_body)
+ stub_prometheus_request(/prometheus\/api\/v1/, body: prometheus_values_body)
+ end
+ end
+end
diff --git a/spec/features/clusters/installing_applications_shared_examples.rb b/spec/features/clusters/installing_applications_shared_examples.rb
index d2f28f5b219..74150c42519 100644
--- a/spec/features/clusters/installing_applications_shared_examples.rb
+++ b/spec/features/clusters/installing_applications_shared_examples.rb
@@ -2,6 +2,9 @@
RSpec.shared_examples "installing applications for a cluster" do |managed_apps_local_tiller|
before do
+ # Reduce interval from 10 seconds which is too long for an automated test
+ stub_const("#{Clusters::ClustersController}::STATUS_POLLING_INTERVAL", 500)
+
stub_feature_flags(managed_apps_local_tiller: managed_apps_local_tiller)
visit cluster_path
@@ -107,10 +110,6 @@ RSpec.shared_examples "installing applications for a cluster" do |managed_apps_l
end
describe 'when user clicks install button' do
- def domainname_form_value
- page.find('.js-knative-domainname').value
- end
-
before do
allow(ClusterInstallAppWorker).to receive(:perform_async)
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in)
@@ -135,7 +134,7 @@ RSpec.shared_examples "installing applications for a cluster" do |managed_apps_l
it 'shows status transition' do
page.within('.js-cluster-application-row-knative') do
- expect(domainname_form_value).to eq('domain.example.org')
+ expect(page).to have_field('Knative Domain Name:', with: 'domain.example.org')
expect(page).to have_css('.js-cluster-application-uninstall-button', exact_text: 'Uninstall')
end
@@ -147,7 +146,7 @@ RSpec.shared_examples "installing applications for a cluster" do |managed_apps_l
page.within('.js-cluster-application-row-knative') do
expect(ClusterPatchAppWorker).to receive(:perform_async)
- expect(domainname_form_value).to eq('domain.example.org')
+ expect(page).to have_field('Knative Domain Name:', with: 'domain.example.org')
page.find('.js-knative-domainname').set("new.domain.example.org")
@@ -155,7 +154,7 @@ RSpec.shared_examples "installing applications for a cluster" do |managed_apps_l
wait_for_requests
- expect(domainname_form_value).to eq('new.domain.example.org')
+ expect(page).to have_field('Knative Domain Name:', with: 'new.domain.example.org')
end
end
end
@@ -169,34 +168,54 @@ RSpec.shared_examples "installing applications for a cluster" do |managed_apps_l
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
create(:clusters_applications_helm, :installed, cluster: cluster) unless managed_apps_local_tiller
+ end
+ it 'shows status transition' do
page.within('.js-cluster-application-row-cert_manager') do
click_button 'Install'
+ wait_for_requests
+
+ expect(page).to have_field('Issuer Email', with: cluster.user.email)
+ expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
+
+ Clusters::Cluster.last.application_cert_manager.make_installing!
+
+ expect(page).to have_field('Issuer Email', with: cluster.user.email)
+ expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
+
+ Clusters::Cluster.last.application_cert_manager.make_installed!
+
+ expect(page).to have_field('Issuer Email', with: cluster.user.email)
+ expect(page).to have_css('.js-cluster-application-uninstall-button', exact_text: 'Uninstall')
end
+
+ expect(page).to have_content('Cert-Manager was successfully installed on your Kubernetes cluster')
end
- it 'shows status transition' do
- def email_form_value
- page.find('.js-email').value
- end
+ it 'installs with custom email' do
+ custom_email = 'new_email@example.org'
page.within('.js-cluster-application-row-cert_manager') do
- expect(email_form_value).to eq(cluster.user.email)
+ # Wait for the polling to finish
+ wait_for_requests
+
+ page.find('.js-email').set(custom_email)
+ click_button 'Install'
+ wait_for_requests
+
+ expect(page).to have_field('Issuer Email', with: custom_email)
expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
- page.find('.js-email').set("new_email@example.org")
Clusters::Cluster.last.application_cert_manager.make_installing!
- expect(email_form_value).to eq('new_email@example.org')
+ expect(page).to have_field('Issuer Email', with: custom_email)
expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
Clusters::Cluster.last.application_cert_manager.make_installed!
- expect(email_form_value).to eq('new_email@example.org')
+ expect(page).to have_field('Issuer Email', with: custom_email)
expect(page).to have_css('.js-cluster-application-uninstall-button', exact_text: 'Uninstall')
end
-
- expect(page).to have_content('Cert-Manager was successfully installed on your Kubernetes cluster')
end
end
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index e1beaf923e8..f870adbbdb6 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -205,6 +205,14 @@ RSpec.describe 'Dashboard Projects' do
it_behaves_like 'hidden pipeline status'
end
+
+ context "when last_pipeline is missing" do
+ before do
+ project.last_pipeline.delete
+ end
+
+ it_behaves_like 'hidden pipeline status'
+ end
end
context 'last push widget', :use_clean_rails_memory_store_caching do
diff --git a/spec/features/discussion_comments/commit_spec.rb b/spec/features/discussion_comments/commit_spec.rb
index 5a744e43bb6..32c0ba2a9a7 100644
--- a/spec/features/discussion_comments/commit_spec.rb
+++ b/spec/features/discussion_comments/commit_spec.rb
@@ -24,7 +24,7 @@ RSpec.describe 'Thread Comments Commit', :js do
expect(page).to have_css('.js-note-emoji')
end
- it 'adds award to the correct note', quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/207973' do
+ it 'adds award to the correct note', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/207973' do
find("#note_#{commit_discussion_note2.id} .js-note-emoji").click
first('.emoji-menu .js-emoji-btn').click
diff --git a/spec/features/groups/container_registry_spec.rb b/spec/features/groups/container_registry_spec.rb
index 87ef2131211..acac8724edf 100644
--- a/spec/features/groups/container_registry_spec.rb
+++ b/spec/features/groups/container_registry_spec.rb
@@ -82,7 +82,7 @@ RSpec.describe 'Container Registry', :js do
expect(service).to receive(:execute).with(container_repository) { { status: :success } }
expect(Projects::ContainerRepository::DeleteTagsService).to receive(:new).with(container_repository.project, user, tags: ['latest']) { service }
- first('[data-testid="singleDeleteButton"]').click
+ first('[data-testid="single-delete-button"]').click
expect(find('.modal .modal-title')).to have_content _('Remove tag')
find('.modal .modal-footer .btn-danger').click
end
diff --git a/spec/features/groups/empty_states_spec.rb b/spec/features/groups/empty_states_spec.rb
index d76cf993004..aaa59108b95 100644
--- a/spec/features/groups/empty_states_spec.rb
+++ b/spec/features/groups/empty_states_spec.rb
@@ -7,6 +7,8 @@ RSpec.describe 'Group empty states' do
let(:user) { create(:group_member, :developer, user: create(:user), group: group ).user }
before do
+ stub_feature_flags(vue_issuables_list: false)
+
sign_in(user)
end
diff --git a/spec/features/groups/import_export/import_file_spec.rb b/spec/features/groups/import_export/import_file_spec.rb
index 577198ef3f1..ee4f2740f9f 100644
--- a/spec/features/groups/import_export/import_file_spec.rb
+++ b/spec/features/groups/import_export/import_file_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Import/Export - Group Import', :js do
+RSpec.describe 'Import/Export - Group Import', :js do
let_it_be(:user) { create(:user) }
let_it_be(:import_path) { "#{Dir.tmpdir}/group_import_spec" }
diff --git a/spec/features/groups/members/manage_members_spec.rb b/spec/features/groups/members/manage_members_spec.rb
index e29d8fd651e..99846ecee27 100644
--- a/spec/features/groups/members/manage_members_spec.rb
+++ b/spec/features/groups/members/manage_members_spec.rb
@@ -68,9 +68,12 @@ RSpec.describe 'Groups > Members > Manage members' do
visit group_group_members_path(group)
- accept_confirm do
- find(:css, '.project-members-page li', text: user2.name).find(:css, 'a.btn-remove').click
- end
+ # Open modal
+ find(:css, '.project-members-page li', text: user2.name).find(:css, 'button.btn-remove').click
+
+ expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests'
+
+ click_on('Remove member')
wait_for_requests
diff --git a/spec/features/groups/navbar_spec.rb b/spec/features/groups/navbar_spec.rb
index cfa1f3338a1..06ff33ff0eb 100644
--- a/spec/features/groups/navbar_spec.rb
+++ b/spec/features/groups/navbar_spec.rb
@@ -47,6 +47,7 @@ RSpec.describe 'Group navbar' do
before do
stub_feature_flags(group_push_rules: false)
stub_feature_flags(group_iterations: false)
+ stub_feature_flags(group_wiki: false)
group.add_maintainer(user)
sign_in(user)
end
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index 78a35fe1d3f..8104ff3f987 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -68,6 +68,35 @@ RSpec.describe 'Group' do
end
end
+ describe 'real-time group url validation', :js do
+ it 'shows a message if group url is available' do
+ fill_in 'group_path', with: 'az'
+ wait_for_requests
+
+ expect(page).to have_content('Group path is available')
+ end
+
+ it 'shows an error if group url is taken' do
+ fill_in 'group_path', with: user.username
+ wait_for_requests
+
+ expect(page).to have_content('Group path is already taken')
+ end
+
+ it 'does not break after an invalid form submit' do
+ fill_in 'group_name', with: 'MyGroup'
+ fill_in 'group_path', with: 'z'
+ click_button 'Create group'
+
+ expect(page).to have_content('Group URL is too short')
+
+ fill_in 'group_path', with: 'az'
+ wait_for_requests
+
+ expect(page).to have_content('Group path is available')
+ end
+ end
+
describe 'Mattermost team creation' do
before do
stub_mattermost_setting(enabled: mattermost_enabled)
diff --git a/spec/features/invites_spec.rb b/spec/features/invites_spec.rb
index f85b4b78e35..d91fae5cdfd 100644
--- a/spec/features/invites_spec.rb
+++ b/spec/features/invites_spec.rb
@@ -2,8 +2,8 @@
require 'spec_helper'
-RSpec.describe 'Invites' do
- let(:user) { create(:user) }
+RSpec.describe 'Invites', :aggregate_failures do
+ let(:user) { create(:user, email: 'user@example.com') }
let(:owner) { create(:user, name: 'John Doe') }
let(:group) { create(:group, name: 'Owned') }
let(:project) { create(:project, :repository, namespace: group) }
@@ -11,7 +11,7 @@ RSpec.describe 'Invites' do
before do
project.add_maintainer(owner)
- group.add_user(owner, Gitlab::Access::OWNER)
+ group.add_owner(owner)
group.add_developer('user@example.com', owner)
group_invite.generate_invite_token!
end
@@ -23,12 +23,12 @@ RSpec.describe 'Invites' do
end
def fill_in_sign_up_form(new_user)
- fill_in 'new_user_name', with: new_user.name
- fill_in 'new_user_username', with: new_user.username
- fill_in 'new_user_email', with: new_user.email
- fill_in 'new_user_email_confirmation', with: new_user.email
- fill_in 'new_user_password', with: new_user.password
- click_button "Register"
+ fill_in 'new_user_name', with: new_user.name
+ fill_in 'new_user_username', with: new_user.username
+ fill_in 'new_user_email', with: new_user.email
+ fill_in 'new_user_email_confirmation', with: new_user.email
+ fill_in 'new_user_password', with: new_user.password
+ click_button 'Register'
end
def fill_in_sign_in_form(user)
@@ -48,19 +48,15 @@ RSpec.describe 'Invites' do
expect(page).to have_content('To accept this invitation, sign in')
end
- it 'sign in and redirects to invitation page' do
+ it 'sign in, grants access and redirects to group page' do
fill_in_sign_in_form(user)
- expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
- expect(page).to have_content(
- 'You have been invited by John Doe to join group Owned as Developer.'
- )
- expect(page).to have_link('Accept invitation')
- expect(page).to have_link('Decline')
+ expect(current_path).to eq(group_path(group))
+ expect(page).to have_content('You have been granted Developer access to group Owned.')
end
end
- context 'when signed in as an exists member' do
+ context 'when signed in as an existing member' do
before do
sign_in(owner)
end
@@ -71,166 +67,166 @@ RSpec.describe 'Invites' do
end
end
- describe 'accepting the invitation' do
- before do
- sign_in(user)
- visit invite_path(group_invite.raw_invite_token)
- end
-
- it 'grants access and redirects to group page' do
- page.click_link 'Accept invitation'
- expect(current_path).to eq(group_path(group))
- expect(page).to have_content(
- 'You have been granted Developer access to group Owned.'
- )
- end
- end
-
- describe 'declining the application' do
- context 'when signed in' do
- before do
- sign_in(user)
- visit invite_path(group_invite.raw_invite_token)
- end
-
- it 'declines application and redirects to dashboard' do
- page.click_link 'Decline'
- expect(current_path).to eq(dashboard_projects_path)
- expect(page).to have_content(
- 'You have declined the invitation to join group Owned.'
- )
- end
- end
-
- context 'when signed out' do
- before do
- visit decline_invite_path(group_invite.raw_invite_token)
- end
-
- it 'declines application and redirects to sign in page' do
- expect(current_path).to eq(new_user_session_path)
- expect(page).to have_content(
- 'You have declined the invitation to join group Owned.'
- )
- end
- end
- end
-
- describe 'invite an user using their email address' do
+ context 'when inviting a user using their email address' do
let(:new_user) { build_stubbed(:user) }
let(:invite_email) { new_user.email }
let(:group_invite) { create(:group_member, :invited, group: group, invite_email: invite_email) }
let!(:project_invite) { create(:project_member, :invited, project: project, invite_email: invite_email) }
- before do
- stub_application_setting(send_user_confirmation_email: send_email_confirmation)
- visit invite_path(group_invite.raw_invite_token)
- end
-
- context 'email confirmation disabled' do
- let(:send_email_confirmation) { false }
-
- it 'signs up and redirects to the dashboard page with all the projects/groups invitations automatically accepted' do
- fill_in_sign_up_form(new_user)
-
- expect(current_path).to eq(dashboard_projects_path)
- expect(page).to have_content(project.full_name)
-
- visit group_path(group)
-
- expect(page).to have_content(group.full_name)
+ context 'when user has not signed in yet' do
+ before do
+ stub_application_setting(send_user_confirmation_email: send_email_confirmation)
+ visit invite_path(group_invite.raw_invite_token)
end
- context 'the user sign-up using a different email address' do
- let(:invite_email) { build_stubbed(:user).email }
+ context 'email confirmation disabled' do
+ let(:send_email_confirmation) { false }
- it 'signs up and redirects to the invitation page' do
+ it 'signs up and redirects to the dashboard page with all the projects/groups invitations automatically accepted' do
fill_in_sign_up_form(new_user)
- expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
- end
- end
- end
-
- context 'email confirmation enabled' do
- let(:send_email_confirmation) { true }
-
- context 'when soft email confirmation is not enabled' do
- before do
- allow(User).to receive(:allow_unconfirmed_access_for).and_return 0
- end
-
- it 'signs up and redirects to root page with all the project/groups invitation automatically accepted' do
- fill_in_sign_up_form(new_user)
- confirm_email(new_user)
- fill_in_sign_in_form(new_user)
-
- expect(current_path).to eq(root_path)
+ expect(current_path).to eq(dashboard_projects_path)
expect(page).to have_content(project.full_name)
visit group_path(group)
expect(page).to have_content(group.full_name)
end
- end
- context 'when soft email confirmation is enabled' do
- before do
- allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
- end
+ context 'the user sign-up using a different email address' do
+ let(:invite_email) { build_stubbed(:user).email }
- it 'signs up and redirects to root page with all the project/groups invitation automatically accepted' do
- fill_in_sign_up_form(new_user)
- confirm_email(new_user)
-
- expect(current_path).to eq(root_path)
- expect(page).to have_content(project.full_name)
-
- visit group_path(group)
+ it 'signs up and redirects to the invitation page' do
+ fill_in_sign_up_form(new_user)
- expect(page).to have_content(group.full_name)
+ expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
+ end
end
end
- it "doesn't accept invitations until the user confirms their email" do
- fill_in_sign_up_form(new_user)
- sign_in(owner)
-
- visit project_project_members_path(project)
- expect(page).to have_content 'Invited'
- end
-
- context 'the user sign-up using a different email address' do
- let(:invite_email) { build_stubbed(:user).email }
+ context 'email confirmation enabled' do
+ let(:send_email_confirmation) { true }
context 'when soft email confirmation is not enabled' do
before do
- stub_feature_flags(soft_email_confirmation: false)
allow(User).to receive(:allow_unconfirmed_access_for).and_return 0
end
- it 'signs up and redirects to the invitation page' do
+ it 'signs up and redirects to root page with all the project/groups invitation automatically accepted' do
fill_in_sign_up_form(new_user)
confirm_email(new_user)
fill_in_sign_in_form(new_user)
- expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
+ expect(current_path).to eq(root_path)
+ expect(page).to have_content(project.full_name)
+
+ visit group_path(group)
+
+ expect(page).to have_content(group.full_name)
end
end
context 'when soft email confirmation is enabled' do
before do
- stub_feature_flags(soft_email_confirmation: true)
allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
end
- it 'signs up and redirects to the invitation page' do
+ it 'signs up and redirects to root page with all the project/groups invitation automatically accepted' do
fill_in_sign_up_form(new_user)
+ confirm_email(new_user)
- expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
+ expect(current_path).to eq(root_path)
+ expect(page).to have_content(project.full_name)
+
+ visit group_path(group)
+
+ expect(page).to have_content(group.full_name)
+ end
+ end
+
+ it "doesn't accept invitations until the user confirms their email" do
+ fill_in_sign_up_form(new_user)
+ sign_in(owner)
+
+ visit project_project_members_path(project)
+ expect(page).to have_content 'Invited'
+ end
+
+ context 'the user sign-up using a different email address' do
+ let(:invite_email) { build_stubbed(:user).email }
+
+ context 'when soft email confirmation is not enabled' do
+ before do
+ stub_feature_flags(soft_email_confirmation: false)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 0
+ end
+
+ it 'signs up and redirects to the invitation page' do
+ fill_in_sign_up_form(new_user)
+ confirm_email(new_user)
+ fill_in_sign_in_form(new_user)
+
+ expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
+ end
+ end
+
+ context 'when soft email confirmation is enabled' do
+ before do
+ stub_feature_flags(soft_email_confirmation: true)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
+ end
+
+ it 'signs up and redirects to the invitation page' do
+ fill_in_sign_up_form(new_user)
+
+ expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
+ end
end
end
end
end
+
+ context 'when declining the invitation' do
+ let(:send_email_confirmation) { true }
+
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ visit invite_path(group_invite.raw_invite_token)
+ end
+
+ it 'declines application and redirects to dashboard' do
+ page.click_link 'Decline'
+ expect(current_path).to eq(dashboard_projects_path)
+ expect(page).to have_content('You have declined the invitation to join group Owned.')
+ end
+ end
+
+ context 'when signed out' do
+ before do
+ visit decline_invite_path(group_invite.raw_invite_token)
+ end
+
+ it 'declines application and redirects to sign in page' do
+ expect(current_path).to eq(new_user_session_path)
+ expect(page).to have_content('You have declined the invitation to join group Owned.')
+ end
+ end
+ end
+
+ context 'when accepting the invitation' do
+ let(:send_email_confirmation) { true }
+
+ before do
+ sign_in(user)
+ visit invite_path(group_invite.raw_invite_token)
+ end
+
+ it 'grants access and redirects to group page' do
+ page.click_link 'Accept invitation'
+ expect(current_path).to eq(group_path(group))
+ expect(page).to have_content('You have been granted Owner access to group Owned.')
+ end
+ end
end
end
diff --git a/spec/features/issuables/close_reopen_report_toggle_spec.rb b/spec/features/issuables/close_reopen_report_toggle_spec.rb
index cf3028ec4c9..f442b25f593 100644
--- a/spec/features/issuables/close_reopen_report_toggle_spec.rb
+++ b/spec/features/issuables/close_reopen_report_toggle_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
let(:human_model_name) { issuable.model_name.human.downcase }
it 'shows toggle' do
- expect(page).to have_link("Close #{human_model_name}")
+ expect(page).to have_button("Close #{human_model_name}")
expect(page).to have_selector('.issuable-close-dropdown')
end
@@ -63,7 +63,7 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
let(:issuable) { create(:issue, :closed, :locked, project: project) }
it 'hides the reopen button' do
- expect(page).not_to have_link('Reopen issue')
+ expect(page).not_to have_button('Reopen issue')
end
context 'when the issue author is the current user' do
@@ -72,7 +72,7 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
end
it 'hides the reopen button' do
- expect(page).not_to have_link('Reopen issue')
+ expect(page).not_to have_button('Reopen issue')
end
end
end
@@ -91,8 +91,8 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
it 'only shows the `Report abuse` and `New issue` buttons' do
expect(page).to have_link('Report abuse')
expect(page).to have_link('New issue')
- expect(page).not_to have_link('Close issue')
- expect(page).not_to have_link('Reopen issue')
+ expect(page).not_to have_button('Close issue')
+ expect(page).not_to have_button('Reopen issue')
expect(page).not_to have_link('Edit')
end
end
@@ -120,8 +120,8 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
it 'shows only the `Report abuse` and `Edit` button' do
expect(page).to have_link('Report abuse')
expect(page).to have_link('Edit')
- expect(page).not_to have_link('Close merge request')
- expect(page).not_to have_link('Reopen merge request')
+ expect(page).not_to have_button('Close merge request')
+ expect(page).not_to have_button('Reopen merge request')
end
context 'when the merge request author is the current user' do
@@ -130,8 +130,8 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
it 'shows only the `Edit` button' do
expect(page).to have_link('Edit')
expect(page).not_to have_link('Report abuse')
- expect(page).not_to have_link('Close merge request')
- expect(page).not_to have_link('Reopen merge request')
+ expect(page).not_to have_button('Close merge request')
+ expect(page).not_to have_button('Reopen merge request')
end
end
end
@@ -149,8 +149,8 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
it 'only shows a `Report abuse` button' do
expect(page).to have_link('Report abuse')
- expect(page).not_to have_link('Close merge request')
- expect(page).not_to have_link('Reopen merge request')
+ expect(page).not_to have_button('Close merge request')
+ expect(page).not_to have_button('Reopen merge request')
expect(page).not_to have_link('Edit')
end
end
diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb
index 382a7a9321c..259c09b9d11 100644
--- a/spec/features/issuables/issuable_list_spec.rb
+++ b/spec/features/issuables/issuable_list_spec.rb
@@ -2,13 +2,15 @@
require 'spec_helper'
-RSpec.describe 'issuable list' do
+RSpec.describe 'issuable list', :js do
let(:project) { create(:project) }
let(:user) { create(:user) }
issuable_types = [:issue, :merge_request]
before do
+ stub_feature_flags(vue_issuables_list: false)
+ # something is going on
project.add_user(user, :developer)
sign_in(user)
issuable_types.each { |type| create_issuables(type) }
@@ -26,9 +28,9 @@ RSpec.describe 'issuable list' do
it "counts upvotes, downvotes and notes count for each #{issuable_type.to_s.humanize}" do
visit_issuable_list(issuable_type)
- expect(first('.fa-thumbs-up').find(:xpath, '..')).to have_content(1)
- expect(first('.fa-thumbs-down').find(:xpath, '..')).to have_content(1)
- expect(first('.fa-comments').find(:xpath, '..')).to have_content(2)
+ expect(first('.issuable-upvotes')).to have_content(1)
+ expect(first('.issuable-downvotes')).to have_content(1)
+ expect(first('.issuable-comments')).to have_content(2)
end
it 'sorts labels alphabetically' do
diff --git a/spec/features/issuables/sorting_list_spec.rb b/spec/features/issuables/sorting_list_spec.rb
index 59518723740..ff92fe369d4 100644
--- a/spec/features/issuables/sorting_list_spec.rb
+++ b/spec/features/issuables/sorting_list_spec.rb
@@ -10,6 +10,10 @@ RSpec.describe 'Sort Issuable List' do
let(:first_updated_issuable) { issuables.order_updated_asc.first }
let(:last_updated_issuable) { issuables.order_updated_desc.first }
+ before do
+ stub_feature_flags(vue_issuables_list: false)
+ end
+
context 'for merge requests' do
include MergeRequestHelpers
diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb
index 84a786e91a7..91f0e983fa8 100644
--- a/spec/features/issues/bulk_assignment_labels_spec.rb
+++ b/spec/features/issues/bulk_assignment_labels_spec.rb
@@ -5,14 +5,18 @@ require 'spec_helper'
RSpec.describe 'Issues > Labels bulk assignment' do
let(:user) { create(:user) }
let!(:project) { create(:project) }
- let!(:issue1) { create(:issue, project: project, title: "Issue 1") }
- let!(:issue2) { create(:issue, project: project, title: "Issue 2") }
let!(:bug) { create(:label, project: project, title: 'bug') }
let!(:feature) { create(:label, project: project, title: 'feature') }
+ let!(:frontend) { create(:label, project: project, title: 'frontend') }
let!(:wontfix) { create(:label, project: project, title: 'wontfix') }
+ let!(:issue1) { create(:issue, project: project, title: "Issue 1", labels: [frontend]) }
+ let!(:issue2) { create(:issue, project: project, title: "Issue 2") }
context 'as an allowed user', :js do
before do
+ # Make sure that issuables list FF is not turned on.
+ stub_feature_flags(vue_issuables_list: false)
+
project.add_maintainer(user)
sign_in user
@@ -48,11 +52,29 @@ RSpec.describe 'Issues > Labels bulk assignment' do
it do
expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).to have_content 'frontend'
expect(find("#issue_#{issue2.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue2.id}")).not_to have_content 'frontend'
end
end
- context 'to a issue' do
+ context 'to some issues' do
+ before do
+ check "selected_issue_#{issue1.id}"
+ check "selected_issue_#{issue2.id}"
+ open_labels_dropdown ['bug']
+ update_issues
+ end
+
+ it do
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).to have_content 'frontend'
+ expect(find("#issue_#{issue2.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue2.id}")).not_to have_content 'frontend'
+ end
+ end
+
+ context 'to an issue' do
before do
check "selected_issue_#{issue1.id}"
open_labels_dropdown ['bug']
@@ -61,7 +83,24 @@ RSpec.describe 'Issues > Labels bulk assignment' do
it do
expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).to have_content 'frontend'
+ expect(find("#issue_#{issue2.id}")).not_to have_content 'bug'
+ expect(find("#issue_#{issue2.id}")).not_to have_content 'frontend'
+ end
+ end
+
+ context 'to an issue by selecting the label first' do
+ before do
+ open_labels_dropdown ['bug']
+ check "selected_issue_#{issue1.id}"
+ update_issues
+ end
+
+ it do
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).to have_content 'frontend'
expect(find("#issue_#{issue2.id}")).not_to have_content 'bug'
+ expect(find("#issue_#{issue2.id}")).not_to have_content 'frontend'
end
end
end
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index 5b5348d4069..080943da185 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -20,9 +20,7 @@ RSpec.describe 'Filter issues', :js do
let!(:milestone) { create(:milestone, title: "8", project: project, start_date: 2.days.ago) }
def expect_no_issues_list
- page.within '.issues-list' do
- expect(page).to have_no_selector('.issue')
- end
+ expect(page).to have_no_selector('.issue')
end
before do
@@ -88,7 +86,7 @@ RSpec.describe 'Filter issues', :js do
end
it 'does not have the != option' do
- input_filtered_search("label:", submit: false)
+ input_filtered_search("label:", submit: false, extra_space: false)
wait_for_requests
within('#js-dropdown-operator') do
@@ -344,7 +342,7 @@ RSpec.describe 'Filter issues', :js do
context 'issue label clicked' do
it 'filters and displays in search bar' do
- find('.issues-list .issue .issuable-main-info .issuable-info a .gl-label-text', text: multiple_words_label.title).click
+ find('[data-qa-selector="issuable-label"]', text: multiple_words_label.title).click
expect_issues_list_count(1)
expect_tokens([label_token("\"#{multiple_words_label.title}\"")])
diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb
index 167fecc5ab1..2a094281133 100644
--- a/spec/features/issues/filtered_search/search_bar_spec.rb
+++ b/spec/features/issues/filtered_search/search_bar_spec.rb
@@ -88,7 +88,7 @@ RSpec.describe 'Search bar', :js do
expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: original_size)
end
- it 'resets the dropdown filters', :quarantine do
+ it 'resets the dropdown filters', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/9985' do
filtered_search.click
hint_offset = get_left_style(find('#js-dropdown-hint')['style'])
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 4a7e1ba99e9..0b2e8013304 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -3,455 +3,647 @@
require 'spec_helper'
RSpec.describe 'GFM autocomplete', :js do
- let(:issue_xss_title) { 'This will execute alert<img src=x onerror=alert(2)&lt;img src=x onerror=alert(1)&gt;' }
- let(:user_xss_title) { 'eve <img src=x onerror=alert(2)&lt;img src=x onerror=alert(1)&gt;' }
- let(:label_xss_title) { 'alert label &lt;img src=x onerror="alert(\'Hello xss\');" a' }
- let(:milestone_xss_title) { 'alert milestone &lt;img src=x onerror="alert(\'Hello xss\');" a' }
-
- let(:user_xss) { create(:user, name: user_xss_title, username: 'xss.user') }
- let(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') }
- let(:project) { create(:project) }
- let(:label) { create(:label, project: project, title: 'special+') }
+ let_it_be(:user_xss_title) { 'eve <img src=x onerror=alert(2)&lt;img src=x onerror=alert(1)&gt;' }
+ let_it_be(:user_xss) { create(:user, name: user_xss_title, username: 'xss.user') }
+ let_it_be(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:label) { create(:label, project: project, title: 'special+') }
+
let(:issue) { create(:issue, project: project) }
- before do
+ before_all do
project.add_maintainer(user)
project.add_maintainer(user_xss)
+ end
- sign_in(user)
- visit project_issue_path(project, issue)
+ describe 'when tribute_autocomplete feature flag is off' do
+ before do
+ stub_feature_flags(tribute_autocomplete: false)
- wait_for_requests
- end
+ sign_in(user)
+ visit project_issue_path(project, issue)
- it 'updates issue description with GFM reference' do
- find('.js-issuable-edit').click
+ wait_for_requests
+ end
- wait_for_requests
+ it 'updates issue description with GFM reference' do
+ find('.js-issuable-edit').click
- simulate_input('#issue-description', "@#{user.name[0...3]}")
+ wait_for_requests
- wait_for_requests
+ simulate_input('#issue-description', "@#{user.name[0...3]}")
- find('.atwho-view .cur').click
+ wait_for_requests
- click_button 'Save changes'
+ find('.atwho-view .cur').click
- wait_for_requests
+ click_button 'Save changes'
- expect(find('.description')).to have_content(user.to_reference)
- end
+ wait_for_requests
- it 'opens autocomplete menu when field starts with text' do
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys('@')
+ expect(find('.description')).to have_content(user.to_reference)
end
- expect(page).to have_selector('.atwho-container')
- end
+ it 'opens quick action autocomplete when updating description' do
+ find('.js-issuable-edit').click
- it 'opens autocomplete menu for Issues when field starts with text with item escaping HTML characters' do
- create(:issue, project: project, title: issue_xss_title)
+ find('#issue-description').native.send_keys('/')
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys('#')
+ expect(page).to have_selector('.atwho-container')
end
- wait_for_requests
-
- expect(page).to have_selector('.atwho-container')
+ it 'opens autocomplete menu when field starts with text' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('@')
+ end
- page.within '.atwho-container #at-view-issues' do
- expect(page.all('li').first.text).to include(issue_xss_title)
+ expect(page).to have_selector('.atwho-container')
end
- end
- it 'opens autocomplete menu for Username when field starts with text with item escaping HTML characters' do
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys('@ev')
- end
+ it 'opens autocomplete menu for Issues when field starts with text with item escaping HTML characters' do
+ issue_xss_title = 'This will execute alert<img src=x onerror=alert(2)&lt;img src=x onerror=alert(1)&gt;'
+ create(:issue, project: project, title: issue_xss_title)
+
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('#')
+ end
- wait_for_requests
+ wait_for_requests
- expect(page).to have_selector('.atwho-container')
+ expect(page).to have_selector('.atwho-container')
- page.within '.atwho-container #at-view-users' do
- expect(find('li').text).to have_content(user_xss.username)
+ page.within '.atwho-container #at-view-issues' do
+ expect(page.all('li').first.text).to include(issue_xss_title)
+ end
end
- end
- it 'opens autocomplete menu for Milestone when field starts with text with item escaping HTML characters' do
- create(:milestone, project: project, title: milestone_xss_title)
+ it 'opens autocomplete menu for Username when field starts with text with item escaping HTML characters' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('@ev')
+ end
+
+ wait_for_requests
+
+ expect(page).to have_selector('.atwho-container')
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys('%')
+ page.within '.atwho-container #at-view-users' do
+ expect(find('li').text).to have_content(user_xss.username)
+ end
end
- wait_for_requests
+ it 'opens autocomplete menu for Milestone when field starts with text with item escaping HTML characters' do
+ milestone_xss_title = 'alert milestone &lt;img src=x onerror="alert(\'Hello xss\');" a'
+ create(:milestone, project: project, title: milestone_xss_title)
+
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('%')
+ end
+
+ wait_for_requests
- expect(page).to have_selector('.atwho-container')
+ expect(page).to have_selector('.atwho-container')
- page.within '.atwho-container #at-view-milestones' do
- expect(find('li').text).to have_content('alert milestone')
+ page.within '.atwho-container #at-view-milestones' do
+ expect(find('li').text).to have_content('alert milestone')
+ end
end
- end
- it 'doesnt open autocomplete menu character is prefixed with text' do
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys('testing')
- find('#note-body').native.send_keys('@')
+ it 'doesnt open autocomplete menu character is prefixed with text' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('testing')
+ find('#note-body').native.send_keys('@')
+ end
+
+ expect(page).not_to have_selector('.atwho-view')
end
- expect(page).not_to have_selector('.atwho-view')
- end
+ it 'doesnt select the first item for non-assignee dropdowns' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys(':')
+ end
+
+ expect(page).to have_selector('.atwho-container')
- it 'doesnt select the first item for non-assignee dropdowns' do
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys(':')
+ wait_for_requests
+
+ expect(find('#at-view-58')).not_to have_selector('.cur:first-of-type')
end
- expect(page).to have_selector('.atwho-container')
+ it 'does not open autocomplete menu when ":" is prefixed by a number and letters' do
+ note = find('#note-body')
- wait_for_requests
+ # Number.
+ page.within '.timeline-content-form' do
+ note.native.send_keys('7:')
+ end
- expect(find('#at-view-58')).not_to have_selector('.cur:first-of-type')
- end
+ expect(page).not_to have_selector('.atwho-view')
- it 'does not open autocomplete menu when ":" is prefixed by a number and letters' do
- note = find('#note-body')
+ # ASCII letter.
+ page.within '.timeline-content-form' do
+ note.set('')
+ note.native.send_keys('w:')
+ end
- # Number.
- page.within '.timeline-content-form' do
- note.native.send_keys('7:')
- end
+ expect(page).not_to have_selector('.atwho-view')
- expect(page).not_to have_selector('.atwho-view')
+ # Non-ASCII letter.
+ page.within '.timeline-content-form' do
+ note.set('')
+ note.native.send_keys('Ё:')
+ end
- # ASCII letter.
- page.within '.timeline-content-form' do
- note.set('')
- note.native.send_keys('w:')
+ expect(page).not_to have_selector('.atwho-view')
end
- expect(page).not_to have_selector('.atwho-view')
+ it 'selects the first item for assignee dropdowns' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('@')
+ end
- # Non-ASCII letter.
- page.within '.timeline-content-form' do
- note.set('')
- note.native.send_keys('Ё:')
- end
+ expect(page).to have_selector('.atwho-container')
- expect(page).not_to have_selector('.atwho-view')
- end
+ wait_for_requests
- it 'selects the first item for assignee dropdowns' do
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys('@')
+ expect(find('#at-view-users')).to have_selector('.cur:first-of-type')
end
- expect(page).to have_selector('.atwho-container')
+ it 'includes items for assignee dropdowns with non-ASCII characters in name' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('')
+ simulate_input('#note-body', "@#{user.name[0...8]}")
+ end
- wait_for_requests
+ expect(page).to have_selector('.atwho-container')
- expect(find('#at-view-users')).to have_selector('.cur:first-of-type')
- end
+ wait_for_requests
- it 'includes items for assignee dropdowns with non-ASCII characters in name' do
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys('')
- simulate_input('#note-body', "@#{user.name[0...8]}")
+ expect(find('#at-view-users')).to have_content(user.name)
end
- expect(page).to have_selector('.atwho-container')
+ it 'selects the first item for non-assignee dropdowns if a query is entered' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys(':1')
+ end
- wait_for_requests
+ expect(page).to have_selector('.atwho-container')
- expect(find('#at-view-users')).to have_content(user.name)
- end
+ wait_for_requests
- it 'selects the first item for non-assignee dropdowns if a query is entered' do
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys(':1')
+ expect(find('#at-view-58')).to have_selector('.cur:first-of-type')
end
- expect(page).to have_selector('.atwho-container')
+ context 'if a selected value has special characters' do
+ it 'wraps the result in double quotes' do
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('')
+ simulate_input('#note-body', "~#{label.title[0]}")
+ end
- wait_for_requests
+ label_item = find('.atwho-view li', text: label.title)
- expect(find('#at-view-58')).to have_selector('.cur:first-of-type')
- end
+ expect_to_wrap(true, label_item, note, label.title)
+ end
- context 'if a selected value has special characters' do
- it 'wraps the result in double quotes' do
- note = find('#note-body')
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys('')
- simulate_input('#note-body', "~#{label.title[0]}")
+ it "shows dropdown after a new line" do
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys('test')
+ note.native.send_keys(:enter)
+ note.native.send_keys(:enter)
+ note.native.send_keys('@')
+ end
+
+ expect(page).to have_selector('.atwho-container')
end
- label_item = find('.atwho-view li', text: label.title)
+ it "does not show dropdown when preceded with a special character" do
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys("@")
+ end
- expect_to_wrap(true, label_item, note, label.title)
- end
+ expect(page).to have_selector('.atwho-container')
- it "shows dropdown after a new line" do
- note = find('#note-body')
- page.within '.timeline-content-form' do
- note.native.send_keys('test')
- note.native.send_keys(:enter)
- note.native.send_keys(:enter)
- note.native.send_keys('@')
+ page.within '.timeline-content-form' do
+ note.native.send_keys("@")
+ end
+
+ expect(page).to have_selector('.atwho-container', visible: false)
end
- expect(page).to have_selector('.atwho-container')
- end
+ it "does not throw an error if no labels exist" do
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys('~')
+ end
- it "does not show dropdown when preceded with a special character" do
- note = find('#note-body')
- page.within '.timeline-content-form' do
- note.native.send_keys("@")
+ expect(page).to have_selector('.atwho-container', visible: false)
end
- expect(page).to have_selector('.atwho-container')
+ it 'doesn\'t wrap for assignee values' do
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys("@#{user.username[0]}")
+ end
- page.within '.timeline-content-form' do
- note.native.send_keys("@")
+ user_item = find('.atwho-view li', text: user.username)
+
+ expect_to_wrap(false, user_item, note, user.username)
end
- expect(page).to have_selector('.atwho-container', visible: false)
- end
+ it 'doesn\'t wrap for emoji values' do
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys(":cartwheel_")
+ end
- it "does not throw an error if no labels exist" do
- note = find('#note-body')
- page.within '.timeline-content-form' do
- note.native.send_keys('~')
+ emoji_item = find('.atwho-view li', text: 'cartwheel_tone1')
+
+ expect_to_wrap(false, emoji_item, note, 'cartwheel_tone1')
end
- expect(page).to have_selector('.atwho-container', visible: false)
- end
+ it 'doesn\'t open autocomplete after non-word character' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys("@#{user.username[0..2]}!")
+ end
- it 'doesn\'t wrap for assignee values' do
- note = find('#note-body')
- page.within '.timeline-content-form' do
- note.native.send_keys("@#{user.username[0]}")
+ expect(page).not_to have_selector('.atwho-view')
+ end
+
+ it 'doesn\'t open autocomplete if there is no space before' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys("hello:#{user.username[0..2]}")
+ end
+
+ expect(page).not_to have_selector('.atwho-view')
end
- user_item = find('.atwho-view li', text: user.username)
+ it 'triggers autocomplete after selecting a quick action' do
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys('/as')
+ end
+
+ find('.atwho-view li', text: '/assign')
+ note.native.send_keys(:tab)
- expect_to_wrap(false, user_item, note, user.username)
+ user_item = find('.atwho-view li', text: user.username)
+ expect(user_item).to have_content(user.username)
+ end
end
- it 'doesn\'t wrap for emoji values' do
- note = find('#note-body')
- page.within '.timeline-content-form' do
- note.native.send_keys(":cartwheel_")
+ context 'assignees' do
+ let(:issue_assignee) { create(:issue, project: project) }
+ let(:unassigned_user) { create(:user) }
+
+ before do
+ issue_assignee.update(assignees: [user])
+
+ project.add_maintainer(unassigned_user)
end
- emoji_item = find('.atwho-view li', text: 'cartwheel_tone1')
+ it 'lists users who are currently not assigned to the issue when using /assign' do
+ visit project_issue_path(project, issue_assignee)
- expect_to_wrap(false, emoji_item, note, 'cartwheel_tone1')
- end
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys('/as')
+ end
- it 'doesn\'t open autocomplete after non-word character' do
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys("@#{user.username[0..2]}!")
+ find('.atwho-view li', text: '/assign')
+ note.native.send_keys(:tab)
+
+ wait_for_requests
+
+ expect(find('#at-view-users .atwho-view-ul')).not_to have_content(user.username)
+ expect(find('#at-view-users .atwho-view-ul')).to have_content(unassigned_user.username)
end
- expect(page).not_to have_selector('.atwho-view')
+ it 'shows dropdown on new issue form' do
+ visit new_project_issue_path(project)
+
+ textarea = find('#issue_description')
+ textarea.native.send_keys('/ass')
+ find('.atwho-view li', text: '/assign')
+ textarea.native.send_keys(:tab)
+
+ expect(find('#at-view-users .atwho-view-ul')).to have_content(unassigned_user.username)
+ expect(find('#at-view-users .atwho-view-ul')).to have_content(user.username)
+ end
end
- it 'doesn\'t open autocomplete if there is no space before' do
- page.within '.timeline-content-form' do
- find('#note-body').native.send_keys("hello:#{user.username[0..2]}")
+ context 'labels' do
+ it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do
+ label_xss_title = 'alert label &lt;img src=x onerror="alert(\'Hello xss\');" a'
+ create(:label, project: project, title: label_xss_title)
+
+ note = find('#note-body')
+
+ # It should show all the labels on "~".
+ type(note, '~')
+
+ wait_for_requests
+
+ page.within '.atwho-container #at-view-labels' do
+ expect(find('.atwho-view-ul').text).to have_content('alert label')
+ end
end
- expect(page).not_to have_selector('.atwho-view')
- end
+ it 'allows colons when autocompleting scoped labels' do
+ create(:label, project: project, title: 'scoped:label')
- it 'triggers autocomplete after selecting a quick action' do
- note = find('#note-body')
- page.within '.timeline-content-form' do
- note.native.send_keys('/as')
+ note = find('#note-body')
+ type(note, '~scoped:')
+
+ wait_for_requests
+
+ page.within '.atwho-container #at-view-labels' do
+ expect(find('.atwho-view-ul').text).to have_content('scoped:label')
+ end
end
- find('.atwho-view li', text: '/assign')
- note.native.send_keys(:tab)
+ it 'allows colons when autocompleting scoped labels with double colons' do
+ create(:label, project: project, title: 'scoped::label')
- user_item = find('.atwho-view li', text: user.username)
- expect(user_item).to have_content(user.username)
- end
- end
+ note = find('#note-body')
+ type(note, '~scoped::')
- context 'assignees' do
- let(:issue_assignee) { create(:issue, project: project) }
- let(:unassigned_user) { create(:user) }
+ wait_for_requests
- before do
- issue_assignee.update(assignees: [user])
+ page.within '.atwho-container #at-view-labels' do
+ expect(find('.atwho-view-ul').text).to have_content('scoped::label')
+ end
+ end
+
+ it 'allows spaces when autocompleting multi-word labels' do
+ create(:label, project: project, title: 'Accepting merge requests')
- project.add_maintainer(unassigned_user)
+ note = find('#note-body')
+ type(note, '~Accepting merge')
+
+ wait_for_requests
+
+ page.within '.atwho-container #at-view-labels' do
+ expect(find('.atwho-view-ul').text).to have_content('Accepting merge requests')
+ end
+ end
+
+ it 'only autocompletes the latest label' do
+ create(:label, project: project, title: 'Accepting merge requests')
+ create(:label, project: project, title: 'Accepting job applicants')
+
+ note = find('#note-body')
+ type(note, '~Accepting merge requests foo bar ~Accepting job')
+
+ wait_for_requests
+
+ page.within '.atwho-container #at-view-labels' do
+ expect(find('.atwho-view-ul').text).to have_content('Accepting job applicants')
+ end
+ end
+
+ it 'does not autocomplete labels if no tilde is typed' do
+ create(:label, project: project, title: 'Accepting merge requests')
+
+ note = find('#note-body')
+ type(note, 'Accepting merge')
+
+ wait_for_requests
+
+ expect(page).not_to have_css('.atwho-container #at-view-labels')
+ end
end
- it 'lists users who are currently not assigned to the issue when using /assign' do
- visit project_issue_path(project, issue_assignee)
+ 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
- note = find('#note-body')
- page.within '.timeline-content-form' do
- note.native.send_keys('/as')
+ page.within '.atwho-container' do
+ expect(page).to have_content(object.title)
+
+ find('ul li').click
+ end
+
+ expect(find('.new-note #note-body').value).to include(expected_body)
end
+ end
- find('.atwho-view li', text: '/assign')
- note.native.send_keys(:tab)
+ context 'issues' do
+ let(:object) { issue }
+ let(:expected_body) { object.to_reference }
- wait_for_requests
+ it_behaves_like 'autocomplete suggestions'
+ end
+
+ context 'merge requests' do
+ let(:object) { create(:merge_request, source_project: project) }
+ let(:expected_body) { object.to_reference }
- expect(find('#at-view-users .atwho-view-ul')).not_to have_content(user.username)
- expect(find('#at-view-users .atwho-view-ul')).to have_content(unassigned_user.username)
+ it_behaves_like 'autocomplete suggestions'
end
- it 'shows dropdown on new issue form' do
- visit new_project_issue_path(project)
+ context 'project snippets' do
+ let!(:object) { create(:project_snippet, project: project, title: 'code snippet') }
+ let(:expected_body) { object.to_reference }
- textarea = find('#issue_description')
- textarea.native.send_keys('/ass')
- find('.atwho-view li', text: '/assign')
- textarea.native.send_keys(:tab)
+ 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 }
- expect(find('#at-view-users .atwho-view-ul')).to have_content(unassigned_user.username)
- expect(find('#at-view-users .atwho-view-ul')).to have_content(user.username)
+ it_behaves_like 'autocomplete suggestions'
end
end
- context 'labels' do
- it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do
- create(:label, project: project, title: label_xss_title)
+ describe 'when tribute_autocomplete feature flag is on' do
+ before do
+ stub_feature_flags(tribute_autocomplete: true)
+
+ sign_in(user)
+ visit project_issue_path(project, issue)
- note = find('#note-body')
+ wait_for_requests
+ end
- # It should show all the labels on "~".
- type(note, '~')
+ it 'updates issue description with GFM reference' do
+ find('.js-issuable-edit').click
wait_for_requests
- page.within '.atwho-container #at-view-labels' do
- expect(find('.atwho-view-ul').text).to have_content('alert label')
- end
- end
+ simulate_input('#issue-description', "@#{user.name[0...3]}")
+
+ wait_for_requests
- it 'allows colons when autocompleting scoped labels' do
- create(:label, project: project, title: 'scoped:label')
+ find('.tribute-container .highlight').click
- note = find('#note-body')
- type(note, '~scoped:')
+ click_button 'Save changes'
wait_for_requests
- page.within '.atwho-container #at-view-labels' do
- expect(find('.atwho-view-ul').text).to have_content('scoped:label')
- end
+ expect(find('.description')).to have_content(user.to_reference)
end
- it 'allows colons when autocompleting scoped labels with double colons' do
- create(:label, project: project, title: 'scoped::label')
+ it 'opens autocomplete menu when field starts with text' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('@')
+ end
- note = find('#note-body')
- type(note, '~scoped::')
+ expect(page).to have_selector('.tribute-container')
+ end
+
+ it 'opens autocomplete menu for Username when field starts with text with item escaping HTML characters' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('@ev')
+ end
wait_for_requests
- page.within '.atwho-container #at-view-labels' do
- expect(find('.atwho-view-ul').text).to have_content('scoped::label')
+ expect(page).to have_selector('.tribute-container')
+
+ page.within '.tribute-container ul' do
+ expect(find('li').text).to have_content(user_xss.username)
end
end
- it 'allows spaces when autocompleting multi-word labels' do
- create(:label, project: project, title: 'Accepting merge requests')
+ it 'doesnt open autocomplete menu character is prefixed with text' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('testing')
+ find('#note-body').native.send_keys('@')
+ end
- note = find('#note-body')
- type(note, '~Accepting merge')
+ expect(page).not_to have_selector('.tribute-container')
+ end
+
+ it 'selects the first item for assignee dropdowns' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('@')
+ end
+
+ expect(page).to have_selector('.tribute-container')
wait_for_requests
- page.within '.atwho-container #at-view-labels' do
- expect(find('.atwho-view-ul').text).to have_content('Accepting merge requests')
- end
+ expect(find('.tribute-container ul')).to have_selector('.highlight:first-of-type')
end
- it 'only autocompletes the latest label' do
- create(:label, project: project, title: 'Accepting merge requests')
- create(:label, project: project, title: 'Accepting job applicants')
+ it 'includes items for assignee dropdowns with non-ASCII characters in name' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('')
+ simulate_input('#note-body', "@#{user.name[0...8]}")
+ end
- note = find('#note-body')
- type(note, '~Accepting merge requests foo bar ~Accepting job')
+ expect(page).to have_selector('.tribute-container')
wait_for_requests
- page.within '.atwho-container #at-view-labels' do
- expect(find('.atwho-view-ul').text).to have_content('Accepting job applicants')
- end
+ expect(find('.tribute-container')).to have_content(user.name)
end
- it 'does not autocomplete labels if no tilde is typed' do
- create(:label, project: project, title: 'Accepting merge requests')
+ context 'if a selected value has special characters' do
+ it "shows dropdown after a new line" do
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys('test')
+ note.native.send_keys(:enter)
+ note.native.send_keys(:enter)
+ note.native.send_keys('@')
+ end
- note = find('#note-body')
- type(note, 'Accepting merge')
+ expect(page).to have_selector('.tribute-container')
+ end
- wait_for_requests
+ it "does not show dropdown when preceded with a special character" do
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys("@")
+ end
- expect(page).not_to have_css('.atwho-container #at-view-labels')
- end
- end
+ expect(page).to have_selector('.tribute-container')
- 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)
+ page.within '.timeline-content-form' do
+ note.native.send_keys("@")
+ end
+
+ expect(page).to have_selector('.tribute-container', visible: false)
end
- page.within '.atwho-container' do
- expect(page).to have_content(object.title)
+ it 'doesn\'t wrap for assignee values' do
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys("@#{user.username[0]}")
+ end
- find('ul li').click
+ user_item = find('.tribute-container li', text: user.username)
+
+ expect_to_wrap(false, user_item, note, user.username)
end
- expect(find('.new-note #note-body').value).to include(expected_body)
- end
- end
+ it 'doesn\'t open autocomplete after non-word character' do
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys("@#{user.username[0..2]}!")
+ end
- context 'issues' do
- let(:object) { issue }
- let(:expected_body) { object.to_reference }
+ expect(page).not_to have_selector('.tribute-container')
+ end
- it_behaves_like 'autocomplete suggestions'
- end
+ it 'triggers autocomplete after selecting a quick action' do
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys('/as')
+ end
- context 'merge requests' do
- let(:object) { create(:merge_request, source_project: project) }
- let(:expected_body) { object.to_reference }
+ find('.atwho-view li', text: '/assign')
+ note.native.send_keys(:tab)
+ note.native.send_keys(:right)
- it_behaves_like 'autocomplete suggestions'
- end
+ wait_for_requests
- context 'project snippets' do
- let!(:object) { create(:project_snippet, project: project, title: 'code snippet') }
- let(:expected_body) { object.to_reference }
+ user_item = find('.tribute-container li', text: user.username)
+ expect(user_item).to have_content(user.username)
+ end
+ end
- it_behaves_like 'autocomplete suggestions'
- end
+ context 'assignees' do
+ let(:issue_assignee) { create(:issue, project: project) }
+ let(:unassigned_user) { create(:user) }
- context 'label' do
- let!(:object) { label }
- let(:expected_body) { object.title }
+ before do
+ issue_assignee.update(assignees: [user])
- it_behaves_like 'autocomplete suggestions'
- end
+ project.add_maintainer(unassigned_user)
+ end
+
+ it 'lists users who are currently not assigned to the issue when using /assign' do
+ visit project_issue_path(project, issue_assignee)
+
+ note = find('#note-body')
+ page.within '.timeline-content-form' do
+ note.native.send_keys('/as')
+ end
- context 'milestone' do
- let!(:object) { create(:milestone, project: project) }
- let(:expected_body) { object.to_reference }
+ find('.atwho-view li', text: '/assign')
+ note.native.send_keys(:tab)
+ note.native.send_keys(:right)
- it_behaves_like 'autocomplete suggestions'
+ wait_for_requests
+
+ expect(find('.tribute-container ul')).not_to have_content(user.username)
+ expect(find('.tribute-container ul')).to have_content(unassigned_user.username)
+ end
+ end
end
private
diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb
index ab319daec71..9879703c8bf 100644
--- a/spec/features/issues/issue_detail_spec.rb
+++ b/spec/features/issues/issue_detail_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe 'Issue Detail', :js do
visit project_issue_path(project, issue)
end
- it 'encodes the description to prevent xss issues', quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/207951' do
+ it 'encodes the description to prevent xss issues', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/207951' do
page.within('.issuable-details .detail-page-description') do
image = find('img.js-lazy-loaded')
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index 9e4362bf0e5..ecda80f2483 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -195,7 +195,7 @@ RSpec.describe 'Issue Sidebar' do
end
end
- context 'creating a project label', :js, :quarantine do
+ context 'creating a project label', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27992' do
before do
page.within('.block.labels') do
click_link 'Create project'
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
index f3a6655f397..ee2fbf0865e 100644
--- a/spec/features/issues/move_spec.rb
+++ b/spec/features/issues/move_spec.rb
@@ -97,6 +97,45 @@ RSpec.describe 'issue move to another project' do
end
end
+ context 'service desk issue moved to a project with service desk disabled', :js do
+ let(:project_title) { 'service desk disabled project' }
+ let(:warning_selector) { '.js-alert-moved-from-service-desk-warning' }
+ let(:namespace) { create(:namespace) }
+ let(:regular_project) { create(:project, title: project_title, service_desk_enabled: false) }
+ let(:service_desk_project) { build(:project, :private, namespace: namespace, service_desk_enabled: true) }
+ let(:service_desk_issue) { create(:issue, project: service_desk_project, author: ::User.support_bot) }
+
+ before do
+ allow(Gitlab).to receive(:com?).and_return(true)
+ allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(true)
+ allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(true)
+
+ regular_project.add_reporter(user)
+ service_desk_project.add_reporter(user)
+
+ visit issue_path(service_desk_issue)
+
+ find('.js-move-issue').click
+ wait_for_requests
+ find('.js-move-issue-dropdown-item', text: project_title).click
+ find('.js-move-issue-confirmation-button').click
+ end
+
+ it 'shows an alert after being moved' do
+ expect(page).to have_content('This project does not have Service Desk enabled')
+ end
+
+ it 'does not show an alert after being dismissed' do
+ find("#{warning_selector} .js-close").click
+
+ expect(page).to have_no_selector(warning_selector)
+
+ page.refresh
+
+ expect(page).to have_no_selector(warning_selector)
+ end
+ end
+
def issue_path(issue)
project_issue_path(issue.project, issue)
end
diff --git a/spec/features/issues/service_desk_spec.rb b/spec/features/issues/service_desk_spec.rb
new file mode 100644
index 00000000000..0995aa11654
--- /dev/null
+++ b/spec/features/issues/service_desk_spec.rb
@@ -0,0 +1,163 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Service Desk Issue Tracker', :js do
+ let(:project) { create(:project, :private, service_desk_enabled: true) }
+ let(:user) { create(:user) }
+
+ before do
+ stub_feature_flags(vue_issuables_list: false)
+
+ allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(true)
+ allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(true)
+
+ project.add_maintainer(user)
+ sign_in(user)
+ end
+
+ describe 'navigation to service desk' do
+ before do
+ visit project_path(project)
+ find('.sidebar-top-level-items .shortcuts-issues').click
+ find('.sidebar-sub-level-items a[title="Service Desk"]').click
+ end
+
+ it 'can navigate to the service desk from link in the sidebar' do
+ expect(page).to have_content('Use Service Desk to connect with your users')
+ end
+ end
+
+ describe 'issues list' do
+ context 'when service desk is misconfigured' do
+ before do
+ allow(Gitlab::ServiceDesk).to receive(:supported?).and_return(false)
+ visit service_desk_project_issues_path(project)
+ end
+
+ it 'shows a message to say the configuration is incomplete' do
+ expect(page).to have_css('.empty-state')
+ expect(page).to have_text('Service Desk is enabled but not yet active')
+ expect(page).to have_text('You must set up incoming email before it becomes active')
+ expect(page).to have_link('More information', href: help_page_path('administration/incoming_email', anchor: 'set-it-up'))
+ end
+ end
+
+ context 'when service desk has not been activated' do
+ let(:project_without_service_desk) { create(:project, :private, service_desk_enabled: false) }
+
+ describe 'service desk info content' do
+ context 'when user has permissions to edit project settings' do
+ before do
+ project_without_service_desk.add_maintainer(user)
+ visit service_desk_project_issues_path(project_without_service_desk)
+ end
+
+ it 'displays the large info box, documentation, and a button to configure' do
+ aggregate_failures do
+ expect(page).to have_css('.empty-state')
+ expect(page).to have_link('Read more', href: help_page_path('user/project/service_desk'))
+ expect(page).to have_link('Turn on Service Desk')
+ end
+ end
+ end
+
+ context 'when user does not have permission to edit project settings' do
+ before do
+ project_without_service_desk.add_guest(user)
+ visit service_desk_project_issues_path(project_without_service_desk)
+ end
+
+ it 'does not show a button configure service desk' do
+ expect(page).not_to have_link('Turn on Service Desk')
+ end
+ end
+ end
+ end
+
+ context 'when service desk has been activated' do
+ context 'when there are no issues' do
+ describe 'service desk info content' do
+ before do
+ visit service_desk_project_issues_path(project)
+ end
+
+ it 'displays the large info box, documentation, and the address' do
+ aggregate_failures do
+ expect(page).to have_css('.empty-state')
+ expect(page).to have_link('Read more', href: help_page_path('user/project/service_desk'))
+ expect(page).not_to have_link('Turn on Service Desk')
+ expect(page).to have_content(project.service_desk_address)
+ end
+ end
+
+ context 'when user does not have permission to edit project settings' do
+ before do
+ user_2 = create(:user)
+
+ project.add_guest(user_2)
+ sign_in(user_2)
+ visit service_desk_project_issues_path(project)
+ end
+
+ it 'displays the large info box and the documentation link' do
+ aggregate_failures do
+ expect(page).to have_css('.empty-state')
+ expect(page).to have_link('Read more', href: help_page_path('user/project/service_desk'))
+ expect(page).not_to have_link('Turn on Service Desk')
+ expect(page).not_to have_content(project.service_desk_address)
+ end
+ end
+ end
+ end
+ end
+
+ context 'when there are issues' do
+ let(:support_bot) { User.support_bot }
+ let(:other_user) { create(:user) }
+ let!(:service_desk_issue) { create(:issue, project: project, author: support_bot) }
+ let!(:other_user_issue) { create(:issue, project: project, author: other_user) }
+
+ describe 'service desk info content' do
+ before do
+ visit service_desk_project_issues_path(project)
+ end
+
+ it 'displays the small info box, documentation, a button to configure service desk, and the address' do
+ aggregate_failures do
+ expect(page).to have_css('.non-empty-state')
+ expect(page).to have_link('Read more', href: help_page_path('user/project/service_desk'))
+ expect(page).not_to have_link('Turn on Service Desk')
+ expect(page).to have_content(project.service_desk_address)
+ end
+ end
+ end
+
+ describe 'issues list' do
+ before do
+ visit service_desk_project_issues_path(project)
+ end
+
+ it 'only displays issues created by support bot' do
+ expect(page).to have_selector('.issues-list .issue', count: 1)
+ end
+ end
+
+ describe 'search box' do
+ before do
+ visit service_desk_project_issues_path(project)
+ end
+
+ it 'adds hidden support bot author token' do
+ expect(page).to have_selector('.filtered-search-token .value', text: 'Support Bot', visible: false)
+ end
+
+ it 'support bot author token cannot be deleted' do
+ find('.input-token .filtered-search').native.send_key(:backspace)
+ expect(page).to have_selector('.js-visual-token', count: 1)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb
index f8385f183d2..dfe3a1bf1b3 100644
--- a/spec/features/issues/update_issues_spec.rb
+++ b/spec/features/issues/update_issues_spec.rb
@@ -8,6 +8,7 @@ RSpec.describe 'Multiple issue updating from issues#index', :js do
let!(:user) { create(:user)}
before do
+ stub_feature_flags(vue_issuables_list: false)
project.add_maintainer(user)
sign_in(user)
end
diff --git a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
index a546fb3e85b..617eac88973 100644
--- a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
+++ b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
@@ -31,7 +31,7 @@ RSpec.describe 'User creates branch and merge request on issue page', :js do
end
# In order to improve tests performance, all UI checks are placed in this test.
- it 'shows elements', :quarantine do
+ it 'shows elements', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27993' do
button_create_merge_request = find('.js-create-merge-request')
button_toggle_dropdown = find('.create-mr-dropdown-wrap .dropdown-toggle')
@@ -71,7 +71,7 @@ RSpec.describe 'User creates branch and merge request on issue page', :js do
perform_enqueued_jobs do
select_dropdown_option('create-mr')
- expect(page).to have_content('WIP: Resolve "Cherry-Coloured Funk"')
+ expect(page).to have_content('Draft: Resolve "Cherry-Coloured Funk"')
expect(current_path).to eq(project_merge_request_path(project, MergeRequest.first))
wait_for_requests
@@ -100,7 +100,7 @@ RSpec.describe 'User creates branch and merge request on issue page', :js do
perform_enqueued_jobs do
select_dropdown_option('create-mr', branch_name)
- expect(page).to have_content('WIP: Resolve "Cherry-Coloured Funk"')
+ expect(page).to have_content('Draft: Resolve "Cherry-Coloured Funk"')
expect(page).to have_content('Request to merge custom-branch-name into')
expect(current_path).to eq(project_merge_request_path(project, MergeRequest.first))
@@ -141,7 +141,7 @@ RSpec.describe 'User creates branch and merge request on issue page', :js do
visit project_issue_path(project, issue)
end
- it 'disables the create branch button', :quarantine do
+ it 'disables the create branch button', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27985' do
expect(page).to have_css('.create-mr-dropdown-wrap .unavailable:not(.hidden)')
expect(page).to have_css('.create-mr-dropdown-wrap .available.hidden', visible: false)
expect(page).to have_content /Related merge requests/
diff --git a/spec/features/issues/user_filters_issues_spec.rb b/spec/features/issues/user_filters_issues_spec.rb
index 20ad47b111a..54a600910ef 100644
--- a/spec/features/issues/user_filters_issues_spec.rb
+++ b/spec/features/issues/user_filters_issues_spec.rb
@@ -7,6 +7,8 @@ RSpec.describe 'User filters issues' do
let_it_be(:project) { create(:project_empty_repo, :public) }
before do
+ stub_feature_flags(vue_issuables_list: false)
+
%w[foobar barbaz].each do |title|
create(:issue,
author: user,
diff --git a/spec/features/issues/user_interacts_with_awards_spec.rb b/spec/features/issues/user_interacts_with_awards_spec.rb
index 7b7e087a6d6..35d9db68d32 100644
--- a/spec/features/issues/user_interacts_with_awards_spec.rb
+++ b/spec/features/issues/user_interacts_with_awards_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe 'User interacts with awards' do
visit(project_issue_path(project, issue))
end
- it 'toggles the thumbsup award emoji', :quarantine do
+ it 'toggles the thumbsup award emoji', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27959' do
page.within('.awards') do
thumbsup = page.first('.award-control')
thumbsup.click
@@ -77,7 +77,7 @@ RSpec.describe 'User interacts with awards' do
end
end
- it 'shows the list of award emoji categories', :quarantine do
+ it 'shows the list of award emoji categories', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27991' do
page.within('.awards') do
page.find('.js-add-award').click
end
diff --git a/spec/features/issues/user_sees_breadcrumb_links_spec.rb b/spec/features/issues/user_sees_breadcrumb_links_spec.rb
index 2660101c330..f5793758a9b 100644
--- a/spec/features/issues/user_sees_breadcrumb_links_spec.rb
+++ b/spec/features/issues/user_sees_breadcrumb_links_spec.rb
@@ -7,6 +7,8 @@ RSpec.describe 'New issue breadcrumb' do
let(:user) { project.creator }
before do
+ stub_feature_flags(vue_issuables_list: false)
+
sign_in(user)
visit(new_project_issue_path(project))
end
diff --git a/spec/features/issues/user_sees_empty_state_spec.rb b/spec/features/issues/user_sees_empty_state_spec.rb
index 047c5ca2189..e39369b0150 100644
--- a/spec/features/issues/user_sees_empty_state_spec.rb
+++ b/spec/features/issues/user_sees_empty_state_spec.rb
@@ -6,6 +6,10 @@ RSpec.describe 'Issues > User sees empty state' do
let_it_be(:project) { create(:project, :public) }
let_it_be(:user) { project.creator }
+ before do
+ stub_feature_flags(vue_issuables_list: false)
+ end
+
shared_examples_for 'empty state with filters' do
it 'user sees empty state with filters' do
create(:issue, author: user, project: project)
diff --git a/spec/features/issues/user_sorts_issues_spec.rb b/spec/features/issues/user_sorts_issues_spec.rb
index ec38bf99035..91c6419b464 100644
--- a/spec/features/issues/user_sorts_issues_spec.rb
+++ b/spec/features/issues/user_sorts_issues_spec.rb
@@ -16,6 +16,8 @@ RSpec.describe "User sorts issues" do
let_it_be(:later_due_milestone) { create(:milestone, project: project, due_date: '2013-12-12') }
before do
+ stub_feature_flags(vue_issuables_list: false)
+
create_list(:award_emoji, 2, :upvote, awardable: issue1)
create_list(:award_emoji, 2, :downvote, awardable: issue2)
create(:award_emoji, :downvote, awardable: issue1)
diff --git a/spec/features/issues/user_views_issues_spec.rb b/spec/features/issues/user_views_issues_spec.rb
index 91de813e414..34cea7f3b0b 100644
--- a/spec/features/issues/user_views_issues_spec.rb
+++ b/spec/features/issues/user_views_issues_spec.rb
@@ -6,9 +6,14 @@ RSpec.describe "User views issues" do
let!(:closed_issue) { create(:closed_issue, project: project) }
let!(:open_issue1) { create(:issue, project: project) }
let!(:open_issue2) { create(:issue, project: project) }
+ let!(:moved_open_issue) { create(:issue, project: project, moved_to: create(:issue)) }
let_it_be(:user) { create(:user) }
+ before do
+ stub_feature_flags(vue_issuables_list: false)
+ end
+
shared_examples "opens issue from list" do
it "opens issue" do
click_link(issue.title)
@@ -32,6 +37,7 @@ RSpec.describe "User views issues" do
.and have_content(open_issue1.title)
.and have_content(open_issue2.title)
.and have_no_content(closed_issue.title)
+ .and have_content(moved_open_issue.title)
.and have_no_selector(".js-new-board-list")
end
@@ -62,6 +68,7 @@ RSpec.describe "User views issues" do
.and have_content(closed_issue.title)
.and have_no_content(open_issue1.title)
.and have_no_content(open_issue2.title)
+ .and have_no_content(moved_open_issue.title)
.and have_no_selector(".js-new-board-list")
end
@@ -82,6 +89,8 @@ RSpec.describe "User views issues" do
.and have_content(closed_issue.title)
.and have_content(open_issue1.title)
.and have_content(open_issue2.title)
+ .and have_content(moved_open_issue.title)
+ .and have_no_content('CLOSED (MOVED)')
.and have_no_selector(".js-new-board-list")
end
@@ -116,4 +125,24 @@ RSpec.describe "User views issues" do
context "when not signed in" do
include_examples "public project"
end
+
+ context 'when vue_issuables_list feature is enabled', :js do
+ before do
+ stub_feature_flags(vue_issuables_list: true)
+ end
+
+ context 'when signed in' do
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ end
+
+ include_examples "public project"
+ include_examples "internal project"
+ end
+
+ context 'when not signed in' do
+ include_examples "public project"
+ end
+ end
end
diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb
index 3ab7fbea198..1545cb36e9b 100644
--- a/spec/features/labels_hierarchy_spec.rb
+++ b/spec/features/labels_hierarchy_spec.rb
@@ -23,7 +23,7 @@ RSpec.describe 'Labels Hierarchy', :js do
end
shared_examples 'assigning labels from sidebar' do
- it 'can assign all ancestors labels', :quarantine do
+ it 'can assign all ancestors labels', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27952' do
[grandparent_group_label, parent_group_label, project_label_1].each do |label|
page.within('.block.labels') do
find('.edit-link').click
diff --git a/spec/features/markdown/metrics_spec.rb b/spec/features/markdown/metrics_spec.rb
index 092408c2be0..3e63ae67f19 100644
--- a/spec/features/markdown/metrics_spec.rb
+++ b/spec/features/markdown/metrics_spec.rb
@@ -2,8 +2,9 @@
require 'spec_helper'
-RSpec.describe 'Metrics rendering', :js, :use_clean_rails_memory_store_caching, :sidekiq_inline do
+RSpec.describe 'Metrics rendering', :js, :kubeclient, :use_clean_rails_memory_store_caching, :sidekiq_inline do
include PrometheusHelpers
+ include KubernetesHelpers
include GrafanaApiHelpers
include MetricsDashboardUrlHelpers
@@ -166,6 +167,41 @@ RSpec.describe 'Metrics rendering', :js, :use_clean_rails_memory_store_caching,
end
end
+ context 'for GitLab embedded cluster health metrics' do
+ before do
+ project.add_maintainer(user)
+ import_common_metrics
+ stub_any_prometheus_request_with_response
+
+ allow(Prometheus::ProxyService).to receive(:new).and_call_original
+
+ create(:clusters_applications_prometheus, :installed, cluster: cluster)
+ stub_kubeclient_discover(cluster.platform.api_url)
+ stub_prometheus_request(/prometheus-prometheus-server/, body: prometheus_values_body)
+ stub_prometheus_request(/prometheus\/api\/v1/, body: prometheus_values_body)
+ end
+
+ let_it_be(:cluster) { create(:cluster, :provided_by_gcp, :project, projects: [project], user: user) }
+ let(:params) { [project.namespace.path, project.path, cluster.id] }
+ let(:query_params) { { group: 'Cluster Health', title: 'CPU Usage', y_label: 'CPU (cores)' } }
+ let(:metrics_url) { urls.namespace_project_cluster_url(*params, **query_params) }
+ let(:description) { "# Summary \n[](#{metrics_url})" }
+
+ it 'shows embedded metrics' do
+ visit project_issue_path(project, issue)
+
+ expect(page).to have_css('div.prometheus-graph')
+ expect(page).to have_text(query_params[:title])
+ expect(page).to have_text(query_params[:y_label])
+ expect(page).not_to have_text(metrics_url)
+
+ expect(Prometheus::ProxyService)
+ .to have_received(:new)
+ .with(cluster, 'GET', 'query_range', hash_including('start', 'end', 'step'))
+ .at_least(:once)
+ end
+ end
+
def import_common_metrics
::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
end
diff --git a/spec/features/merge_request/maintainer_edits_fork_spec.rb b/spec/features/merge_request/maintainer_edits_fork_spec.rb
index 7573553b3fb..be12f774c29 100644
--- a/spec/features/merge_request/maintainer_edits_fork_spec.rb
+++ b/spec/features/merge_request/maintainer_edits_fork_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe 'a maintainer edits files on a source-branch of an MR from a fork
end
before do
- stub_feature_flags(web_ide_default: false, single_mr_diff_view: false)
+ stub_feature_flags(single_mr_diff_view: false)
target_project.add_maintainer(user)
sign_in(user)
@@ -37,7 +37,7 @@ RSpec.describe 'a maintainer edits files on a source-branch of an MR from a fork
end
it 'allows committing to the source branch' do
- find('.ace_text-input', visible: false).send_keys('Updated the readme')
+ execute_script("monaco.editor.getModels()[0].setValue('Updated the readme')")
click_button 'Commit changes'
wait_for_requests
diff --git a/spec/features/merge_request/user_approves_spec.rb b/spec/features/merge_request/user_approves_spec.rb
new file mode 100644
index 00000000000..d319fdcb87b
--- /dev/null
+++ b/spec/features/merge_request/user_approves_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Merge request > User approves', :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public, :repository) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+
+ before do
+ project.add_developer(user)
+
+ sign_in(user)
+
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'approves merge request' do
+ click_approval_button('Approve')
+ expect(page).to have_content('Merge request approved')
+
+ verify_approvals_count_on_index!
+
+ click_approval_button('Revoke approval')
+ expect(page).to have_content('No approval required; you can still approve')
+ end
+
+ def verify_approvals_count_on_index!
+ visit(project_merge_requests_path(project, state: :all))
+ expect(page.all('li').any? { |item| item["title"] == "1 approver (you've approved)"}).to be true
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ def click_approval_button(action)
+ page.within('.mr-state-widget') do
+ click_button(action)
+ end
+
+ wait_for_requests
+ end
+end
diff --git a/spec/features/merge_request/user_closes_merge_request_spec.rb b/spec/features/merge_request/user_closes_merge_request_spec.rb
index 669bd989f4f..e6b6778c76e 100644
--- a/spec/features/merge_request/user_closes_merge_request_spec.rb
+++ b/spec/features/merge_request/user_closes_merge_request_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe 'User closes a merge requests', :js do
end
it 'closes a merge request' do
- click_link('Close merge request', match: :first)
+ click_button('Close merge request', match: :first)
expect(page).to have_content(merge_request.title)
expect(page).to have_content('Closed by')
diff --git a/spec/features/merge_request/user_comments_on_diff_spec.rb b/spec/features/merge_request/user_comments_on_diff_spec.rb
index 9cd3c7eaf76..30bf82e3665 100644
--- a/spec/features/merge_request/user_comments_on_diff_spec.rb
+++ b/spec/features/merge_request/user_comments_on_diff_spec.rb
@@ -117,9 +117,58 @@ RSpec.describe 'User comments on a diff', :js do
context 'when adding multiline comments' do
it 'saves a multiline comment' do
click_diff_line(find("[id='#{sample_commit.line_code}']"))
+ add_comment('-13', '+14')
+ end
+
+ context 'when in side-by-side view' do
+ before do
+ visit(diffs_project_merge_request_path(project, merge_request, view: 'parallel'))
+ end
+
+ # In `files/ruby/popen.rb`
+ it 'allows comments for changes involving both sides' do
+ # click +15, select -13 add and verify comment
+ click_diff_line(find('div[data-path="files/ruby/popen.rb"] .new_line a[data-linenumber="15"]').find(:xpath, '../..'), 'right')
+ add_comment('-13', '+15')
+ end
+
+ it 'allows comments to start above hidden lines and end below' do
+ # click +28, select 21 add and verify comment
+ click_diff_line(find('div[data-path="files/ruby/popen.rb"] .new_line a[data-linenumber="28"]').find(:xpath, '../..'), 'right')
+ add_comment('21', '+28')
+ end
+
+ it 'allows comments on previously hidden lines at the top of a file' do
+ # Click -9, expand up, select 1 add and verify comment
+ page.within('[data-path="files/ruby/popen.rb"]') do
+ all('.js-unfold-all')[0].click
+ end
+ click_diff_line(find('div[data-path="files/ruby/popen.rb"] .old_line a[data-linenumber="9"]').find(:xpath, '../..'), 'left')
+ add_comment('1', '-9')
+ end
+
+ it 'allows comments on previously hidden lines the middle of a file' do
+ # Click 27, expand up, select 18, add and verify comment
+ page.within('[data-path="files/ruby/popen.rb"]') do
+ all('.js-unfold-all')[1].click
+ end
+ click_diff_line(find('div[data-path="files/ruby/popen.rb"] .old_line a[data-linenumber="21"]').find(:xpath, '../..'), 'left')
+ add_comment('18', '21')
+ end
+
+ it 'allows comments on previously hidden lines at the bottom of a file' do
+ # Click +28, expand down, select 37 add and verify comment
+ page.within('[data-path="files/ruby/popen.rb"]') do
+ all('.js-unfold-down')[1].click
+ end
+ click_diff_line(find('div[data-path="files/ruby/popen.rb"] .old_line a[data-linenumber="30"]').find(:xpath, '../..'), 'left')
+ add_comment('+28', '37')
+ end
+ end
+ def add_comment(start_line, end_line)
page.within('.discussion-form') do
- find('#comment-line-start option', text: '-13').select_option
+ find('#comment-line-start option', exact_text: start_line).select_option
end
page.within('.js-discussion-note-form') do
@@ -131,7 +180,7 @@ RSpec.describe 'User comments on a diff', :js do
page.within('.notes_holder') do
expect(page).to have_content('Line is wrong')
- expect(page).to have_content('Comment on lines -13 to +14')
+ expect(page).to have_content("Comment on lines #{start_line} to #{end_line}")
end
visit(merge_request_path(merge_request))
diff --git a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
index 34eaca24a01..3cd23764382 100644
--- a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
@@ -88,7 +88,7 @@ RSpec.describe 'Merge request > User creates image diff notes', :js do
create_image_diff_note
end
- it 'shows indicator and avatar badges, and allows collapsing/expanding the discussion notes', :quarantine do
+ it 'shows indicator and avatar badges, and allows collapsing/expanding the discussion notes', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27950' do
indicator = find('.js-image-badge', match: :first)
badge = find('.user-avatar-link .badge', match: :first)
diff --git a/spec/features/merge_request/user_edits_merge_request_spec.rb b/spec/features/merge_request/user_edits_merge_request_spec.rb
index 84ecd6537dc..6c5f508c8c6 100644
--- a/spec/features/merge_request/user_edits_merge_request_spec.rb
+++ b/spec/features/merge_request/user_edits_merge_request_spec.rb
@@ -16,6 +16,75 @@ RSpec.describe 'User edits a merge request', :js do
visit(edit_project_merge_request_path(project, merge_request))
end
+ describe 'Squash commits' do
+ it 'override MR setting if "Required" is saved' do
+ merge_request.update!(squash: false)
+
+ project.project_setting.update!(squash_option: 'always')
+ visit(edit_project_merge_request_path(project, merge_request))
+ click_button('Save changes')
+
+ project.project_setting.update!(squash_option: 'default_off')
+ visit(edit_project_merge_request_path(project, merge_request))
+
+ expect(find("#merge_request_squash").selected?).to be(true)
+ end
+
+ it 'recovers MR squash setting if "Required" is not saved' do
+ merge_request.update!(squash: false)
+
+ project.project_setting.update!(squash_option: 'always')
+ visit(edit_project_merge_request_path(project, merge_request))
+
+ project.project_setting.update!(squash_option: 'default_on')
+ visit(edit_project_merge_request_path(project, merge_request))
+
+ expect(find("#merge_request_squash").selected?).to be(false)
+ end
+
+ it 'does not override MR squash setting if "Do not allow" is saved' do
+ merge_request.update!(squash: true)
+
+ project.project_setting.update!(squash_option: 'never')
+ visit(edit_project_merge_request_path(project, merge_request))
+ click_button('Save changes')
+
+ expect(merge_request.squash).to be true
+ end
+
+ it 'displays "Required in this project" for "Required" project setting squash option' do
+ project.project_setting.update!(squash_option: 'always')
+ visit(edit_project_merge_request_path(project, merge_request))
+
+ expect(page).to have_content('Squash commits when merge request is accepted.')
+ expect(page).to have_content("Required in this project")
+ end
+
+ it 'does not display message for "Allow" project setting squash option' do
+ project.project_setting.update!(squash_option: 'default_off')
+ visit(edit_project_merge_request_path(project, merge_request))
+
+ expect(page).to have_content('Squash commits when merge request is accepted.')
+ expect(page).not_to have_content("Required in this project")
+ end
+
+ it 'does not display message for "Encourage" project setting squash option' do
+ project.project_setting.update!(squash_option: 'default_on')
+ visit(edit_project_merge_request_path(project, merge_request))
+
+ expect(page).to have_content('Squash commits when merge request is accepted.')
+ expect(page).not_to have_content("Required in this project")
+ end
+
+ it 'is hidden for "Do not allow" project setting squash option' do
+ project.project_setting.update!(squash_option: 'never')
+ visit(edit_project_merge_request_path(project, merge_request))
+
+ expect(page).not_to have_content('Squash commits when merge request is accepted.')
+ expect(page).not_to have_css('#merge_request_squash')
+ end
+ end
+
it 'changes the target branch' do
expect(page).to have_content('From master into feature')
diff --git a/spec/features/merge_request/user_posts_diff_notes_spec.rb b/spec/features/merge_request/user_posts_diff_notes_spec.rb
index dbad2f191a1..6ecffb05009 100644
--- a/spec/features/merge_request/user_posts_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_posts_diff_notes_spec.rb
@@ -46,7 +46,7 @@ RSpec.describe 'Merge request > User posts diff notes', :js do
end
context 'with an old line on the left and a new line on the right' do
- it 'allows commenting on the left side', quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/199050' do
+ it 'allows commenting on the left side', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/199050' do
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..'), 'left')
end
@@ -56,7 +56,7 @@ RSpec.describe 'Merge request > User posts diff notes', :js do
end
context 'with an unchanged line on the left and an unchanged line on the right' do
- it 'allows commenting on the left side', quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/196826' do
+ it 'allows commenting on the left side', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/196826' do
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]', match: :first).find(:xpath, '..'), 'left')
end
diff --git a/spec/features/merge_request/user_reopens_merge_request_spec.rb b/spec/features/merge_request/user_reopens_merge_request_spec.rb
index 020929dc416..7866ece84ac 100644
--- a/spec/features/merge_request/user_reopens_merge_request_spec.rb
+++ b/spec/features/merge_request/user_reopens_merge_request_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe 'User reopens a merge requests', :js do
end
it 'reopens a merge request' do
- click_link('Reopen merge request', match: :first)
+ click_button('Reopen merge request', match: :first)
page.within('.status-box') do
expect(page).to have_content('Open')
diff --git a/spec/features/merge_request/user_sees_diff_spec.rb b/spec/features/merge_request/user_sees_diff_spec.rb
index 2229b242d5b..d067fc0ada4 100644
--- a/spec/features/merge_request/user_sees_diff_spec.rb
+++ b/spec/features/merge_request/user_sees_diff_spec.rb
@@ -72,7 +72,7 @@ RSpec.describe 'Merge request > User sees diff', :js do
end
context 'as user who needs to fork' do
- it 'shows fork/cancel confirmation', :sidekiq_might_not_need_inline, quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/196749' do
+ it 'shows fork/cancel confirmation', :sidekiq_might_not_need_inline, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/196749' do
sign_in(user)
visit diffs_project_merge_request_path(project, merge_request)
diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb
index bd140a0643d..ce49e9f4141 100644
--- a/spec/features/merge_request/user_sees_merge_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb
@@ -268,7 +268,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js do
end
end
- context 'view merge request where project has CI set up but no CI status' do
+ context 'view merge request where there is no pipeline yet' do
before do
pipeline = create(:ci_pipeline, project: project,
sha: merge_request.diff_head_sha,
@@ -278,11 +278,11 @@ RSpec.describe 'Merge request > User sees merge widget', :js do
visit project_merge_request_path(project, merge_request)
end
- it 'has pipeline error text' do
+ it 'has pipeline loading state' do
# Wait for the `ci_status` and `merge_check` requests
wait_for_requests
- expect(page).to have_text("Could not retrieve the pipeline status. For troubleshooting steps, read the documentation.")
+ expect(page).to have_text("Checking pipeline status")
end
end
@@ -889,9 +889,9 @@ RSpec.describe 'Merge request > User sees merge widget', :js do
visit project_merge_request_path(project, merge_request)
end
- it 'renders a CI pipeline error' do
+ it 'renders a CI pipeline loading state' do
within '.ci-widget' do
- expect(page).to have_content('Could not retrieve the pipeline status.')
+ expect(page).to have_content('Checking pipeline status')
end
end
end
diff --git a/spec/features/merge_request/user_sees_pipelines_spec.rb b/spec/features/merge_request/user_sees_pipelines_spec.rb
index 2d125087cb6..d693eec91c8 100644
--- a/spec/features/merge_request/user_sees_pipelines_spec.rb
+++ b/spec/features/merge_request/user_sees_pipelines_spec.rb
@@ -38,14 +38,6 @@ RSpec.describe 'Merge request > User sees pipelines', :js do
expect(page).to have_selector('.stage-cell')
end
- it 'pipeline sha does not equal last commit sha' do
- pipeline.update_attribute(:sha, '19e2e9b4ef76b422ce1154af39a91323ccc57434')
- visit project_merge_request_path(project, merge_request)
- wait_for_requests
-
- expect(page.find('.ci-widget')).to have_text("Could not retrieve the pipeline status. For troubleshooting steps, read the documentation.")
- end
-
context 'with a detached merge request pipeline' do
let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
@@ -92,6 +84,111 @@ RSpec.describe 'Merge request > User sees pipelines', :js do
end
end
+ describe 'fork MRs in parent project', :sidekiq_inline do
+ include ProjectForksHelper
+
+ let_it_be(:parent_project) { create(:project, :public, :repository) }
+ let_it_be(:forked_project) { fork_project(parent_project, developer_in_fork, repository: true, target_project: create(:project, :public, :repository)) }
+ let_it_be(:developer_in_parent) { create(:user) }
+ let_it_be(:developer_in_fork) { create(:user) }
+ let_it_be(:reporter_in_parent_and_developer_in_fork) { create(:user) }
+
+ let(:merge_request) do
+ create(:merge_request, :with_detached_merge_request_pipeline,
+ source_project: forked_project, source_branch: 'feature',
+ target_project: parent_project, target_branch: 'master')
+ end
+
+ let(:config) do
+ { test: { script: 'test', rules: [{ if: '$CI_MERGE_REQUEST_ID' }] } }
+ end
+
+ before_all do
+ parent_project.add_developer(developer_in_parent)
+ parent_project.add_reporter(reporter_in_parent_and_developer_in_fork)
+ forked_project.add_developer(developer_in_fork)
+ forked_project.add_developer(reporter_in_parent_and_developer_in_fork)
+ end
+
+ before do
+ stub_ci_pipeline_yaml_file(YAML.dump(config))
+ sign_in(actor)
+ end
+
+ after do
+ parent_project.all_pipelines.delete_all
+ forked_project.all_pipelines.delete_all
+ end
+
+ context 'when actor is a developer in parent project' do
+ let(:actor) { developer_in_parent }
+
+ it 'creates a pipeline in the parent project' do
+ visit project_merge_request_path(parent_project, merge_request)
+
+ create_merge_request_pipeline
+
+ check_pipeline(expected_project: parent_project)
+ check_head_pipeline(expected_project: parent_project)
+ end
+ end
+
+ context 'when actor is a developer in fork project' do
+ let(:actor) { developer_in_fork }
+
+ it 'creates a pipeline in the fork project' do
+ visit project_merge_request_path(parent_project, merge_request)
+
+ create_merge_request_pipeline
+
+ check_pipeline(expected_project: forked_project)
+ check_head_pipeline(expected_project: forked_project)
+ end
+ end
+
+ context 'when actor is a reporter in parent project and a developer in fork project' do
+ let(:actor) { reporter_in_parent_and_developer_in_fork }
+
+ it 'creates a pipeline in the fork project' do
+ visit project_merge_request_path(parent_project, merge_request)
+
+ create_merge_request_pipeline
+
+ check_pipeline(expected_project: forked_project)
+ check_head_pipeline(expected_project: forked_project)
+ end
+ end
+
+ def create_merge_request_pipeline
+ page.within('.merge-request-tabs') { click_link('Pipelines') }
+ click_button('Run Pipeline')
+ end
+
+ def check_pipeline(expected_project:)
+ page.within('.ci-table') do
+ expect(page).to have_selector('.commit', count: 2)
+
+ page.within(first('.commit')) do
+ page.within('.pipeline-tags') do
+ expect(page.find('.js-pipeline-url-link')[:href]).to include(expected_project.full_path)
+ expect(page).to have_content('detached')
+ end
+ page.within('.pipeline-triggerer') do
+ expect(page).to have_link(href: user_path(actor))
+ end
+ end
+ end
+ end
+
+ def check_head_pipeline(expected_project:)
+ page.within('.merge-request-tabs') { click_link('Overview') }
+
+ page.within('.ci-widget-content') do
+ expect(page.find('.pipeline-id')[:href]).to include(expected_project.full_path)
+ end
+ end
+ end
+
describe 'race condition' do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
diff --git a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
index b81c0e49538..0506d190487 100644
--- a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
+++ b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
@@ -155,7 +155,7 @@ RSpec.describe 'User comments on a diff', :js do
end
end
- it 'can apply multiple suggestions as a batch' do
+ it 'can apply multiple suggestions as a batch', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/224100' do
files.each_with_index do |file, index|
page.within("[id='#{file[:hash]}']") do
find("button[title='Show full file']").click
@@ -188,8 +188,7 @@ RSpec.describe 'User comments on a diff', :js do
end
context 'multiple suggestions in expanded lines' do
- # https://gitlab.com/gitlab-org/gitlab/issues/38277
- it 'suggestions are appliable', :quarantine do
+ it 'suggestions are appliable', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/38277' do
diff_file = merge_request.diffs(paths: ['files/ruby/popen.rb']).diff_files.first
hash = Digest::SHA1.hexdigest(diff_file.file_path)
diff --git a/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb b/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
index fab500f47bf..05f4c16ef60 100644
--- a/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
+++ b/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
@@ -27,7 +27,7 @@ RSpec.describe 'Merge request > User toggles whitespace changes', :js do
find('.js-show-diff-settings').click
- expect(find('#show-whitespace')).to be_checked
+ expect(find('#show-whitespace')).not_to be_checked
end
end
end
diff --git a/spec/features/merge_request/user_views_diffs_file_by_file_spec.rb b/spec/features/merge_request/user_views_diffs_file_by_file_spec.rb
new file mode 100644
index 00000000000..c254a142349
--- /dev/null
+++ b/spec/features/merge_request/user_views_diffs_file_by_file_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'User views diffs file-by-file', :js do
+ let(:merge_request) do
+ create(:merge_request_with_diffs, source_project: project, target_project: project, source_branch: 'merge-test')
+ end
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user, view_diffs_file_by_file: true) }
+
+ before do
+ project.add_developer(user)
+
+ sign_in(user)
+
+ visit(diffs_project_merge_request_path(project, merge_request))
+
+ wait_for_requests
+ end
+
+ it 'shows diffs file-by-file' do
+ page.within('#diffs') do
+ expect(page).to have_selector('.file-holder', count: 1)
+ expect(page).to have_selector('.diff-file .file-title', text: '.DS_Store')
+
+ click_button 'Next'
+
+ expect(page).to have_selector('.file-holder', count: 1)
+ expect(page).to have_selector('.diff-file .file-title', text: '.gitignore')
+ end
+ end
+end
diff --git a/spec/features/participants_autocomplete_spec.rb b/spec/features/participants_autocomplete_spec.rb
index 5c29ac870c0..d6f23b21d65 100644
--- a/spec/features/participants_autocomplete_spec.rb
+++ b/spec/features/participants_autocomplete_spec.rb
@@ -36,12 +36,35 @@ RSpec.describe 'Member autocomplete', :js do
let(:noteable) { create(:issue, author: author, project: project) }
before do
+ stub_feature_flags(tribute_autocomplete: false)
visit project_issue_path(project, noteable)
end
include_examples "open suggestions when typing @", 'issue'
end
+ describe 'when tribute_autocomplete feature flag is on' do
+ context 'adding a new note on a Issue' do
+ let(:noteable) { create(:issue, author: author, project: project) }
+
+ before do
+ stub_feature_flags(tribute_autocomplete: true)
+ visit project_issue_path(project, noteable)
+
+ page.within('.new-note') do
+ find('#note-body').send_keys('@')
+ end
+ end
+
+ it 'suggests noteable author and note author' do
+ page.within('.tribute-container', visible: true) do
+ expect(page).to have_content(author.username)
+ expect(page).to have_content(note.author.username)
+ end
+ end
+ end
+ end
+
context 'adding a new note on a Merge Request' do
let(:project) { create(:project, :public, :repository) }
let(:noteable) do
diff --git a/spec/features/profiles/user_edit_preferences_spec.rb b/spec/features/profiles/user_edit_preferences_spec.rb
index e1117d2d420..817228edca7 100644
--- a/spec/features/profiles/user_edit_preferences_spec.rb
+++ b/spec/features/profiles/user_edit_preferences_spec.rb
@@ -56,4 +56,17 @@ RSpec.describe 'User edit preferences profile' do
expect(page).to have_content('Failed to save preferences')
end
end
+
+ describe 'User language' do
+ let(:user) { create(:user, preferred_language: :es) }
+
+ it 'shows the user preferred language by default' do
+ expect(page).to have_select(
+ 'user[preferred_language]',
+ selected: 'Spanish - español',
+ options: Gitlab::I18n::AVAILABLE_LANGUAGES.values,
+ visible: :all
+ )
+ end
+ end
end
diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb
index 2659157d61d..697fccaca34 100644
--- a/spec/features/profiles/user_edit_profile_spec.rb
+++ b/spec/features/profiles/user_edit_profile_spec.rb
@@ -26,7 +26,7 @@ RSpec.describe 'User edit profile' do
fill_in 'user_twitter', with: 'testtwitter'
fill_in 'user_website_url', with: 'testurl'
fill_in 'user_location', with: 'Ukraine'
- fill_in 'user_bio', with: 'I <3 GitLab'
+ fill_in 'user_bio', with: 'I <3 GitLab :tada:'
fill_in 'user_job_title', with: 'Frontend Engineer'
fill_in 'user_organization', with: 'GitLab'
submit_settings
@@ -36,7 +36,8 @@ RSpec.describe 'User edit profile' do
linkedin: 'testlinkedin',
twitter: 'testtwitter',
website_url: 'testurl',
- bio: 'I <3 GitLab',
+ bio: 'I <3 GitLab :tada:',
+ bio_html: '<p data-sourcepos="1:1-1:18" dir="auto">I &lt;3 GitLab <gl-emoji title="party popper" data-name="tada" data-unicode-version="6.0">🎉</gl-emoji></p>',
job_title: 'Frontend Engineer',
organization: 'GitLab'
)
diff --git a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
index d9421631b32..2747b5894dc 100644
--- a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
+++ b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
@@ -65,7 +65,7 @@ RSpec.describe 'User visits the profile preferences page' do
end
describe 'User changes their language', :js do
- it 'creates a flash message', :quarantine do
+ it 'creates a flash message', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/31404' do
select2('en', from: '#user_preferred_language')
click_button 'Save'
diff --git a/spec/features/projects/activity/user_sees_design_activity_spec.rb b/spec/features/projects/activity/user_sees_design_activity_spec.rb
new file mode 100644
index 00000000000..27a52b87178
--- /dev/null
+++ b/spec/features/projects/activity/user_sees_design_activity_spec.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Projects > Activity > User sees design Activity', :js do
+ include DesignManagementTestHelpers
+
+ let_it_be(:uploader) { create(:user) }
+ let_it_be(:editor) { create(:user) }
+ let_it_be(:deleter) { create(:user) }
+ let_it_be(:archiver) { create(:user) }
+
+ def design_activity(user, action)
+ [user.name, user.to_reference, action, 'design'].join(' ')
+ end
+
+ shared_examples 'being able to see design activity' do
+ let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:design) { create(:design, issue: issue) }
+
+ before_all do
+ project.add_developer(user) # implicitly adds a project join event.
+ common_attrs = { project: project, design: design }
+ create(:design_event, :created, author: uploader, **common_attrs)
+ create(:design_event, :updated, author: editor, **common_attrs)
+ create(:design_event, :destroyed, author: deleter, **common_attrs)
+ create(:design_event, :archived, author: archiver, **common_attrs)
+ end
+
+ before do
+ enable_design_management
+ sign_in(user)
+ end
+
+ it 'shows the design comment action in the activity page' do
+ visit activity_project_path(project)
+
+ expect(page).to have_content('joined project')
+ expect(page).to have_content(design_activity(uploader, 'uploaded'))
+ expect(page).to have_content(design_activity(editor, 'revised'))
+ expect(page).to have_content(design_activity(deleter, 'deleted'))
+ expect(page).to have_content(design_activity(archiver, 'archived'))
+ end
+
+ it 'allows filtering out the design events', :aggregate_failures do
+ visit activity_project_path(project, event_filter: EventFilter::ISSUE)
+
+ expect(page).not_to have_content(design_activity(uploader, 'uploaded'))
+ expect(page).not_to have_content(design_activity(editor, 'revised'))
+ expect(page).not_to have_content(design_activity(deleter, 'deleted'))
+ expect(page).not_to have_content(design_activity(archiver, 'archived'))
+ end
+
+ it 'allows filtering in the design events', :aggregate_failures do
+ visit activity_project_path(project, event_filter: EventFilter::DESIGNS)
+
+ expect(page).not_to have_content('joined project')
+ expect(page).to have_content(design_activity(uploader, 'uploaded'))
+ expect(page).to have_content(design_activity(editor, 'revised'))
+ expect(page).to have_content(design_activity(deleter, 'deleted'))
+ expect(page).to have_content(design_activity(archiver, 'archived'))
+ end
+ end
+
+ context 'the project is public' do
+ let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:user) { create(:user) }
+
+ it_behaves_like 'being able to see design activity'
+ end
+
+ context 'the project is private' do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user, developer_projects: [project]) }
+
+ it_behaves_like 'being able to see design activity'
+ end
+end
diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb
index cce656a1260..3032d115a00 100644
--- a/spec/features/projects/blobs/blob_show_spec.rb
+++ b/spec/features/projects/blobs/blob_show_spec.rb
@@ -589,7 +589,7 @@ RSpec.describe 'File blob', :js do
aggregate_failures do
# shows that dashboard yaml is invalid
expect(page).to have_content('Metrics Dashboard YAML definition is invalid:')
- expect(page).to have_content("panel_groups: can't be blank")
+ expect(page).to have_content("panel_groups: should be an array of panel_groups objects")
# shows a learn more link
expect(page).to have_link('Learn more')
diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb
index a369bacc4dd..5aca994f53e 100644
--- a/spec/features/projects/blobs/edit_spec.rb
+++ b/spec/features/projects/blobs/edit_spec.rb
@@ -11,10 +11,6 @@ RSpec.describe 'Editing file blob', :js do
let(:file_path) { project.repository.ls_files(project.repository.root_ref)[1] }
let(:readme_file_path) { 'README.md' }
- before do
- stub_feature_flags(web_ide_default: false)
- end
-
context 'as a developer' do
let(:user) { create(:user) }
let(:role) { :developer }
@@ -36,8 +32,7 @@ RSpec.describe 'Editing file blob', :js do
def fill_editor(content: 'class NextFeature\\nend\\n')
wait_for_requests
- find('#editor')
- execute_script("ace.edit('editor').setValue('#{content}')")
+ execute_script("monaco.editor.getModels()[0].setValue('#{content}')")
end
context 'from MR diff' do
@@ -67,6 +62,15 @@ RSpec.describe 'Editing file blob', :js do
expect(find_by_id('file_path').value).to eq('ci/.gitlab-ci.yml')
end
+ it 'updating file path updates syntax highlighting' do
+ visit project_edit_blob_path(project, tree_join(branch, readme_file_path))
+ expect(find('#editor')['data-mode-id']).to eq('markdown')
+
+ find('#file_path').send_keys('foo.txt') do
+ expect(find('#editor')['data-mode-id']).to eq('plaintext')
+ end
+ end
+
context 'from blob file path' do
before do
visit project_blob_path(project, tree_join(branch, file_path))
diff --git a/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb b/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb
index d250a01c050..a271a4f43a8 100644
--- a/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb
+++ b/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb
@@ -15,8 +15,7 @@ RSpec.describe 'User creates blob in new project', :js do
it 'allows the user to add a new file' do
click_link 'New file'
- find('#editor')
- execute_script('ace.edit("editor").setValue("Hello world")')
+ execute_script("monaco.editor.getModels()[0].setValue('Hello world')")
fill_in(:file_name, with: 'dummy-file')
diff --git a/spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb b/spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb
index 5270774b541..8b43687c71c 100644
--- a/spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb
+++ b/spec/features/projects/blobs/user_follows_pipeline_suggest_nudge_spec.rb
@@ -32,6 +32,8 @@ RSpec.describe 'User follows pipeline suggest nudge spec when feature is enabled
end
it 'displays suggest_gitlab_ci_yml popover' do
+ page.find(:css, '.gitlab-ci-yml-selector').click
+
popover_selector = '.suggest-gitlab-ci-yml'
expect(page).to have_css(popover_selector, visible: true)
diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb
index 3e1006920e7..2e6a366f77a 100644
--- a/spec/features/projects/clusters/gcp_spec.rb
+++ b/spec/features/projects/clusters/gcp_spec.rb
@@ -139,6 +139,19 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do
end
end
+ context 'when a user adds an existing cluster' do
+ before do
+ visit project_clusters_path(project)
+
+ click_link 'Add Kubernetes cluster'
+ click_link 'Add existing cluster'
+ end
+
+ it 'user sees the "Environment scope" field' do
+ expect(page).to have_css('#cluster_environment_scope')
+ end
+ end
+
context 'when user destroys the cluster' do
before do
click_link 'Advanced Settings'
@@ -155,19 +168,6 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do
end
end
- context 'when a user cannot edit the environment scope' do
- before do
- visit project_clusters_path(project)
-
- click_link 'Add Kubernetes cluster'
- click_link 'Add existing cluster'
- end
-
- it 'user does not see the "Environment scope" field' do
- expect(page).not_to have_css('#cluster_environment_scope')
- end
- end
-
context 'when user has not dismissed GCP signup offer' do
before do
visit project_clusters_path(project)
diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb
index 1cf214a5c4e..c56a1ed1711 100644
--- a/spec/features/projects/clusters_spec.rb
+++ b/spec/features/projects/clusters_spec.rb
@@ -25,6 +25,168 @@ RSpec.describe 'Clusters', :js do
end
end
+ context 'when user has a cluster' do
+ before do
+ allow_any_instance_of(Clusters::Cluster).to receive(:retrieve_connection_status).and_return(:connected)
+ end
+
+ context 'when user adds an existing cluster' do
+ before do
+ create(:cluster, :provided_by_user, name: 'default-cluster', environment_scope: '*', projects: [project])
+ visit project_clusters_path(project)
+ end
+
+ it 'user sees an add cluster button' do
+ expect(page).to have_selector('.js-add-cluster:not(.readonly)')
+ end
+
+ context 'when user filled form with environment scope' do
+ before do
+ click_link 'Add Kubernetes cluster'
+ click_link 'Add existing cluster'
+ fill_in 'cluster_name', with: 'staging-cluster'
+ fill_in 'cluster_environment_scope', with: 'staging/*'
+ click_button 'Add Kubernetes cluster'
+ end
+
+ it 'user sees a cluster details page' do
+ expect(page.find_field('cluster[name]').value).to eq('staging-cluster')
+ expect(page.find_field('cluster[environment_scope]').value).to eq('staging/*')
+ end
+ end
+
+ context 'when user updates environment scope' do
+ before do
+ click_link 'default-cluster'
+ fill_in 'cluster_environment_scope', with: 'production/*'
+ within '.js-cluster-integration-form' do
+ click_button 'Save changes'
+ end
+ end
+
+ it 'updates the environment scope' do
+ expect(page.find_field('cluster[environment_scope]').value).to eq('production/*')
+ end
+ end
+
+ context 'when user updates duplicated environment scope' do
+ before do
+ click_link 'Add Kubernetes cluster'
+ click_link 'Add existing cluster'
+ fill_in 'cluster_name', with: 'staging-cluster'
+ fill_in 'cluster_environment_scope', with: '*'
+ fill_in 'cluster_platform_kubernetes_attributes_api_url', with: 'https://0.0.0.0'
+ fill_in 'cluster_platform_kubernetes_attributes_token', with: 'token'
+
+ click_button 'Add Kubernetes cluster'
+ end
+
+ it 'users sees an environment scope validation error' do
+ expect(page).to have_content('cannot add duplicated environment scope')
+ end
+ end
+ end
+
+ context 'when user adds a Google Kubernetes Engine cluster' do
+ before do
+ allow_any_instance_of(Projects::ClustersController)
+ .to receive(:token_in_session).and_return('token')
+ allow_any_instance_of(Projects::ClustersController)
+ .to receive(:expires_at_in_session).and_return(1.hour.since.to_i.to_s)
+
+ allow_any_instance_of(Projects::ClustersController).to receive(:authorize_google_project_billing)
+ allow_any_instance_of(Projects::ClustersController).to receive(:google_project_billing_status).and_return(true)
+
+ allow_any_instance_of(GoogleApi::CloudPlatform::Client)
+ .to receive(:projects_zones_clusters_create) do
+ OpenStruct.new(
+ self_link: 'projects/gcp-project-12345/zones/us-central1-a/operations/ope-123',
+ status: 'RUNNING'
+ )
+ end
+
+ allow(WaitForClusterCreationWorker).to receive(:perform_in).and_return(nil)
+
+ create(:cluster, :provided_by_gcp, name: 'default-cluster', environment_scope: '*', projects: [project])
+ visit project_clusters_path(project)
+ end
+
+ it 'user sees a add cluster button ' do
+ expect(page).to have_selector('.js-add-cluster:not(.readonly)')
+ end
+
+ context 'when user filled form with environment scope' do
+ before do
+ click_link 'Add Kubernetes cluster'
+ click_link 'Create new cluster'
+ click_link 'Google GKE'
+
+ sleep 2 # wait for ajax
+ execute_script('document.querySelector(".js-gcp-project-id-dropdown input").setAttribute("type", "text")')
+ execute_script('document.querySelector(".js-gcp-zone-dropdown input").setAttribute("type", "text")')
+ execute_script('document.querySelector(".js-gcp-machine-type-dropdown input").setAttribute("type", "text")')
+ execute_script('document.querySelector(".js-gke-cluster-creation-submit").removeAttribute("disabled")')
+
+ fill_in 'cluster_name', with: 'staging-cluster'
+ fill_in 'cluster_environment_scope', with: 'staging/*'
+ fill_in 'cluster[provider_gcp_attributes][gcp_project_id]', with: 'gcp-project-123'
+ fill_in 'cluster[provider_gcp_attributes][zone]', with: 'us-central1-a'
+ fill_in 'cluster[provider_gcp_attributes][machine_type]', with: 'n1-standard-2'
+ click_button 'Create Kubernetes cluster'
+
+ # The frontend won't show the details until the cluster is
+ # created, and we don't want to make calls out to GCP.
+ provider = Clusters::Cluster.last.provider
+ provider.make_created
+ end
+
+ it 'user sees a cluster details page' do
+ expect(page).to have_content('GitLab Integration')
+ expect(page.find_field('cluster[environment_scope]').value).to eq('staging/*')
+ end
+ end
+
+ context 'when user updates environment scope' do
+ before do
+ click_link 'default-cluster'
+ fill_in 'cluster_environment_scope', with: 'production/*'
+ within ".js-cluster-integration-form" do
+ click_button 'Save changes'
+ end
+ end
+
+ it 'updates the environment scope' do
+ expect(page.find_field('cluster[environment_scope]').value).to eq('production/*')
+ end
+ end
+
+ context 'when user updates duplicated environment scope' do
+ before do
+ click_link 'Add Kubernetes cluster'
+ click_link 'Create new cluster'
+ click_link 'Google GKE'
+
+ sleep 2 # wait for ajax
+ execute_script('document.querySelector(".js-gcp-project-id-dropdown input").setAttribute("type", "text")')
+ execute_script('document.querySelector(".js-gcp-zone-dropdown input").setAttribute("type", "text")')
+ execute_script('document.querySelector(".js-gcp-machine-type-dropdown input").setAttribute("type", "text")')
+ execute_script('document.querySelector(".js-gke-cluster-creation-submit").removeAttribute("disabled")')
+
+ fill_in 'cluster_name', with: 'staging-cluster'
+ fill_in 'cluster_environment_scope', with: '*'
+ fill_in 'cluster[provider_gcp_attributes][gcp_project_id]', with: 'gcp-project-123'
+ fill_in 'cluster[provider_gcp_attributes][zone]', with: 'us-central1-a'
+ fill_in 'cluster[provider_gcp_attributes][machine_type]', with: 'n1-standard-2'
+ click_button 'Create Kubernetes cluster'
+ end
+
+ it 'users sees an environment scope validation error' do
+ expect(page).to have_content('cannot add duplicated environment scope')
+ end
+ end
+ end
+ end
+
context 'when user has a cluster and visits cluster index page' do
let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:project) { cluster.project }
diff --git a/spec/features/projects/commit/builds_spec.rb b/spec/features/projects/commit/builds_spec.rb
index 13f73b8cf44..f97abc5bd8b 100644
--- a/spec/features/projects/commit/builds_spec.rb
+++ b/spec/features/projects/commit/builds_spec.rb
@@ -6,23 +6,32 @@ RSpec.describe 'project commit pipelines', :js do
let(:project) { create(:project, :repository) }
before do
+ create(:ci_pipeline, project: project,
+ sha: project.commit.sha,
+ ref: 'master')
+
user = create(:user)
project.add_maintainer(user)
sign_in(user)
+
+ visit pipelines_project_commit_path(project, project.commit.sha)
end
context 'when no builds triggered yet' do
- before do
- create(:ci_pipeline, project: project,
- sha: project.commit.sha,
- ref: 'master')
+ it 'shows the ID of the first pipeline' do
+ page.within('.table-holder') do
+ expect(page).to have_content project.ci_pipelines[0].id # pipeline ids
+ end
end
+ end
- it 'user views commit pipelines page' do
- visit pipelines_project_commit_path(project, project.commit.sha)
+ context 'with no related merge requests' do
+ it 'shows the correct text for no related MRs' do
+ wait_for_requests
- page.within('.table-holder') do
- expect(page).to have_content project.ci_pipelines[0].id # pipeline ids
+ page.within('.merge-request-info') do
+ expect(page).not_to have_selector '.spinner'
+ expect(page).to have_content 'No related merge requests found'
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 2796156bfbf..dee964005a4 100644
--- a/spec/features/projects/commits/user_browses_commits_spec.rb
+++ b/spec/features/projects/commits/user_browses_commits_spec.rb
@@ -137,6 +137,33 @@ RSpec.describe 'User browses commits' do
.and have_selector('entry summary', text: commit.description[0..10].delete("\r\n"))
end
+ context "when commit has a filename with pathspec characters" do
+ let(:path) { ':wq' }
+ let(:filename) { File.join(path, 'test.txt') }
+ let(:ref) { project.repository.root_ref }
+ let(:newrev) { project.repository.commit('master').sha }
+ let(:short_newrev) { project.repository.commit('master').short_id }
+ let(:message) { 'Glob characters'}
+
+ before do
+ create_file_in_repo(project, ref, ref, filename, 'Test file', commit_message: message)
+ visit project_commits_path(project, "#{ref}/#{path}", limit: 1)
+ wait_for_requests
+ end
+
+ it 'searches commit', :js do
+ expect(page).to have_content(message)
+
+ fill_in 'commits-search', with: 'bogus12345'
+
+ expect(page).to have_content "Your search didn't match any commits"
+
+ fill_in 'commits-search', with: 'Glob'
+
+ expect(page).to have_content message
+ end
+ end
+
context 'when a commit links to a confidential issue' do
let(:confidential_issue) { create(:issue, confidential: true, title: 'Secret issue!', project: project) }
diff --git a/spec/features/projects/confluence/user_views_confluence_page_spec.rb b/spec/features/projects/confluence/user_views_confluence_page_spec.rb
new file mode 100644
index 00000000000..d39c97291db
--- /dev/null
+++ b/spec/features/projects/confluence/user_views_confluence_page_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'User views the Confluence page' do
+ let_it_be(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+
+ before do
+ sign_in(user)
+ end
+
+ it 'shows the page when the Confluence integration is enabled' do
+ service = create(:confluence_service, project: project)
+
+ visit project_wikis_confluence_path(project)
+
+ element = page.find('.row.empty-state')
+
+ expect(element).to have_link('Go to Confluence', href: service.confluence_url)
+ end
+
+ it 'does not show the page when the Confluence integration disabled' do
+ visit project_wikis_confluence_path(project)
+
+ expect(page).to have_gitlab_http_status(:not_found)
+ end
+end
diff --git a/spec/features/projects/container_registry_spec.rb b/spec/features/projects/container_registry_spec.rb
index f561149d2ad..7514a26f020 100644
--- a/spec/features/projects/container_registry_spec.rb
+++ b/spec/features/projects/container_registry_spec.rb
@@ -84,7 +84,7 @@ RSpec.describe 'Container Registry', :js do
expect(service).to receive(:execute).with(container_repository) { { status: :success } }
expect(Projects::ContainerRepository::DeleteTagsService).to receive(:new).with(container_repository.project, user, tags: ['1']) { service }
- first('[data-testid="singleDeleteButton"]').click
+ first('[data-testid="single-delete-button"]').click
expect(find('.modal .modal-title')).to have_content _('Remove tag')
find('.modal .modal-footer .btn-danger').click
end
diff --git a/spec/features/projects/environments/environment_metrics_spec.rb b/spec/features/projects/environments/environment_metrics_spec.rb
index a3b979d0f42..c72f88205b5 100644
--- a/spec/features/projects/environments/environment_metrics_spec.rb
+++ b/spec/features/projects/environments/environment_metrics_spec.rb
@@ -28,8 +28,9 @@ RSpec.describe 'Environment > Metrics' do
shared_examples 'has environment selector' do
it 'has a working environment selector', :js do
click_link('See metrics')
-
- expect(page).to have_metrics_path(environment)
+ # TODO: See metrics on the sidebar still points to the old metrics URL
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/229277
+ expect(page).to have_current_path(metrics_project_environment_path(project, id: environment.id))
expect(page).to have_css('[data-qa-selector="environments_dropdown"]')
within('[data-qa-selector="environments_dropdown"]') do
@@ -40,7 +41,7 @@ RSpec.describe 'Environment > Metrics' do
click_on(staging.name)
end
- expect(page).to have_metrics_path(staging)
+ expect(page).to have_current_path(project_metrics_dashboard_path(project, environment: staging.id))
wait_for_requests
end
@@ -67,8 +68,4 @@ RSpec.describe 'Environment > Metrics' do
def visit_environment(environment)
visit project_environment_path(environment.project, environment)
end
-
- def have_metrics_path(environment)
- have_current_path(metrics_project_environment_path(project, id: environment.id))
- end
end
diff --git a/spec/features/projects/environments_pod_logs_spec.rb b/spec/features/projects/environments_pod_logs_spec.rb
index 82dafbc6237..c491cd62d85 100644
--- a/spec/features/projects/environments_pod_logs_spec.rb
+++ b/spec/features/projects/environments_pod_logs_spec.rb
@@ -12,11 +12,12 @@ RSpec.describe 'Environment > Pod Logs', :js, :kubeclient do
let(:service) { create(:cluster_platform_kubernetes, :configured) }
before do
- create(:cluster, :provided_by_gcp, environment_scope: '*', projects: [project])
+ cluster = create(:cluster, :provided_by_gcp, environment_scope: '*', projects: [project])
create(:deployment, :success, environment: environment)
stub_kubeclient_pods(environment.deployment_namespace)
stub_kubeclient_logs(pod_name, environment.deployment_namespace, container: 'container-0')
+ stub_kubeclient_nodes_and_nodes_metrics(cluster.platform.api_url)
sign_in(project.owner)
end
@@ -37,7 +38,7 @@ RSpec.describe 'Environment > Pod Logs', :js, :kubeclient do
dropdown_items = find(".dropdown-menu").all(".dropdown-item")
expect(dropdown_items.first).to have_content(environment.name)
- expect(dropdown_items.size).to eq(2)
+ expect(dropdown_items.size).to eq(3)
end
end
diff --git a/spec/features/projects/files/edit_file_soft_wrap_spec.rb b/spec/features/projects/files/edit_file_soft_wrap_spec.rb
index ede22204dbd..fda024e893d 100644
--- a/spec/features/projects/files/edit_file_soft_wrap_spec.rb
+++ b/spec/features/projects/files/edit_file_soft_wrap_spec.rb
@@ -8,8 +8,9 @@ RSpec.describe 'Projects > Files > User uses soft wrap while editing file', :js
user = project.owner
sign_in user
visit project_new_blob_path(project, 'master', file_name: 'test_file-name')
+
page.within('.file-editor.code') do
- find('.ace_text-input', visible: false).send_keys 'Touch water with paw then recoil in horror chase dog then
+ find('.inputarea', visible: false).send_keys 'Touch water with paw then recoil in horror chase dog then
run away chase the pig around the house eat owner\'s food, and knock
dish off table head butt cant eat out of my own dish. Cat is love, cat
is life rub face on everything poop on grasses so meow. Playing with
@@ -26,17 +27,20 @@ RSpec.describe 'Projects > Files > User uses soft wrap while editing file', :js
it 'user clicks the "Soft wrap" button and then "No wrap" button' do
wrapped_content_width = get_content_width
- toggle_button.click
- expect(toggle_button).to have_content 'No wrap'
- unwrapped_content_width = get_content_width
- expect(unwrapped_content_width).to be < wrapped_content_width
-
- toggle_button.click
- expect(toggle_button).to have_content 'Soft wrap'
- expect(get_content_width).to be > unwrapped_content_width
+
+ toggle_button.click do
+ expect(toggle_button).to have_content 'Soft wrap'
+ unwrapped_content_width = get_content_width
+ expect(unwrapped_content_width).to be > wrapped_content_width
+ end
+
+ toggle_button.click do
+ expect(toggle_button).to have_content 'No wrap'
+ expect(get_content_width).to be < unwrapped_content_width
+ end
end
def get_content_width
- find('.ace_content')[:style].slice!(/width: \d+/).slice!(/\d+/).to_i
+ find('.view-lines', visible: false)[:style].slice!(/width: \d+/).slice!(/\d+/).to_i
end
end
diff --git a/spec/features/projects/files/gitignore_dropdown_spec.rb b/spec/features/projects/files/gitignore_dropdown_spec.rb
index dae1164f7f2..5a39f2bcd98 100644
--- a/spec/features/projects/files/gitignore_dropdown_spec.rb
+++ b/spec/features/projects/files/gitignore_dropdown_spec.rb
@@ -25,6 +25,6 @@ RSpec.describe 'Projects > Files > User wants to add a .gitignore file' do
expect(page).to have_css('.gitignore-selector .dropdown-toggle-text', text: 'Apply a template')
expect(page).to have_content('/.bundle')
- expect(page).to have_content('# Gemfile.lock, .ruby-version, .ruby-gemset')
+ expect(page).to have_content('config/initializers/secret_token.rb')
end
end
diff --git a/spec/features/projects/files/user_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb
index 44b5833a8c8..e5259bd88be 100644
--- a/spec/features/projects/files/user_browses_files_spec.rb
+++ b/spec/features/projects/files/user_browses_files_spec.rb
@@ -3,6 +3,8 @@
require "spec_helper"
RSpec.describe "User browses files" do
+ include RepoHelpers
+
let(:fork_message) do
"You're not allowed to make changes to this project directly. "\
"A fork of this project has been created that you can make changes in, so you can submit a merge request."
@@ -339,6 +341,24 @@ RSpec.describe "User browses files" do
end
end
+ context "when browsing a file with pathspec characters" do
+ let(:filename) { ':wq' }
+ let(:newrev) { project.repository.commit('master').sha }
+
+ before do
+ create_file_in_repo(project, 'master', 'master', filename, 'Test file')
+ path = File.join('master', filename)
+
+ visit(project_blob_path(project, path))
+ end
+
+ it "shows a raw file content" do
+ click_link("Open raw")
+
+ expect(source).to eq("") # Body is filled in by gitlab-workhorse
+ end
+ end
+
context "when browsing a raw file" do
before do
path = File.join(RepoHelpers.sample_commit.id, RepoHelpers.sample_blob.path)
diff --git a/spec/features/projects/files/user_creates_files_spec.rb b/spec/features/projects/files/user_creates_files_spec.rb
index cc90f0cf294..5abc048c135 100644
--- a/spec/features/projects/files/user_creates_files_spec.rb
+++ b/spec/features/projects/files/user_creates_files_spec.rb
@@ -14,8 +14,6 @@ RSpec.describe 'Projects > Files > User creates files', :js do
let(:user) { create(:user) }
before do
- stub_feature_flags(web_ide_default: false)
-
project.add_maintainer(user)
sign_in(user)
end
@@ -67,7 +65,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do
file_name = find('#file_name')
file_name.set options[:file_name] || 'README.md'
- find('.ace_text-input', visible: false).send_keys.native.send_keys options[:file_content] || 'Some content'
+ find('.monaco-editor textarea').send_keys.native.send_keys options[:file_content] || 'Some content'
click_button 'Commit changes'
end
@@ -89,7 +87,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do
it 'creates and commit a new file' do
find('#editor')
- execute_script("ace.edit('editor').setValue('*.rbca')")
+ execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:file_name, with: 'not_a_file.md')
fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes')
@@ -105,7 +103,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do
it 'creates and commit a new file with new lines at the end of file' do
find('#editor')
- execute_script('ace.edit("editor").setValue("Sample\n\n\n")')
+ execute_script('monaco.editor.getModels()[0].setValue("Sample\n\n\n")')
fill_in(:file_name, with: 'not_a_file.md')
fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes')
@@ -117,7 +115,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do
find('.js-edit-blob').click
find('#editor')
- expect(evaluate_script('ace.edit("editor").getValue()')).to eq("Sample\n\n\n")
+ expect(evaluate_script('monaco.editor.getModels()[0].getValue()')).to eq("Sample\n\n\n")
end
it 'creates and commit a new file with a directory name' do
@@ -126,7 +124,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do
expect(page).to have_selector('.file-editor')
find('#editor')
- execute_script("ace.edit('editor').setValue('*.rbca')")
+ execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes')
@@ -141,7 +139,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do
expect(page).to have_selector('.file-editor')
find('#editor')
- execute_script("ace.edit('editor').setValue('*.rbca')")
+ execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:file_name, with: 'not_a_file.md')
fill_in(:commit_message, with: 'New commit message', visible: true)
fill_in(:branch_name, with: 'new_branch_name', visible: true)
@@ -176,7 +174,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do
expect(page).to have_selector('.file-editor')
find('#editor')
- execute_script("ace.edit('editor').setValue('*.rbca')")
+ execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:file_name, with: 'not_a_file.md')
fill_in(:commit_message, with: 'New commit message', visible: true)
diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb
index 1bb931e35ec..d3e075001c8 100644
--- a/spec/features/projects/files/user_edits_files_spec.rb
+++ b/spec/features/projects/files/user_edits_files_spec.rb
@@ -11,8 +11,6 @@ RSpec.describe 'Projects > Files > User edits files', :js do
let(:user) { create(:user) }
before do
- stub_feature_flags(web_ide_default: false)
-
sign_in(user)
end
@@ -46,9 +44,9 @@ RSpec.describe 'Projects > Files > User edits files', :js do
find('.file-editor', match: :first)
find('#editor')
- execute_script("ace.edit('editor').setValue('*.rbca')")
+ execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
- expect(evaluate_script('ace.edit("editor").getValue()')).to eq('*.rbca')
+ expect(evaluate_script('monaco.editor.getModels()[0].getValue()')).to eq('*.rbca')
end
it 'does not show the edit link if a file is binary' do
@@ -67,7 +65,7 @@ RSpec.describe 'Projects > Files > User edits files', :js do
find('.file-editor', match: :first)
find('#editor')
- execute_script("ace.edit('editor').setValue('*.rbca')")
+ execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes')
@@ -85,7 +83,7 @@ RSpec.describe 'Projects > Files > User edits files', :js do
find('.file-editor', match: :first)
find('#editor')
- execute_script("ace.edit('editor').setValue('*.rbca')")
+ execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:commit_message, with: 'New commit message', visible: true)
fill_in(:branch_name, with: 'new_branch_name', visible: true)
click_button('Commit changes')
@@ -103,7 +101,7 @@ RSpec.describe 'Projects > Files > User edits files', :js do
find('.file-editor', match: :first)
find('#editor')
- execute_script("ace.edit('editor').setValue('*.rbca')")
+ execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
click_link('Preview changes')
expect(page).to have_css('.line_holder.new')
@@ -148,9 +146,9 @@ RSpec.describe 'Projects > Files > User edits files', :js do
find('.file-editor', match: :first)
find('#editor')
- execute_script("ace.edit('editor').setValue('*.rbca')")
+ execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
- expect(evaluate_script('ace.edit("editor").getValue()')).to eq('*.rbca')
+ expect(evaluate_script('monaco.editor.getModels()[0].getValue()')).to eq('*.rbca')
end
it 'opens the Web IDE in a forked project', :sidekiq_might_not_need_inline do
@@ -178,7 +176,7 @@ RSpec.describe 'Projects > Files > User edits files', :js do
find('.file-editor', match: :first)
find('#editor')
- execute_script("ace.edit('editor').setValue('*.rbca')")
+ execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes')
@@ -207,7 +205,7 @@ RSpec.describe 'Projects > Files > User edits files', :js do
expect(page).not_to have_button('Cancel')
find('#editor')
- execute_script("ace.edit('editor').setValue('*.rbca')")
+ execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:commit_message, with: 'Another commit', visible: true)
click_button('Commit changes')
diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz
index 730e586b278..b93da033aea 100644
--- a/spec/features/projects/import_export/test_project_export.tar.gz
+++ b/spec/features/projects/import_export/test_project_export.tar.gz
Binary files differ
diff --git a/spec/features/projects/issues/design_management/user_paginates_designs_spec.rb b/spec/features/projects/issues/design_management/user_paginates_designs_spec.rb
index f871ca60596..aff8951d9de 100644
--- a/spec/features/projects/issues/design_management/user_paginates_designs_spec.rb
+++ b/spec/features/projects/issues/design_management/user_paginates_designs_spec.rb
@@ -8,33 +8,57 @@ RSpec.describe 'User paginates issue designs', :js do
let(:project) { create(:project_empty_repo, :public) }
let(:issue) { create(:issue, project: project) }
- before do
- enable_design_management
+ context 'design_management_moved flag disabled' do
+ before do
+ stub_feature_flags(design_management_moved: false)
+ enable_design_management
- create_list(:design, 2, :with_file, issue: issue)
+ create_list(:design, 2, :with_file, issue: issue)
+ visit project_issue_path(project, issue)
+ click_link 'Designs'
+ wait_for_requests
+ find('.js-design-list-item', match: :first).click
+ end
- visit project_issue_path(project, issue)
+ it 'paginates to next design' do
+ expect(find('.js-previous-design')[:disabled]).to eq('true')
- click_link 'Designs'
+ page.within(find('.js-design-header')) do
+ expect(page).to have_content('1 of 2')
+ end
- wait_for_requests
+ find('.js-next-design').click
- find('.js-design-list-item', match: :first).click
- end
+ expect(find('.js-previous-design')[:disabled]).not_to eq('true')
- it 'paginates to next design' do
- expect(find('.js-previous-design')[:disabled]).to eq('true')
+ page.within(find('.js-design-header')) do
+ expect(page).to have_content('2 of 2')
+ end
+ end
+ end
- page.within(find('.js-design-header')) do
- expect(page).to have_content('1 of 2')
+ context 'design_management_moved flag enabled' do
+ before do
+ enable_design_management
+ create_list(:design, 2, :with_file, issue: issue)
+ visit project_issue_path(project, issue)
+ find('.js-design-list-item', match: :first).click
end
- find('.js-next-design').click
+ it 'paginates to next design' do
+ expect(find('.js-previous-design')[:disabled]).to eq('true')
+
+ page.within(find('.js-design-header')) do
+ expect(page).to have_content('1 of 2')
+ end
+
+ find('.js-next-design').click
- expect(find('.js-previous-design')[:disabled]).not_to eq('true')
+ expect(find('.js-previous-design')[:disabled]).not_to eq('true')
- page.within(find('.js-design-header')) do
- expect(page).to have_content('2 of 2')
+ page.within(find('.js-design-header')) do
+ expect(page).to have_content('2 of 2')
+ end
end
end
end
diff --git a/spec/features/projects/issues/design_management/user_permissions_upload_spec.rb b/spec/features/projects/issues/design_management/user_permissions_upload_spec.rb
index 902a84afc83..4e45312eac3 100644
--- a/spec/features/projects/issues/design_management/user_permissions_upload_spec.rb
+++ b/spec/features/projects/issues/design_management/user_permissions_upload_spec.rb
@@ -8,17 +8,32 @@ RSpec.describe 'User design permissions', :js do
let(:project) { create(:project_empty_repo, :public) }
let(:issue) { create(:issue, project: project) }
- before do
- enable_design_management
+ context 'design_management_moved flag disabled' do
+ before do
+ enable_design_management
+ stub_feature_flags(design_management_moved: false)
- visit project_issue_path(project, issue)
+ visit project_issue_path(project, issue)
- click_link 'Designs'
+ click_link 'Designs'
- wait_for_requests
+ wait_for_requests
+ end
+
+ it 'user does not have permissions to upload design' do
+ expect(page).not_to have_field('design_file')
+ end
end
- it 'user does not have permissions to upload design' do
- expect(page).not_to have_field('design_file')
+ context 'design_management_moved flag enabled' do
+ before do
+ enable_design_management
+
+ visit project_issue_path(project, issue)
+ end
+
+ it 'user does not have permissions to upload design' do
+ expect(page).not_to have_field('design_file')
+ end
end
end
diff --git a/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb b/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb
index 66b449a9de5..2381e00972f 100644
--- a/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb
+++ b/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb
@@ -13,10 +13,10 @@ RSpec.describe 'User uploads new design', :js do
sign_in(user)
end
- context "when the feature is available" do
+ context 'design_management_moved flag disabled' do
before do
- enable_design_management
-
+ enable_design_management(feature_enabled)
+ stub_feature_flags(design_management_moved: false)
visit project_issue_path(project, issue)
click_link 'Designs'
@@ -24,32 +24,64 @@ RSpec.describe 'User uploads new design', :js do
wait_for_requests
end
- it 'uploads designs' do
- attach_file(:design_file, logo_fixture, make_visible: true)
+ context "when the feature is available" do
+ let(:feature_enabled) { true }
+
+ it 'uploads designs' do
+ attach_file(:design_file, logo_fixture, make_visible: true)
+
+ expect(page).to have_selector('.js-design-list-item', count: 1)
+
+ within first('#designs-tab .js-design-list-item') do
+ expect(page).to have_content('dk.png')
+ end
- expect(page).to have_selector('.js-design-list-item', count: 1)
+ attach_file(:design_file, gif_fixture, make_visible: true)
- within first('#designs-tab .js-design-list-item') do
- expect(page).to have_content('dk.png')
+ expect(page).to have_selector('.js-design-list-item', count: 2)
end
+ end
- attach_file(:design_file, gif_fixture, make_visible: true)
+ context 'when the feature is not available' do
+ let(:feature_enabled) { false }
- expect(page).to have_selector('.js-design-list-item', count: 2)
+ it 'shows the message about requirements' do
+ expect(page).to have_content("To enable design management, you'll need to meet the requirements.")
+ end
end
end
- context 'when the feature is not available' do
+ context 'design_management_moved flag enabled' do
before do
+ enable_design_management(feature_enabled)
+ stub_feature_flags(design_management_moved: true)
visit project_issue_path(project, issue)
+ end
- click_link 'Designs'
+ context "when the feature is available" do
+ let(:feature_enabled) { true }
- wait_for_requests
+ it 'uploads designs', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/225616' do
+ attach_file(:design_file, logo_fixture, make_visible: true)
+
+ expect(page).to have_selector('.js-design-list-item', count: 1)
+
+ within first('[data-testid="designs-root"] .js-design-list-item') do
+ expect(page).to have_content('dk.png')
+ end
+
+ attach_file(:design_file, gif_fixture, make_visible: true)
+
+ expect(page).to have_selector('.js-design-list-item', count: 2)
+ end
end
- it 'shows the message about requirements' do
- expect(page).to have_content("To enable design management, you'll need to meet the requirements.")
+ context 'when the feature is not available' do
+ let(:feature_enabled) { false }
+
+ it 'shows the message about requirements' do
+ expect(page).to have_content("To enable design management, you'll need to meet the requirements.")
+ end
end
end
diff --git a/spec/features/projects/issues/design_management/user_views_design_spec.rb b/spec/features/projects/issues/design_management/user_views_design_spec.rb
index 527442d5339..49245218e81 100644
--- a/spec/features/projects/issues/design_management/user_views_design_spec.rb
+++ b/spec/features/projects/issues/design_management/user_views_design_spec.rb
@@ -9,21 +9,42 @@ RSpec.describe 'User views issue designs', :js do
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:design) { create(:design, :with_file, issue: issue) }
- before do
- enable_design_management
+ context 'design_management_moved flag disabled' do
+ before do
+ enable_design_management
+ stub_feature_flags(design_management_moved: false)
- visit project_issue_path(project, issue)
+ visit project_issue_path(project, issue)
- click_link 'Designs'
+ click_link 'Designs'
+ end
+
+ it 'opens design detail' do
+ click_link design.filename
+
+ page.within(find('.js-design-header')) do
+ expect(page).to have_content(design.filename)
+ end
+
+ expect(page).to have_selector('.js-design-image')
+ end
end
- it 'opens design detail' do
- click_link design.filename
+ context 'design_management_moved flag enabled' do
+ before do
+ enable_design_management
- page.within(find('.js-design-header')) do
- expect(page).to have_content(design.filename)
+ visit project_issue_path(project, issue)
end
- expect(page).to have_selector('.js-design-image')
+ it 'opens design detail' do
+ click_link design.filename
+
+ page.within(find('.js-design-header')) do
+ expect(page).to have_content(design.filename)
+ end
+
+ expect(page).to have_selector('.js-design-image')
+ end
end
end
diff --git a/spec/features/projects/issues/design_management/user_views_designs_spec.rb b/spec/features/projects/issues/design_management/user_views_designs_spec.rb
index d371ae1aad7..772a9ffbe6f 100644
--- a/spec/features/projects/issues/design_management/user_views_designs_spec.rb
+++ b/spec/features/projects/issues/design_management/user_views_designs_spec.rb
@@ -9,39 +9,78 @@ RSpec.describe 'User views issue designs', :js do
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:design) { create(:design, :with_file, issue: issue) }
- before do
- enable_design_management
- end
-
- context 'navigates from the issue view' do
+ context 'design_management_moved flag disabled' do
before do
- visit project_issue_path(project, issue)
- click_link 'Designs'
- wait_for_requests
+ enable_design_management
+ stub_feature_flags(design_management_moved: false)
end
- it 'fetches list of designs' do
- expect(page).to have_selector('.js-design-list-item', count: 1)
+ context 'navigates from the issue view' do
+ before do
+ visit project_issue_path(project, issue)
+ click_link 'Designs'
+ wait_for_requests
+ end
+
+ it 'fetches list of designs' do
+ expect(page).to have_selector('.js-design-list-item', count: 1)
+ end
end
- end
- context 'navigates directly to the design collection view' do
- before do
- visit designs_project_issue_path(project, issue)
+ context 'navigates directly to the design collection view' do
+ before do
+ visit designs_project_issue_path(project, issue)
+ end
+
+ it 'expands the sidebar' do
+ expect(page).to have_selector('.layout-page.right-sidebar-expanded')
+ end
end
- it 'expands the sidebar' do
- expect(page).to have_selector('.layout-page.right-sidebar-expanded')
+ context 'navigates directly to the individual design view' do
+ before do
+ visit designs_project_issue_path(project, issue, vueroute: design.filename)
+ end
+
+ it 'sees the design' do
+ expect(page).to have_selector('.js-design-detail')
+ end
end
end
- context 'navigates directly to the individual design view' do
+ context 'design_management_moved flag enabled' do
before do
- visit designs_project_issue_path(project, issue, vueroute: design.filename)
+ enable_design_management
end
- it 'sees the design' do
- expect(page).to have_selector('.js-design-detail')
+ context 'navigates from the issue view' do
+ before do
+ visit project_issue_path(project, issue)
+ end
+
+ it 'fetches list of designs' do
+ expect(page).to have_selector('.js-design-list-item', count: 1)
+ end
+ end
+
+ context 'navigates directly to the design collection view' do
+ before do
+ visit designs_project_issue_path(project, issue)
+ end
+
+ it 'expands the sidebar' do
+ expect(page).to have_selector('.layout-page.right-sidebar-expanded')
+ end
+ end
+
+ context 'navigates directly to the individual design view' do
+ before do
+ visit designs_project_issue_path(project, issue, vueroute: design.filename)
+ end
+
+ it 'sees the design' do
+ expect(page).to have_selector('.js-design-detail')
+ end
end
end
end
diff --git a/spec/features/projects/issues/design_management/user_views_designs_with_svg_xss_spec.rb b/spec/features/projects/issues/design_management/user_views_designs_with_svg_xss_spec.rb
index 5bc1271309c..0fe84ab47ed 100644
--- a/spec/features/projects/issues/design_management/user_views_designs_with_svg_xss_spec.rb
+++ b/spec/features/projects/issues/design_management/user_views_designs_with_svg_xss_spec.rb
@@ -29,6 +29,7 @@ RSpec.describe 'User views an SVG design that contains XSS', :js do
end
it 'displays the SVG' do
+ find("[data-testid='close-design']").click
expect(page).to have_selector("img.design-img[alt='xss.svg']", count: 1, visible: false)
end
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index e78e8989575..62e8997f6cb 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -837,7 +837,7 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do
it 'renders empty state' do
expect(page).to have_content(job.detailed_status(user).illustration[:title])
- expect(page).not_to have_selector('.js-build-trace')
+ expect(page).not_to have_selector('.job-log')
expect(page).to have_content('This job has been canceled')
end
end
@@ -852,7 +852,7 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do
it 'renders empty state' do
expect(page).to have_content(job.detailed_status(user).illustration[:title])
- expect(page).not_to have_selector('.js-build-trace')
+ expect(page).not_to have_selector('.job-log')
expect(page).to have_content('This job has been skipped')
end
end
diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
index 66d61e629df..85a08c441ca 100644
--- a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
+++ b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe 'Issue prioritization' do
let(:label_5) { create(:label, title: 'label_5', project: project) } # no priority
# According to https://gitlab.com/gitlab-org/gitlab-foss/issues/14189#note_4360653
- context 'when issues have one label' do
+ context 'when issues have one label', :js do
it 'Are sorted properly' do
# Issues
issue_1 = create(:issue, title: 'issue_1', project: project)
@@ -44,7 +44,7 @@ RSpec.describe 'Issue prioritization' do
end
end
- context 'when issues have multiple labels' do
+ context 'when issues have multiple labels', :js do
it 'Are sorted properly' do
# Issues
issue_1 = create(:issue, title: 'issue_1', project: project)
diff --git a/spec/features/projects/members/list_spec.rb b/spec/features/projects/members/list_spec.rb
index f51ebde8f80..56b807e08d7 100644
--- a/spec/features/projects/members/list_spec.rb
+++ b/spec/features/projects/members/list_spec.rb
@@ -64,9 +64,12 @@ RSpec.describe 'Project members list' do
visit_members_page
- accept_confirm do
- find(:css, 'li.project_member', text: other_user.name).find(:css, 'a.btn-remove').click
- end
+ # Open modal
+ find(:css, 'li.project_member', text: other_user.name).find(:css, 'button.btn-remove').click
+
+ expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests'
+
+ click_on('Remove member')
wait_for_requests
diff --git a/spec/features/projects/members/member_leaves_project_spec.rb b/spec/features/projects/members/member_leaves_project_spec.rb
index aa7633c3b28..c4bd0b81dc0 100644
--- a/spec/features/projects/members/member_leaves_project_spec.rb
+++ b/spec/features/projects/members/member_leaves_project_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe 'Projects > Members > Member leaves project' do
expect(project.users.exists?(user.id)).to be_falsey
end
- it 'user leaves project by url param', :js, :quarantine do
+ it 'user leaves project by url param', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/35925' do
visit project_path(project, leave: 1)
page.accept_confirm
diff --git a/spec/features/projects/navbar_spec.rb b/spec/features/projects/navbar_spec.rb
index 94d79d60aeb..22cd832ff06 100644
--- a/spec/features/projects/navbar_spec.rb
+++ b/spec/features/projects/navbar_spec.rb
@@ -12,8 +12,6 @@ RSpec.describe 'Project navbar' do
let_it_be(:project) { create(:project, :repository) }
before do
- stub_licensed_features(service_desk: false)
-
project.add_maintainer(user)
sign_in(user)
end
diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index 921bbbfbe7d..1e2cd3c0a69 100644
--- a/spec/features/projects/pipeline_schedules_spec.rb
+++ b/spec/features/projects/pipeline_schedules_spec.rb
@@ -3,8 +3,6 @@
require 'spec_helper'
RSpec.describe 'Pipeline Schedules', :js do
- include PipelineSchedulesHelper
-
let!(:project) { create(:project, :repository) }
let!(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project ) }
let!(:pipeline) { create(:ci_pipeline, pipeline_schedule: pipeline_schedule) }
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index c6a002ad18b..2ca584ab8f6 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -111,7 +111,7 @@ RSpec.describe 'Pipeline', :js do
end
context 'when there is one related merge request' do
- before do
+ let!(:merge_request) do
create(:merge_request,
source_project: project,
source_branch: pipeline.ref)
@@ -123,7 +123,7 @@ RSpec.describe 'Pipeline', :js do
within '.related-merge-requests' do
expect(page).to have_content('1 related merge request: ')
expect(page).to have_selector('.js-truncated-mr-list')
- expect(page).to have_link('!1 My title 1')
+ expect(page).to have_link("#{merge_request.to_reference} #{merge_request.title}")
expect(page).not_to have_selector('.js-full-mr-list')
expect(page).not_to have_selector('.text-expander')
@@ -132,9 +132,16 @@ RSpec.describe 'Pipeline', :js do
end
context 'when there are two related merge requests' do
- before do
- create(:merge_request, source_project: project, source_branch: pipeline.ref)
- create(:merge_request, source_project: project, source_branch: pipeline.ref, target_branch: 'fix')
+ let!(:merge_request1) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: pipeline.ref)
+ end
+ let!(:merge_request2) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: pipeline.ref,
+ target_branch: 'fix')
end
it 'links to the most recent related merge request' do
@@ -142,7 +149,7 @@ RSpec.describe 'Pipeline', :js do
within '.related-merge-requests' do
expect(page).to have_content('2 related merge requests: ')
- expect(page).to have_link('!2 My title 3')
+ expect(page).to have_link("#{merge_request2.to_reference} #{merge_request2.title}")
expect(page).to have_selector('.text-expander')
expect(page).to have_selector('.js-full-mr-list', visible: false)
end
@@ -354,37 +361,68 @@ RSpec.describe 'Pipeline', :js do
end
describe 'test tabs' do
- let(:pipeline) { create(:ci_pipeline, :with_test_reports, project: project) }
+ let(:pipeline) { create(:ci_pipeline, :with_test_reports, :with_report_results, project: project) }
- before do
- visit_pipeline
- wait_for_requests
- end
+ context 'with build_report_summary feature flag disabled' do
+ before do
+ stub_feature_flags(build_report_summary: false)
+ visit_pipeline
+ wait_for_requests
+ end
+
+ context 'with test reports' do
+ it 'shows badge counter in Tests tab' do
+ expect(pipeline.test_reports.total_count).to eq(4)
+ expect(page.find('.js-test-report-badge-counter').text).to eq(pipeline.test_reports.total_count.to_s)
+ end
- context 'with test reports' do
- it 'shows badge counter in Tests tab' do
- expect(pipeline.test_reports.total_count).to eq(4)
- expect(page.find('.js-test-report-badge-counter').text).to eq(pipeline.test_reports.total_count.to_s)
+ it 'does not call test_report.json endpoint by default', :js do
+ expect(page).to have_selector('.js-no-tests-to-show', visible: :all)
+ end
+
+ it 'does call test_report.json endpoint when tab is selected', :js do
+ find('.js-tests-tab-link').click
+ wait_for_requests
+
+ expect(page).to have_content('Jobs')
+ expect(page).to have_selector('.js-tests-detail', visible: :all)
+ end
end
- it 'does not call test_report.json endpoint by default', :js do
- expect(page).to have_selector('.js-no-tests-to-show', visible: :all)
+ context 'without test reports' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ it 'shows zero' do
+ expect(page.find('.js-test-report-badge-counter', visible: :all).text).to eq("0")
+ end
end
+ end
- it 'does call test_report.json endpoint when tab is selected', :js do
- find('.js-tests-tab-link').click
+ context 'with build_report_summary feature flag enabled' do
+ before do
+ visit_pipeline
wait_for_requests
+ end
+
+ context 'with test reports' do
+ it 'shows badge counter in Tests tab' do
+ expect(page.find('.js-test-report-badge-counter').text).to eq(pipeline.test_report_summary.total_count.to_s)
+ end
- expect(page).to have_content('Test suites')
- expect(page).to have_selector('.js-tests-detail', visible: :all)
+ it 'calls summary.json endpoint', :js do
+ find('.js-tests-tab-link').click
+
+ expect(page).to have_content('Jobs')
+ expect(page).to have_selector('.js-tests-detail', visible: :all)
+ end
end
- end
- context 'without test reports' do
- let(:pipeline) { create(:ci_pipeline, project: project) }
+ context 'without test reports' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
- it 'shows nothing' do
- expect(page.find('.js-test-report-badge-counter', visible: :all).text).to eq("")
+ it 'shows zero' do
+ expect(page.find('.js-test-report-badge-counter', visible: :all).text).to eq("0")
+ end
end
end
end
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 0e33204f851..0eb92f3e679 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -65,19 +65,8 @@ RSpec.describe 'Pipelines', :js do
expect(page.find('.js-pipelines-tab-all .badge').text).to include('1')
end
- it 'shows a tab for Pending pipelines and count' do
- expect(page.find('.js-pipelines-tab-pending').text).to include('Pending')
- expect(page.find('.js-pipelines-tab-pending .badge').text).to include('0')
- end
-
- it 'shows a tab for Running pipelines and count' do
- expect(page.find('.js-pipelines-tab-running').text).to include('Running')
- expect(page.find('.js-pipelines-tab-running .badge').text).to include('1')
- end
-
it 'shows a tab for Finished pipelines and count' do
expect(page.find('.js-pipelines-tab-finished').text).to include('Finished')
- expect(page.find('.js-pipelines-tab-finished .badge').text).to include('0')
end
it 'shows a tab for Branches' do
@@ -89,9 +78,9 @@ RSpec.describe 'Pipelines', :js do
end
it 'updates content when tab is clicked' do
- page.find('.js-pipelines-tab-pending').click
+ page.find('.js-pipelines-tab-finished').click
wait_for_requests
- expect(page).to have_content('There are currently no pending pipelines.')
+ expect(page).to have_content('There are currently no finished pipelines.')
end
end
@@ -539,7 +528,7 @@ RSpec.describe 'Pipelines', :js do
end
it 'renders a mini pipeline graph' do
- expect(page).to have_selector('.js-mini-pipeline-graph')
+ expect(page).to have_selector('[data-testid="widget-mini-pipeline-graph"]')
expect(page).to have_selector('.js-builds-dropdown-button')
end
diff --git a/spec/features/projects/services/disable_triggers_spec.rb b/spec/features/projects/services/disable_triggers_spec.rb
index 8f87d0e7ff1..b3a3d7f0622 100644
--- a/spec/features/projects/services/disable_triggers_spec.rb
+++ b/spec/features/projects/services/disable_triggers_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe 'Disable individual triggers', :js do
include_context 'project service activation'
- let(:checkbox_selector) { 'input[type=checkbox][name$="_events]"]' }
+ let(:checkbox_selector) { 'input[name$="_events]"]' }
before do
visit_project_integration(service_name)
@@ -18,7 +18,7 @@ RSpec.describe 'Disable individual triggers', :js do
event_count = HipchatService.supported_events.count
expect(page).to have_content "Trigger"
- expect(page).to have_css(checkbox_selector, count: event_count)
+ expect(page).to have_css(checkbox_selector, visible: :all, count: event_count)
end
end
@@ -27,7 +27,7 @@ RSpec.describe 'Disable individual triggers', :js do
it "doesn't show unnecessary Trigger checkboxes" do
expect(page).not_to have_content "Trigger"
- expect(page).not_to have_css(checkbox_selector)
+ expect(page).not_to have_css(checkbox_selector, visible: :all)
end
end
end
diff --git a/spec/features/projects/services/user_activates_alerts_spec.rb b/spec/features/projects/services/user_activates_alerts_spec.rb
index 95642f49d61..8b0acdf3618 100644
--- a/spec/features/projects/services/user_activates_alerts_spec.rb
+++ b/spec/features/projects/services/user_activates_alerts_spec.rb
@@ -15,35 +15,32 @@ RSpec.describe 'User activates Alerts', :js do
end
context 'when service is deactivated' do
- it 'activates service' do
+ it 'user cannot activate service' do
visit_project_services
expect(page).to have_link(service_title)
click_link(service_title)
+ expect(page).to have_callout_message
expect(page).not_to have_active_service
-
- click_activate_service
- wait_for_requests
-
- expect(page).to have_active_service
+ expect(page).to have_toggle_active_disabled
end
end
context 'when service is activated' do
+ let_it_be(:activated_alerts_service) do
+ create(:alerts_service, :active, project: project)
+ end
+
before do
visit_alerts_service
- click_activate_service
end
- it 're-generates key' do
- expect(reset_key.value).to be_blank
-
- click_reset_key
- click_confirm_reset_key
- wait_for_requests
-
- expect(reset_key.value).to be_present
+ it 'user cannot change settings' do
+ expect(page).to have_callout_message
+ expect(page).to have_active_service
+ expect(page).to have_toggle_active_disabled
+ expect(page).to have_button_reset_key_disabled
end
end
@@ -57,25 +54,21 @@ RSpec.describe 'User activates Alerts', :js do
visit(edit_project_service_path(project, service_name))
end
- def click_activate_service
- find('#activated').click
- end
-
- def click_reset_key
- click_button('Reset key')
+ def have_callout_message
+ within('.gl-alert') do
+ have_content('You can now manage alert endpoint configuration in the Alerts section on the Operations settings page.')
+ end
end
- def click_confirm_reset_key
- within '.modal-content' do
- click_reset_key
- end
+ def have_active_service
+ have_selector('.js-service-active-status[data-value="true"]')
end
- def reset_key
- find_field('Authorization key')
+ def have_toggle_active_disabled
+ have_selector('#activated .project-feature-toggle.is-disabled')
end
- def have_active_service
- have_selector('.js-service-active-status[data-value="true"]')
+ def have_button_reset_key_disabled
+ have_button('Reset key', disabled: true)
end
end
diff --git a/spec/features/projects/services/user_activates_issue_tracker_spec.rb b/spec/features/projects/services/user_activates_issue_tracker_spec.rb
index a2a2604c610..4f25794d058 100644
--- a/spec/features/projects/services/user_activates_issue_tracker_spec.rb
+++ b/spec/features/projects/services/user_activates_issue_tracker_spec.rb
@@ -30,7 +30,7 @@ RSpec.describe 'User activates issue tracker', :js do
it 'activates the service' do
expect(page).to have_content("#{tracker} activated.")
- expect(current_path).to eq(project_settings_integrations_path(project))
+ expect(current_path).to eq(edit_project_service_path(project, tracker.parameterize(separator: '_')))
end
it 'shows the link in the menu' do
@@ -50,7 +50,7 @@ RSpec.describe 'User activates issue tracker', :js do
click_test_then_save_integration
expect(page).to have_content("#{tracker} activated.")
- expect(current_path).to eq(project_settings_integrations_path(project))
+ expect(current_path).to eq(edit_project_service_path(project, tracker.parameterize(separator: '_')))
end
end
end
@@ -65,7 +65,7 @@ RSpec.describe 'User activates issue tracker', :js do
it 'saves but does not activate the service' do
expect(page).to have_content("#{tracker} settings saved, but not activated.")
- expect(current_path).to eq(project_settings_integrations_path(project))
+ expect(current_path).to eq(edit_project_service_path(project, tracker.parameterize(separator: '_')))
end
it 'does not show the external tracker link in the menu' do
diff --git a/spec/features/projects/services/user_activates_jira_spec.rb b/spec/features/projects/services/user_activates_jira_spec.rb
index 1da8a49699b..483671c4b5b 100644
--- a/spec/features/projects/services/user_activates_jira_spec.rb
+++ b/spec/features/projects/services/user_activates_jira_spec.rb
@@ -4,18 +4,7 @@ require 'spec_helper'
RSpec.describe 'User activates Jira', :js do
include_context 'project service activation'
-
- let(:url) { 'http://jira.example.com' }
- let(:test_url) { 'http://jira.example.com/rest/api/2/serverInfo' }
-
- def fill_form(disable: false)
- click_active_toggle if disable
-
- fill_in 'service_url', with: url
- fill_in 'service_username', with: 'username'
- fill_in 'service_password', with: 'password'
- fill_in 'service_jira_issue_transition_id', with: '25'
- end
+ include_context 'project service Jira context'
describe 'user sets and activates Jira Service' do
context 'when Jira connection test succeeds' do
@@ -30,12 +19,17 @@ RSpec.describe 'User activates Jira', :js do
it 'activates the Jira service' do
expect(page).to have_content('Jira activated.')
- expect(current_path).to eq(project_settings_integrations_path(project))
+ expect(current_path).to eq(edit_project_service_path(project, :jira))
end
- it 'shows the Jira link in the menu' do
- page.within('.nav-sidebar') do
- expect(page).to have_link('Jira', href: url)
+ unless Gitlab.ee?
+ it 'adds Jira link to sidebar menu' do
+ page.within('.nav-sidebar') do
+ expect(page).not_to have_link('Jira Issues')
+ expect(page).not_to have_link('Issue List', visible: false)
+ expect(page).not_to have_link('Open Jira', href: url, visible: false)
+ expect(page).to have_link('Jira', href: url)
+ end
end
end
end
@@ -61,7 +55,7 @@ RSpec.describe 'User activates Jira', :js do
click_test_then_save_integration
expect(page).to have_content('Jira activated.')
- expect(current_path).to eq(project_settings_integrations_path(project))
+ expect(current_path).to eq(edit_project_service_path(project, :jira))
end
end
end
@@ -75,7 +69,7 @@ RSpec.describe 'User activates Jira', :js do
it 'saves but does not activate the Jira service' do
expect(page).to have_content('Jira settings saved, but not activated.')
- expect(current_path).to eq(project_settings_integrations_path(project))
+ expect(current_path).to eq(edit_project_service_path(project, :jira))
end
it 'does not show the Jira link in the menu' do
diff --git a/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb b/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb
index a6b4aaccfb5..6ddffb710a8 100644
--- a/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb
+++ b/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb
@@ -31,7 +31,7 @@ RSpec.describe 'Set up Mattermost slash commands', :js do
click_active_toggle
click_on 'Save changes'
- expect(current_path).to eq(project_settings_integrations_path(project))
+ expect(current_path).to eq(edit_project_service_path(project, :mattermost_slash_commands))
expect(page).to have_content('Mattermost slash commands settings saved, but not activated.')
end
@@ -41,7 +41,7 @@ RSpec.describe 'Set up Mattermost slash commands', :js do
fill_in 'service_token', with: token
click_on 'Save changes'
- expect(current_path).to eq(project_settings_integrations_path(project))
+ expect(current_path).to eq(edit_project_service_path(project, :mattermost_slash_commands))
expect(page).to have_content('Mattermost slash commands activated.')
end
diff --git a/spec/features/projects/services/user_activates_slack_slash_command_spec.rb b/spec/features/projects/services/user_activates_slack_slash_command_spec.rb
index 360e462b935..afe6855d6ad 100644
--- a/spec/features/projects/services/user_activates_slack_slash_command_spec.rb
+++ b/spec/features/projects/services/user_activates_slack_slash_command_spec.rb
@@ -24,7 +24,7 @@ RSpec.describe 'Slack slash commands', :js do
click_active_toggle
click_on 'Save'
- expect(current_path).to eq(project_settings_integrations_path(project))
+ expect(current_path).to eq(edit_project_service_path(project, :slack_slash_commands))
expect(page).to have_content('Slack slash commands settings saved, but not activated.')
end
@@ -32,7 +32,7 @@ RSpec.describe 'Slack slash commands', :js do
fill_in 'Token', with: 'token'
click_on 'Save'
- expect(current_path).to eq(project_settings_integrations_path(project))
+ expect(current_path).to eq(edit_project_service_path(project, :slack_slash_commands))
expect(page).to have_content('Slack slash commands activated.')
end
diff --git a/spec/features/projects/settings/operations_settings_spec.rb b/spec/features/projects/settings/operations_settings_spec.rb
index dfbb6342173..878794bd897 100644
--- a/spec/features/projects/settings/operations_settings_spec.rb
+++ b/spec/features/projects/settings/operations_settings_spec.rb
@@ -45,15 +45,12 @@ RSpec.describe 'Projects > Settings > For a forked project', :js do
it 'updates form values' do
check(create_issue)
- template_select = find_field('Issue template')
- template_select.find(:xpath, 'option[2]').select_option
uncheck(send_email)
save_form
click_expand_incident_management_button
expect(find_field(create_issue)).to be_checked
- expect(page).to have_select('Issue template', selected: 'bug')
expect(find_field(send_email)).not_to be_checked
end
@@ -64,7 +61,7 @@ RSpec.describe 'Projects > Settings > For a forked project', :js do
end
def save_form
- page.within "#edit_project_#{project.id}" do
+ page.within ".qa-incident-management-settings" do
click_on 'Save changes'
end
end
diff --git a/spec/features/projects/settings/registry_settings_spec.rb b/spec/features/projects/settings/registry_settings_spec.rb
index 3dcb7ca54a1..8e2f97fd6a0 100644
--- a/spec/features/projects/settings/registry_settings_spec.rb
+++ b/spec/features/projects/settings/registry_settings_spec.rb
@@ -20,10 +20,10 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p
it 'shows available section' do
settings_block = find('#js-registry-policies')
- expect(settings_block).to have_text 'Container Registry tag expiration policy'
+ expect(settings_block).to have_text 'Cleanup policy for tags'
end
- it 'saves expiration policy submit the form' do
+ it 'saves cleanup policy submit the form' do
within '#js-registry-policies' do
within '.card-body' do
select('7 days until tags are automatically removed', from: 'Expiration interval:')
@@ -36,10 +36,10 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p
submit_button.click
end
toast = find('.gl-toast')
- expect(toast).to have_content('Expiration policy successfully saved.')
+ expect(toast).to have_content('Cleanup policy successfully saved.')
end
- it 'does not save expiration policy submit form with invalid regex' do
+ it 'does not save cleanup policy submit form with invalid regex' do
within '#js-registry-policies' do
within '.card-body' do
fill_in('Tags with names matching this regex pattern will expire:', with: '*-production')
@@ -49,7 +49,7 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p
submit_button.click
end
toast = find('.gl-toast')
- expect(toast).to have_content('Something went wrong while updating the expiration policy.')
+ expect(toast).to have_content('Something went wrong while updating the cleanup policy.')
end
end
diff --git a/spec/features/projects/settings/service_desk_setting_spec.rb b/spec/features/projects/settings/service_desk_setting_spec.rb
new file mode 100644
index 00000000000..7856ab1fb4e
--- /dev/null
+++ b/spec/features/projects/settings/service_desk_setting_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Service Desk Setting', :js do
+ let(:project) { create(:project_empty_repo, :private, service_desk_enabled: false) }
+ let(:presenter) { project.present(current_user: user) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+
+ allow_any_instance_of(Project).to receive(:present).with(current_user: user).and_return(presenter)
+ allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true }
+ allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
+
+ visit edit_project_path(project)
+ end
+
+ it 'shows activation checkbox' do
+ expect(page).to have_selector("#service-desk-checkbox")
+ end
+
+ it 'shows incoming email after activating' do
+ find("#service-desk-checkbox").click
+ wait_for_requests
+ project.reload
+ expect(project.service_desk_enabled).to be_truthy
+ expect(project.service_desk_address).to be_present
+ expect(find('.incoming-email').value).to eq(project.service_desk_address)
+ 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 3fc1f47d98a..e97e4a2030a 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
@@ -28,6 +28,22 @@ RSpec.describe 'Projects > Settings > User manages merge request settings' do
end
end
+ it 'shows Squash commit options', :aggregate_failures do
+ page.within '#js-merge-request-settings' do
+ expect(page).to have_content 'Do not allow'
+ expect(page).to have_content 'Squashing is never performed and the checkbox is hidden.'
+
+ expect(page).to have_content 'Allow'
+ expect(page).to have_content 'Checkbox is visible and unselected by default.'
+
+ expect(page).to have_content 'Encourage'
+ expect(page).to have_content 'Checkbox is visible and selected by default.'
+
+ expect(page).to have_content 'Require'
+ expect(page).to have_content 'Squashing is always performed. Checkbox is visible and selected, and users cannot change it.'
+ end
+ end
+
context 'when Merge Request and Pipelines are initially enabled', :js do
context 'when Pipelines are initially enabled' do
it 'shows the Merge Requests settings' do
@@ -130,4 +146,56 @@ RSpec.describe 'Projects > Settings > User manages merge request settings' do
expect(project.remove_source_branch_after_merge).to be(false)
end
end
+
+ describe 'Squash commits when merging', :js do
+ it 'initially has :squash_option set to :default_off' do
+ radio = find_field('project_project_setting_attributes_squash_option_default_off')
+ expect(radio).to be_checked
+ end
+
+ it 'allows :squash_option to be set to :default_on' do
+ choose('project_project_setting_attributes_squash_option_default_on')
+
+ within('.merge-request-settings-form') do
+ find('.qa-save-merge-request-changes')
+ click_on('Save changes')
+ end
+
+ find('.flash-notice')
+ radio = find_field('project_project_setting_attributes_squash_option_default_on')
+
+ expect(radio).to be_checked
+ expect(project.reload.project_setting.squash_option).to eq('default_on')
+ end
+
+ it 'allows :squash_option to be set to :always' do
+ choose('project_project_setting_attributes_squash_option_always')
+
+ within('.merge-request-settings-form') do
+ find('.qa-save-merge-request-changes')
+ click_on('Save changes')
+ end
+
+ find('.flash-notice')
+ radio = find_field('project_project_setting_attributes_squash_option_always')
+
+ expect(radio).to be_checked
+ expect(project.reload.project_setting.squash_option).to eq('always')
+ end
+
+ it 'allows :squash_option to be set to :never' do
+ choose('project_project_setting_attributes_squash_option_never')
+
+ within('.merge-request-settings-form') do
+ find('.qa-save-merge-request-changes')
+ click_on('Save changes')
+ end
+
+ find('.flash-notice')
+ radio = find_field('project_project_setting_attributes_squash_option_never')
+
+ expect(radio).to be_checked
+ expect(project.reload.project_setting.squash_option).to eq('never')
+ end
+ end
end
diff --git a/spec/features/projects/settings/user_manages_project_members_spec.rb b/spec/features/projects/settings/user_manages_project_members_spec.rb
index d32f4cb8ec7..3836b95a28a 100644
--- a/spec/features/projects/settings/user_manages_project_members_spec.rb
+++ b/spec/features/projects/settings/user_manages_project_members_spec.rb
@@ -16,15 +16,20 @@ RSpec.describe 'Projects > Settings > User manages project members' do
sign_in(user)
end
- it 'cancels a team member' do
+ it 'cancels a team member', :js do
visit(project_project_members_path(project))
project_member = project.project_members.find_by(user_id: user_dmitriy.id)
page.within("#project_member_#{project_member.id}") do
- click_link('Remove user from project')
+ # Open modal
+ click_on('Remove user from project')
end
+ expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests'
+
+ click_on('Remove member')
+
visit(project_project_members_path(project))
expect(page).not_to have_content(user_dmitriy.name)
diff --git a/spec/features/projects/show/user_sees_git_instructions_spec.rb b/spec/features/projects/show/user_sees_git_instructions_spec.rb
index a35f6420bdc..3b77fd7eebf 100644
--- a/spec/features/projects/show/user_sees_git_instructions_spec.rb
+++ b/spec/features/projects/show/user_sees_git_instructions_spec.rb
@@ -18,6 +18,8 @@ RSpec.describe 'Projects > Show > User sees Git instructions' do
page.within '.empty-wrapper' do
expect(page).to have_content('Command line instructions')
end
+
+ expect(page).to have_content("git push -u origin master")
end
end
@@ -59,6 +61,26 @@ RSpec.describe 'Projects > Show > User sees Git instructions' do
include_examples 'shows details of empty project with no repo'
end
+ context ":default_branch_name is specified" do
+ let_it_be(:project) { create(:project, :public) }
+
+ before do
+ expect(Gitlab::CurrentSettings)
+ .to receive(:default_branch_name)
+ .at_least(:once)
+ .and_return('example_branch')
+
+ sign_in(project.owner)
+ visit project_path(project)
+ end
+
+ it "recommends default_branch_name instead of master" do
+ click_link 'Create empty repository'
+
+ expect(page).to have_content("git push -u origin example_branch")
+ end
+ end
+
context 'when project is empty' do
let_it_be(:project) { create(:project_empty_repo, :public) }
diff --git a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
index 0f10b0a4010..afa9de5ce86 100644
--- a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
+++ b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
@@ -63,6 +63,23 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do
expect(page).to have_link('Add LICENSE', href: presenter.add_license_path)
end
end
+
+ context 'Gitlab::CurrentSettings.default_branch_name is available' do
+ before do
+ expect(Gitlab::CurrentSettings)
+ .to receive(:default_branch_name)
+ .at_least(:once)
+ .and_return('example_branch')
+
+ visit project_path(project)
+ end
+
+ it '"New file" button linked to new file page' do
+ page.within('.project-buttons') do
+ expect(page).to have_link('New file', href: project_new_blob_path(project, 'example_branch'))
+ end
+ end
+ end
end
end
diff --git a/spec/features/projects/tree/tree_show_spec.rb b/spec/features/projects/tree/tree_show_spec.rb
index 388fa39874d..bd2af66710a 100644
--- a/spec/features/projects/tree/tree_show_spec.rb
+++ b/spec/features/projects/tree/tree_show_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe 'Projects tree', :js do
+ include RepoHelpers
+
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:gravatar_enabled) { true }
@@ -47,6 +49,30 @@ RSpec.describe 'Projects tree', :js do
expect(page).not_to have_selector('.flash-alert')
end
+ context "with a tree that contains pathspec characters" do
+ let(:path) { ':wq' }
+ let(:filename) { File.join(path, 'test.txt') }
+ let(:newrev) { project.repository.commit('master').sha }
+ let(:short_newrev) { project.repository.commit('master').short_id }
+ let(:message) { 'Glob characters'}
+
+ before do
+ create_file_in_repo(project, 'master', 'master', filename, 'Test file', commit_message: message)
+ visit project_tree_path(project, File.join('master', path))
+ wait_for_requests
+ end
+
+ it "renders tree table without errors" do
+ expect(page).to have_selector('.tree-item')
+ expect(page).to have_content('test.txt')
+ expect(page).to have_content(message)
+
+ # Check last commit
+ expect(find('.commit-content').text).to include(message)
+ expect(find('.commit-sha-group').text).to eq(short_newrev)
+ end
+ end
+
context 'gravatar disabled' do
let(:gravatar_enabled) { false }
diff --git a/spec/features/projects/user_sees_sidebar_spec.rb b/spec/features/projects/user_sees_sidebar_spec.rb
index cc2a9eacbad..1d443e0b339 100644
--- a/spec/features/projects/user_sees_sidebar_spec.rb
+++ b/spec/features/projects/user_sees_sidebar_spec.rb
@@ -6,6 +6,10 @@ RSpec.describe 'Projects > User sees sidebar' do
let(:user) { create(:user) }
let(:project) { create(:project, :private, public_builds: false, namespace: user.namespace) }
+ before do
+ stub_feature_flags(vue_issuables_list: false)
+ end
+
# NOTE: See documented behaviour https://design.gitlab.com/regions/navigation#contextual-navigation
context 'on different viewports', :js do
include MobileHelpers
diff --git a/spec/features/projects/user_sees_user_popover_spec.rb b/spec/features/projects/user_sees_user_popover_spec.rb
index 851ce79e1c6..9cfc6234969 100644
--- a/spec/features/projects/user_sees_user_popover_spec.rb
+++ b/spec/features/projects/user_sees_user_popover_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe 'User sees user popover', :js do
subject { page }
describe 'hovering over a user link in a merge request' do
- let(:popover_selector) { '.user-popover' }
+ let(:popover_selector) { '[data-testid="user-popover"]' }
before do
visit project_merge_request_path(project, merge_request)
diff --git a/spec/features/projects/wiki/user_views_wiki_empty_spec.rb b/spec/features/projects/wiki/user_views_wiki_empty_spec.rb
index d9f79162c19..0af40a2d760 100644
--- a/spec/features/projects/wiki/user_views_wiki_empty_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_empty_spec.rb
@@ -4,18 +4,19 @@ require 'spec_helper'
RSpec.describe 'User views empty wiki' do
let(:user) { create(:user) }
+ let(:confluence_link) { 'Enable the Confluence Wiki integration' }
+ let(:element) { page.find('.row.empty-state') }
shared_examples 'empty wiki and accessible issues' do
it 'show "issue tracker" message' do
visit(project_wikis_path(project))
- element = page.find('.row.empty-state')
-
expect(element).to have_content('This project has no wiki pages')
expect(element).to have_content('You must be a project member')
expect(element).to have_content('improve the wiki for this project')
expect(element).to have_link("issue tracker", href: project_issues_path(project))
expect(element).to have_link("Suggest wiki improvement", href: new_project_issue_path(project))
+ expect(element).to have_no_link(confluence_link)
end
end
@@ -23,11 +24,10 @@ RSpec.describe 'User views empty wiki' do
it 'does not show "issue tracker" message' do
visit(project_wikis_path(project))
- element = page.find('.row.empty-state')
-
expect(element).to have_content('This project has no wiki pages')
expect(element).to have_content('You must be a project member')
expect(element).to have_no_link('Suggest wiki improvement')
+ expect(element).to have_no_link(confluence_link)
end
end
@@ -60,16 +60,15 @@ RSpec.describe 'User views empty wiki' do
end
context 'when user is logged in and a member' do
- let(:project) { create(:project, :public, :wiki_repo) }
+ let(:project) { create(:project, :public) }
before do
sign_in(user)
project.add_developer(user)
end
- it 'show "create first page" message' do
+ it 'shows "create first page" message' do
visit(project_wikis_path(project))
- element = page.find('.row.empty-state')
expect(element).to have_content('your project', count: 2)
@@ -77,5 +76,34 @@ RSpec.describe 'User views empty wiki' do
expect(page).to have_button('Create page')
end
+
+ it 'does not show the "enable confluence" button' do
+ visit(project_wikis_path(project))
+
+ expect(element).to have_no_link(confluence_link)
+ end
+ end
+
+ context 'when user is logged in and an admin' do
+ let(:project) { create(:project, :public, :wiki_repo) }
+
+ before do
+ sign_in(user)
+ project.add_maintainer(user)
+ end
+
+ it 'shows the "enable confluence" button' do
+ visit(project_wikis_path(project))
+
+ expect(element).to have_link(confluence_link)
+ end
+
+ it 'does not show "enable confluence" button if confluence is already enabled' do
+ create(:confluence_service, project: project)
+
+ visit(project_wikis_path(project))
+
+ expect(element).to have_no_link(confluence_link)
+ end
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 59ccb83a9bb..e93689af0aa 100644
--- a/spec/features/projects/wiki/user_views_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_page_spec.rb
@@ -8,9 +8,10 @@ RSpec.describe 'User views a wiki page' do
let(:user) { create(:user) }
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
let(:path) { 'image.png' }
+ let(:wiki) { project.wiki }
let(:wiki_page) do
create(:wiki_page,
- wiki: project.wiki,
+ wiki: wiki,
title: 'home', content: "Look at this [image](#{path})\n\n ![alt text](#{path})")
end
@@ -70,11 +71,13 @@ RSpec.describe 'User views a wiki page' do
click_on('Page history')
- page.within(:css, '.nav-text') do
+ within('.nav-text') do
expect(page).to have_content('History')
end
- find('a[href*="?version_id"]')
+ within('.wiki-history') do
+ expect(page).to have_css('a[href*="?version_id"]', count: 4)
+ end
end
end
@@ -92,8 +95,8 @@ RSpec.describe 'User views a wiki page' do
let(:path) { upload_file_to_wiki(project, user, 'dk.png') }
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}")
+ expect(page).to have_xpath("//img[@data-src='#{wiki.wiki_base_path}/#{path}']")
+ expect(page).to have_link('image', href: "#{wiki.wiki_base_path}/#{path}")
click_on('image')
@@ -103,7 +106,7 @@ RSpec.describe 'User views a wiki page' do
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}")
+ expect(page).to have_link('image', href: "#{wiki.wiki_base_path}/#{path}")
click_on('image')
@@ -114,7 +117,7 @@ RSpec.describe 'User views a wiki page' do
context 'when a page has history' do
before do
- wiki_page.update(message: 'updated home', content: 'updated [some link](other-page)')
+ wiki_page.update(message: 'updated home', content: 'updated [some link](other-page)') # rubocop:disable Rails/SaveBang
end
it 'shows the page history' do
@@ -134,13 +137,74 @@ RSpec.describe 'User views a wiki page' do
expect(page).not_to have_selector('a.btn', text: 'Edit')
end
+
+ context 'show the diff' do
+ def expect_diff_links(commit)
+ diff_path = wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff)
+
+ expect(page).to have_link('Hide whitespace changes', href: "#{diff_path}&w=1")
+ expect(page).to have_link('Inline', href: "#{diff_path}&view=inline")
+ expect(page).to have_link('Side-by-side', href: "#{diff_path}&view=parallel")
+ expect(page).to have_link("View page @ #{commit.short_id}", href: wiki_page_path(wiki, wiki_page, version_id: commit))
+ expect(page).to have_css('.diff-file[data-blob-diff-path="%s"]' % diff_path)
+ end
+
+ it 'links to the correct diffs' do
+ visit project_wiki_history_path(project, wiki_page)
+
+ commit1 = wiki.commit('HEAD^')
+ commit2 = wiki.commit
+
+ expect(page).to have_link('created page: home', href: wiki_page_path(wiki, wiki_page, version_id: commit1, action: :diff))
+ expect(page).to have_link('updated home', href: wiki_page_path(wiki, wiki_page, version_id: commit2, action: :diff))
+ end
+
+ it 'between the current and the previous version of a page' do
+ commit = wiki.commit
+ visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff)
+
+ expect(page).to have_content('by John Doe')
+ expect(page).to have_content('updated home')
+ expect(page).to have_content('Showing 1 changed file with 1 addition and 3 deletions')
+ expect(page).to have_content('some link')
+
+ expect_diff_links(commit)
+ end
+
+ it 'between two old versions of a page' do
+ wiki_page.update(message: 'latest home change', content: 'updated [another link](other-page)') # rubocop:disable Rails/SaveBang:
+ commit = wiki.commit('HEAD^')
+ visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff)
+
+ expect(page).to have_content('by John Doe')
+ expect(page).to have_content('updated home')
+ expect(page).to have_content('Showing 1 changed file with 1 addition and 3 deletions')
+ expect(page).to have_content('some link')
+ expect(page).not_to have_content('latest home change')
+ expect(page).not_to have_content('another link')
+
+ expect_diff_links(commit)
+ end
+
+ it 'for the oldest version of a page' do
+ commit = wiki.commit('HEAD^')
+ visit wiki_page_path(wiki, wiki_page, version_id: commit, action: :diff)
+
+ expect(page).to have_content('by John Doe')
+ expect(page).to have_content('created page: home')
+ expect(page).to have_content('Showing 1 changed file with 4 additions and 0 deletions')
+ expect(page).to have_content('Look at this')
+
+ expect_diff_links(commit)
+ end
+ end
end
context 'when a page has special characters in its title' do
let(:title) { '<foo> !@#$%^&*()[]{}=_+\'"\\|<>? <bar>' }
before do
- wiki_page.update(title: title )
+ wiki_page.update(title: title ) # rubocop:disable Rails/SaveBang
end
it 'preserves the special characters' do
@@ -155,7 +219,7 @@ RSpec.describe 'User views a wiki page' do
let(:title) { '<script>alert("title")<script>' }
before do
- wiki_page.update(title: title, content: 'foo <script>alert("content")</script> bar')
+ wiki_page.update(title: title, content: 'foo <script>alert("content")</script> bar') # rubocop:disable Rails/SaveBang
end
it 'safely displays the page' do
@@ -168,7 +232,7 @@ RSpec.describe 'User views a wiki page' do
context 'when a page has XSS in its message' do
before do
- wiki_page.update(message: '<script>alert(true)<script>', content: 'XSS update')
+ wiki_page.update(message: '<script>alert(true)<script>', content: 'XSS update') # rubocop:disable Rails/SaveBang
end
it 'safely displays the message' do
diff --git a/spec/features/promotion_spec.rb b/spec/features/promotion_spec.rb
new file mode 100644
index 00000000000..9344f9b56b8
--- /dev/null
+++ b/spec/features/promotion_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Promotions', :js do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project_empty_repo) }
+
+ describe 'for service desk', :js do
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+ end
+
+ context 'when service desk is not supported' do
+ before do
+ allow(::Gitlab::ServiceDesk).to receive(:supported?).and_return(false)
+ end
+
+ it 'appears in project edit page' do
+ visit edit_project_path(project)
+
+ expect(find('#promote_service_desk')).to have_content 'Improve customer support with GitLab Service Desk.'
+ end
+
+ it 'does not show when cookie is set' do
+ visit edit_project_path(project)
+
+ within('#promote_service_desk') do
+ find('.close').click
+ end
+
+ wait_for_requests
+
+ visit edit_project_path(project)
+
+ expect(page).not_to have_selector('#promote_service_desk')
+ end
+ end
+
+ context 'when service desk is supported' do
+ before do
+ allow(::Gitlab::ServiceDesk).to receive(:supported?).and_return(true)
+ end
+
+ it 'does not show promotion' do
+ visit edit_project_path(project)
+
+ expect(page).not_to have_selector('#promote_service_desk')
+ end
+ end
+ end
+end
diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb
index 8806a363ca4..9b2373bf28b 100644
--- a/spec/features/runners_spec.rb
+++ b/spec/features/runners_spec.rb
@@ -270,7 +270,7 @@ RSpec.describe 'Runners' do
it 'there are no runners displayed' do
visit group_settings_ci_cd_path(group)
- expect(page).to have_content 'This group does not provide any group Runners yet'
+ expect(page).to have_content 'No runners found'
end
it 'user can see a link to install runners on kubernetes clusters' do
@@ -286,26 +286,26 @@ RSpec.describe 'Runners' do
it 'the runner is visible' do
visit group_settings_ci_cd_path(group)
- expect(page).not_to have_content 'This group does not provide any group Runners yet'
- expect(page).to have_content 'Available group Runners: 1'
+ expect(page).not_to have_content 'No runners found'
+ expect(page).to have_content 'Available Runners: 1'
expect(page).to have_content 'group-runner'
end
it 'user can pause and resume the group runner' do
visit group_settings_ci_cd_path(group)
- expect(page).to have_content('Pause')
- expect(page).not_to have_content('Resume')
+ expect(page).to have_link href: pause_group_runner_path(group, runner)
+ expect(page).not_to have_link href: resume_group_runner_path(group, runner)
- click_on 'Pause'
+ click_link href: pause_group_runner_path(group, runner)
- expect(page).not_to have_content('Pause')
- expect(page).to have_content('Resume')
+ expect(page).not_to have_link href: pause_group_runner_path(group, runner)
+ expect(page).to have_link href: resume_group_runner_path(group, runner)
- click_on 'Resume'
+ click_link href: resume_group_runner_path(group, runner)
- expect(page).to have_content('Pause')
- expect(page).not_to have_content('Resume')
+ expect(page).to have_link href: pause_group_runner_path(group, runner)
+ expect(page).not_to have_link href: resume_group_runner_path(group, runner)
end
it 'user can view runner details' do
@@ -321,7 +321,7 @@ RSpec.describe 'Runners' do
it 'user can remove a group runner' do
visit group_settings_ci_cd_path(group)
- click_on 'Remove Runner'
+ all(:link, href: group_runner_path(group, runner))[1].click
expect(page).not_to have_content(runner.display_name)
end
@@ -329,7 +329,7 @@ RSpec.describe 'Runners' do
it 'user edits the runner to be protected' do
visit group_settings_ci_cd_path(group)
- first('.edit-runner > a').click
+ click_link href: edit_group_runner_path(group, runner)
expect(page.find_field('runner[access_level]')).not_to be_checked
@@ -347,7 +347,87 @@ RSpec.describe 'Runners' do
it 'user edits runner not to run untagged jobs' do
visit group_settings_ci_cd_path(group)
- first('.edit-runner > a').click
+ click_link href: edit_group_runner_path(group, runner)
+
+ expect(page.find_field('runner[run_untagged]')).to be_checked
+
+ uncheck 'runner_run_untagged'
+ click_button 'Save changes'
+
+ expect(page).to have_content 'Can run untagged jobs No'
+ end
+ end
+ end
+
+ context 'group with a project runner' do
+ let(:project) { create(:project, group: group) }
+ let!(:runner) { create(:ci_runner, :project, projects: [project], description: 'project-runner') }
+
+ it 'the runner is visible' do
+ visit group_settings_ci_cd_path(group)
+
+ expect(page).not_to have_content 'No runners found'
+ expect(page).to have_content 'Available Runners: 1'
+ expect(page).to have_content 'project-runner'
+ end
+
+ it 'user can pause and resume the project runner' do
+ visit group_settings_ci_cd_path(group)
+
+ expect(page).to have_link href: pause_group_runner_path(group, runner)
+ expect(page).not_to have_link href: resume_group_runner_path(group, runner)
+
+ click_link href: pause_group_runner_path(group, runner)
+
+ expect(page).not_to have_link href: pause_group_runner_path(group, runner)
+ expect(page).to have_link href: resume_group_runner_path(group, runner)
+
+ click_link href: resume_group_runner_path(group, runner)
+
+ expect(page).to have_link href: pause_group_runner_path(group, runner)
+ expect(page).not_to have_link href: resume_group_runner_path(group, runner)
+ end
+
+ it 'user can view runner details' do
+ visit group_settings_ci_cd_path(group)
+
+ expect(page).to have_content(runner.display_name)
+
+ click_on runner.short_sha
+
+ expect(page).to have_content(runner.platform)
+ end
+
+ it 'user can remove a project runner' do
+ visit group_settings_ci_cd_path(group)
+
+ all(:link, href: group_runner_path(group, runner))[1].click
+
+ expect(page).not_to have_content(runner.display_name)
+ end
+
+ it 'user edits the runner to be protected' do
+ visit group_settings_ci_cd_path(group)
+
+ click_link href: edit_group_runner_path(group, runner)
+
+ expect(page.find_field('runner[access_level]')).not_to be_checked
+
+ check 'runner_access_level'
+ click_button 'Save changes'
+
+ expect(page).to have_content 'Protected Yes'
+ end
+
+ context 'when a runner has a tag' do
+ before do
+ runner.update(tag_list: ['tag'])
+ end
+
+ it 'user edits runner not to run untagged jobs' do
+ visit group_settings_ci_cd_path(group)
+
+ click_link href: edit_group_runner_path(group, runner)
expect(page.find_field('runner[run_untagged]')).to be_checked
@@ -358,5 +438,17 @@ RSpec.describe 'Runners' do
end
end
end
+
+ context 'group with a multi-project runner' do
+ let(:project) { create(:project, group: group) }
+ let(:project_2) { create(:project, group: group) }
+ let!(:runner) { create(:ci_runner, :project, projects: [project, project_2], description: 'group-runner') }
+
+ it 'user cannot remove the project runner' do
+ visit group_settings_ci_cd_path(group)
+
+ expect(all(:link, href: group_runner_path(group, runner)).length).to eq(1)
+ end
+ end
end
end
diff --git a/spec/features/security/project/snippet/public_access_spec.rb b/spec/features/security/project/snippet/public_access_spec.rb
index dfe78aa7ebc..20a271f9c0e 100644
--- a/spec/features/security/project/snippet/public_access_spec.rb
+++ b/spec/features/security/project/snippet/public_access_spec.rb
@@ -5,11 +5,10 @@ require 'spec_helper'
RSpec.describe "Public Project Snippets Access" do
include AccessMatchers
- let(:project) { create(:project, :public) }
-
- let(:public_snippet) { create(:project_snippet, :public, project: project, author: project.owner) }
- let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: project.owner) }
- let(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) }
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:public_snippet) { create(:project_snippet, :public, project: project, author: project.owner) }
+ let_it_be(:internal_snippet) { create(:project_snippet, :internal, project: project, author: project.owner) }
+ let_it_be(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) }
describe "GET /:project_path/snippets" do
subject { project_snippets_path(project) }
diff --git a/spec/features/signed_commits_spec.rb b/spec/features/signed_commits_spec.rb
index 04ca8a09ca8..d679e4dbb99 100644
--- a/spec/features/signed_commits_spec.rb
+++ b/spec/features/signed_commits_spec.rb
@@ -72,6 +72,7 @@ RSpec.describe 'GPG signed commits' do
it 'unverified signature' do
visit project_commit_path(project, GpgHelpers::SIGNED_COMMIT_SHA)
+ wait_for_all_requests
page.find('.gpg-status-box', text: 'Unverified').click
@@ -85,6 +86,7 @@ RSpec.describe 'GPG signed commits' do
user_2_key
visit project_commit_path(project, GpgHelpers::DIFFERING_EMAIL_SHA)
+ wait_for_all_requests
page.find('.gpg-status-box', text: 'Unverified').click
@@ -100,6 +102,7 @@ RSpec.describe 'GPG signed commits' do
user_2_key
visit project_commit_path(project, GpgHelpers::SIGNED_COMMIT_SHA)
+ wait_for_all_requests
page.find('.gpg-status-box', text: 'Unverified').click
@@ -115,6 +118,7 @@ RSpec.describe 'GPG signed commits' do
user_1_key
visit project_commit_path(project, GpgHelpers::SIGNED_AND_AUTHORED_SHA)
+ wait_for_all_requests
page.find('.gpg-status-box', text: 'Verified').click
@@ -130,6 +134,7 @@ RSpec.describe 'GPG signed commits' do
user_1_key
visit project_commit_path(project, GpgHelpers::SIGNED_AND_AUTHORED_SHA)
+ wait_for_all_requests
# wait for the signature to get generated
expect(page).to have_selector('.gpg-status-box', text: 'Verified')
@@ -137,6 +142,7 @@ RSpec.describe 'GPG signed commits' do
user_1.destroy!
refresh
+ wait_for_all_requests
page.find('.gpg-status-box', text: 'Verified').click
@@ -153,6 +159,7 @@ RSpec.describe 'GPG signed commits' do
shared_examples 'a commit with a signature' do
before do
visit project_tree_path(project, 'signed-commits')
+ wait_for_all_requests
end
it 'displays commit signature' do
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index fa87c4bb1c4..fc3f8a94318 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -103,7 +103,7 @@ RSpec.describe 'Task Lists' do
wait_for_requests
expect(page).to have_selector(".md .task-list .task-list-item .task-list-item-checkbox")
- expect(page).to have_selector('a.btn-close')
+ expect(page).to have_selector('.btn-close')
end
it 'is only editable by author' do
diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb
index 577134fe722..4be27673adf 100644
--- a/spec/features/triggers_spec.rb
+++ b/spec/features/triggers_spec.rb
@@ -85,7 +85,7 @@ RSpec.describe 'Triggers', :js do
end
expect(page.find('.flash-notice')).to have_content 'Trigger removed'
- expect(page).to have_selector('p.settings-message.text-center.append-bottom-default')
+ expect(page).to have_selector('p.settings-message.text-center.gl-mb-3')
end
end
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index 7ba663d08d4..2d0fcfe84e6 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -110,7 +110,7 @@ RSpec.describe 'Login' do
gitlab_sign_in(user)
- expect(page).not_to have_content('You have to confirm your email address before continuing.')
+ expect(page).not_to have_content(I18n.t('devise.failure.unconfirmed'))
expect(page).not_to have_link('Resend confirmation email', href: new_user_confirmation_path)
end
end
@@ -124,7 +124,7 @@ RSpec.describe 'Login' do
gitlab_sign_in(user)
- expect(page).to have_content('You have to confirm your email address before continuing.')
+ expect(page).to have_content(I18n.t('devise.failure.unconfirmed'))
expect(page).to have_link('Resend confirmation email', href: new_user_confirmation_path)
end
end
@@ -820,7 +820,7 @@ RSpec.describe 'Login' do
gitlab_sign_in(user)
expect(current_path).to eq new_user_session_path
- expect(page).to have_content('You have to confirm your email address before continuing.')
+ expect(page).to have_content(I18n.t('devise.failure.unconfirmed'))
end
end
end
diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb
index 66a26493339..af2ecfec498 100644
--- a/spec/features/users/signup_spec.rb
+++ b/spec/features/users/signup_spec.rb
@@ -70,6 +70,13 @@ RSpec.shared_examples 'Signup' do
expect(page).to have_content("Username is too long (maximum is 255 characters).")
end
+ it 'shows an error message if the username is less than 2 characters' do
+ fill_in 'new_user_username', with: 'u'
+ wait_for_requests
+
+ expect(page).to have_content("Username is too short (minimum is 2 characters).")
+ end
+
it 'shows an error message on submit if the username contains special characters' do
fill_in 'new_user_username', with: 'new$user!username'
wait_for_requests
@@ -91,7 +98,7 @@ RSpec.shared_examples 'Signup' do
expect(page).to have_content("Invalid input, please avoid emojis")
end
- it 'shows a pending message if the username availability is being fetched', :quarantine do
+ it 'shows a pending message if the username availability is being fetched', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/31484' do
fill_in 'new_user_username', with: 'new-user'
expect(find('.username > .validation-pending')).not_to have_css '.hide'
diff --git a/spec/finders/alert_management/alerts_finder_spec.rb b/spec/finders/alert_management/alerts_finder_spec.rb
index 5920d579ba6..7bf9047704b 100644
--- a/spec/finders/alert_management/alerts_finder_spec.rb
+++ b/spec/finders/alert_management/alerts_finder_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe AlertManagement::AlertsFinder, '#execute' do
let(:params) { {} }
describe '#execute' do
- subject { described_class.new(current_user, project, params).execute }
+ subject(:execute) { described_class.new(current_user, project, params).execute }
context 'user is not a developer or above' do
it { is_expected.to be_empty }
@@ -144,81 +144,55 @@ RSpec.describe AlertManagement::AlertsFinder, '#execute' do
end
context 'when sorting by severity' do
- let_it_be(:alert_critical) { create(:alert_management_alert, project: project, severity: :critical) }
- let_it_be(:alert_high) { create(:alert_management_alert, project: project, severity: :high) }
- let_it_be(:alert_medium) { create(:alert_management_alert, project: project, severity: :medium) }
- let_it_be(:alert_low) { create(:alert_management_alert, project: project, severity: :low) }
- let_it_be(:alert_info) { create(:alert_management_alert, project: project, severity: :info) }
- let_it_be(:alert_unknown) { create(:alert_management_alert, project: project, severity: :unknown) }
-
- context 'sorts alerts ascending' do
+ let_it_be(:alert_critical) { create(:alert_management_alert, :critical, project: project) }
+ let_it_be(:alert_high) { create(:alert_management_alert, :high, project: project) }
+ let_it_be(:alert_medium) { create(:alert_management_alert, :medium, project: project) }
+ let_it_be(:alert_low) { create(:alert_management_alert, :low, project: project) }
+ let_it_be(:alert_info) { create(:alert_management_alert, :info, project: project) }
+ let_it_be(:alert_unknown) { create(:alert_management_alert, :unknown, project: project) }
+
+ context 'with ascending sort order' do
let(:params) { { sort: 'severity_asc' } }
- it do
- is_expected.to eq [
- alert_2,
- alert_critical,
- alert_1,
- alert_high,
- alert_medium,
- alert_low,
- alert_info,
- alert_unknown
- ]
+ it 'sorts alerts by severity from less critical to more critical' do
+ expect(execute.pluck(:severity).uniq).to eq(%w(unknown info low medium high critical))
end
end
- context 'sorts alerts descending' do
+ context 'with descending sort order' do
let(:params) { { sort: 'severity_desc' } }
- it do
- is_expected.to eq [
- alert_unknown,
- alert_info,
- alert_low,
- alert_medium,
- alert_1,
- alert_high,
- alert_critical,
- alert_2
- ]
+ it 'sorts alerts by severity from more critical to less critical' do
+ expect(execute.pluck(:severity).uniq).to eq(%w(critical high medium low info unknown))
end
end
end
context 'when sorting by status' do
+ let(:statuses) { AlertManagement::Alert::STATUSES }
+ let(:triggered) { statuses[:triggered] }
+ let(:acknowledged) { statuses[:acknowledged] }
+ let(:resolved) { statuses[:resolved] }
+ let(:ignored) { statuses[:ignored] }
+
let_it_be(:alert_triggered) { create(:alert_management_alert, project: project) }
let_it_be(:alert_acknowledged) { create(:alert_management_alert, :acknowledged, project: project) }
let_it_be(:alert_resolved) { create(:alert_management_alert, :resolved, project: project) }
let_it_be(:alert_ignored) { create(:alert_management_alert, :ignored, project: project) }
- context 'sorts alerts ascending' do
+ context 'with ascending sort order' do
let(:params) { { sort: 'status_asc' } }
- it do
- is_expected.to eq [
- alert_triggered,
- alert_acknowledged,
- alert_1,
- alert_resolved,
- alert_2,
- alert_ignored
- ]
+ it 'sorts by status: Ignored > Resolved > Acknowledged > Triggered' do
+ expect(execute.map(&:status).uniq).to eq([ignored, resolved, acknowledged, triggered])
end
end
- context 'sorts alerts descending' do
+ context 'with descending sort order' do
let(:params) { { sort: 'status_desc' } }
- it do
- is_expected.to eq [
- alert_2,
- alert_ignored,
- alert_1,
- alert_resolved,
- alert_acknowledged,
- alert_triggered
- ]
+ it 'sorts by status: Triggered > Acknowledged > Resolved > Ignored' do
+ expect(execute.map(&:status).uniq).to eq([triggered, acknowledged, resolved, ignored])
end
end
end
diff --git a/spec/finders/branches_finder_spec.rb b/spec/finders/branches_finder_spec.rb
index 2e52093342d..a62dd3842db 100644
--- a/spec/finders/branches_finder_spec.rb
+++ b/spec/finders/branches_finder_spec.rb
@@ -7,142 +7,255 @@ RSpec.describe BranchesFinder do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
+ let(:branch_finder) { described_class.new(repository, params) }
+ let(:params) { {} }
+
describe '#execute' do
+ subject { branch_finder.execute }
+
context 'sort only' do
- it 'sorts by name' do
- branches_finder = described_class.new(repository, {})
+ context 'by name' do
+ let(:params) { {} }
- result = branches_finder.execute
+ it 'sorts' do
+ result = subject
- expect(result.first.name).to eq("'test'")
+ expect(result.first.name).to eq("'test'")
+ end
end
- it 'sorts by recently_updated' do
- branches_finder = described_class.new(repository, { sort: 'updated_desc' })
+ context 'by recently_updated' do
+ let(:params) { { sort: 'updated_desc' } }
- result = branches_finder.execute
+ it 'sorts' do
+ result = subject
- recently_updated_branch = repository.branches.max do |a, b|
- repository.commit(a.dereferenced_target).committed_date <=> repository.commit(b.dereferenced_target).committed_date
- end
+ recently_updated_branch = repository.branches.max do |a, b|
+ repository.commit(a.dereferenced_target).committed_date <=> repository.commit(b.dereferenced_target).committed_date
+ end
- expect(result.first.name).to eq(recently_updated_branch.name)
+ expect(result.first.name).to eq(recently_updated_branch.name)
+ end
end
- it 'sorts by last_updated' do
- branches_finder = described_class.new(repository, { sort: 'updated_asc' })
+ context 'by last_updated' do
+ let(:params) { { sort: 'updated_asc' } }
- result = branches_finder.execute
+ it 'sorts' do
+ result = subject
- expect(result.first.name).to eq('feature')
+ expect(result.first.name).to eq('feature')
+ end
end
end
context 'filter only' do
- it 'filters branches by name' do
- branches_finder = described_class.new(repository, { search: 'fix' })
+ context 'by name' do
+ let(:params) { { search: 'fix' } }
- result = branches_finder.execute
+ it 'filters branches' do
+ result = subject
- expect(result.first.name).to eq('fix')
- expect(result.count).to eq(1)
+ expect(result.first.name).to eq('fix')
+ expect(result.count).to eq(1)
+ end
end
- it 'filters branches by name ignoring letter case' do
- branches_finder = described_class.new(repository, { search: 'FiX' })
+ context 'by name ignoring letter case' do
+ let(:params) { { search: 'FiX' } }
- result = branches_finder.execute
+ it 'filters branches' do
+ result = subject
- expect(result.first.name).to eq('fix')
- expect(result.count).to eq(1)
+ expect(result.first.name).to eq('fix')
+ expect(result.count).to eq(1)
+ end
end
- it 'does not find any branch with that name' do
- branches_finder = described_class.new(repository, { search: 'random' })
+ context 'with an unknown name' do
+ let(:params) { { search: 'random' } }
- result = branches_finder.execute
+ it 'does not find any branch' do
+ result = subject
- expect(result.count).to eq(0)
+ expect(result.count).to eq(0)
+ end
end
- it 'filters branches by provided names' do
- branches_finder = described_class.new(repository, { names: %w[fix csv lfs does-not-exist] })
+ context 'by provided names' do
+ let(:params) { { names: %w[fix csv lfs does-not-exist] } }
- result = branches_finder.execute
+ it 'filters branches' do
+ result = subject
- expect(result.count).to eq(3)
- expect(result.map(&:name)).to eq(%w{csv fix lfs})
+ expect(result.count).to eq(3)
+ expect(result.map(&:name)).to eq(%w{csv fix lfs})
+ end
end
- it 'filters branches by name that begins with' do
- params = { search: '^feature_' }
- branches_finder = described_class.new(repository, params)
+ context 'by name that begins with' do
+ let(:params) { { search: '^feature_' } }
- result = branches_finder.execute
+ it 'filters branches' do
+ result = subject
- expect(result.first.name).to eq('feature_conflict')
- expect(result.count).to eq(1)
+ expect(result.first.name).to eq('feature_conflict')
+ expect(result.count).to eq(1)
+ end
end
- it 'filters branches by name that ends with' do
- params = { search: 'feature$' }
- branches_finder = described_class.new(repository, params)
+ context 'by name that ends with' do
+ let(:params) { { search: 'feature$' } }
- result = branches_finder.execute
+ it 'filters branches' do
+ result = subject
- expect(result.first.name).to eq('feature')
- expect(result.count).to eq(1)
+ expect(result.first.name).to eq('feature')
+ expect(result.count).to eq(1)
+ end
end
- it 'filters branches by nonexistent name that begins with' do
- params = { search: '^nope' }
- branches_finder = described_class.new(repository, params)
+ context 'by nonexistent name that begins with' do
+ let(:params) { { search: '^nope' } }
- result = branches_finder.execute
+ it 'filters branches' do
+ result = subject
- expect(result.count).to eq(0)
+ expect(result.count).to eq(0)
+ end
end
- it 'filters branches by nonexistent name that ends with' do
- params = { search: 'nope$' }
- branches_finder = described_class.new(repository, params)
+ context 'by nonexistent name that ends with' do
+ let(:params) { { search: 'nope$' } }
- result = branches_finder.execute
+ it 'filters branches' do
+ result = subject
- expect(result.count).to eq(0)
+ expect(result.count).to eq(0)
+ end
end
end
context 'filter and sort' do
- it 'filters branches by name and sorts by recently_updated' do
- params = { sort: 'updated_desc', search: 'feat' }
- branches_finder = described_class.new(repository, params)
+ context 'by name and sorts by recently_updated' do
+ let(:params) { { sort: 'updated_desc', search: 'feat' } }
+
+ it 'filters branches' do
+ result = subject
+
+ expect(result.first.name).to eq('feature_conflict')
+ expect(result.count).to eq(2)
+ end
+ end
+
+ context 'by name and sorts by recently_updated, with exact matches first' do
+ let(:params) { { sort: 'updated_desc', search: 'feature' } }
+
+ it 'filters branches' do
+ result = subject
+
+ expect(result.first.name).to eq('feature')
+ expect(result.second.name).to eq('feature_conflict')
+ expect(result.count).to eq(2)
+ end
+ end
+
+ context 'by name and sorts by last_updated' do
+ let(:params) { { sort: 'updated_asc', search: 'feature' } }
+
+ it 'filters branches' do
+ result = subject
+
+ expect(result.first.name).to eq('feature')
+ expect(result.count).to eq(2)
+ end
+ end
+ end
- result = branches_finder.execute
+ context 'with gitaly pagination' do
+ subject { branch_finder.execute(gitaly_pagination: true) }
- expect(result.first.name).to eq('feature_conflict')
- expect(result.count).to eq(2)
+ context 'by page_token and per_page' do
+ let(:params) { { page_token: 'feature', per_page: 2 } }
+
+ it 'filters branches' do
+ result = subject
+
+ expect(result.map(&:name)).to eq(%w(feature_conflict fix))
+ end
end
- it 'filters branches by name and sorts by recently_updated, with exact matches first' do
- params = { sort: 'updated_desc', search: 'feature' }
- branches_finder = described_class.new(repository, params)
+ context 'by next page_token and per_page' do
+ let(:params) { { page_token: 'fix', per_page: 2 } }
- result = branches_finder.execute
+ it 'filters branches' do
+ result = subject
- expect(result.first.name).to eq('feature')
- expect(result.second.name).to eq('feature_conflict')
- expect(result.count).to eq(2)
+ expect(result.map(&:name)).to eq(%w(flatten-dir gitattributes))
+ end
end
- it 'filters branches by name and sorts by last_updated' do
- params = { sort: 'updated_asc', search: 'feature' }
- branches_finder = described_class.new(repository, params)
+ context 'by per_page only' do
+ let(:params) { { per_page: 2 } }
+
+ it 'filters branches' do
+ result = subject
+
+ expect(result.map(&:name)).to eq(["'test'", '2-mb-file'])
+ end
+ end
- result = branches_finder.execute
+ context 'by page_token only' do
+ let(:params) { { page_token: 'feature' } }
- expect(result.first.name).to eq('feature')
- expect(result.count).to eq(2)
+ it 'returns nothing' do
+ result = subject
+
+ expect(result.count).to eq(0)
+ end
+ end
+
+ context 'pagination and sort' do
+ context 'by per_page' do
+ let(:params) { { sort: 'updated_asc', per_page: 5 } }
+
+ it 'filters branches' do
+ result = subject
+
+ expect(result.map(&:name)).to eq(%w(feature improve/awesome merge-test markdown feature_conflict))
+ end
+ end
+
+ context 'by page_token and per_page' do
+ let(:params) { { sort: 'updated_asc', page_token: 'improve/awesome', per_page: 2 } }
+
+ it 'filters branches' do
+ result = subject
+
+ expect(result.map(&:name)).to eq(%w(merge-test markdown))
+ end
+ end
+ end
+
+ context 'pagination and names' do
+ let(:params) { { page_token: 'fix', per_page: 2, names: %w[fix csv lfs does-not-exist] } }
+
+ it 'falls back to default execute and ignore paginations' do
+ result = subject
+
+ expect(result.count).to eq(3)
+ expect(result.map(&:name)).to eq(%w{csv fix lfs})
+ end
+ end
+
+ context 'pagination and search' do
+ let(:params) { { page_token: 'feature', per_page: 2, search: '^f' } }
+
+ it 'falls back to default execute and ignore paginations' do
+ result = subject
+
+ expect(result.map(&:name)).to eq(%w(feature feature_conflict fix flatten-dir))
+ end
end
end
end
diff --git a/spec/finders/ci/pipelines_finder_spec.rb b/spec/finders/ci/pipelines_finder_spec.rb
index 680955ff9f9..a2a714689ba 100644
--- a/spec/finders/ci/pipelines_finder_spec.rb
+++ b/spec/finders/ci/pipelines_finder_spec.rb
@@ -77,13 +77,13 @@ RSpec.describe Ci::PipelinesFinder do
end
end
- HasStatus::AVAILABLE_STATUSES.each do |target|
+ Ci::HasStatus::AVAILABLE_STATUSES.each do |target|
context "when status is #{target}" do
let(:params) { { status: target } }
let!(:pipeline) { create(:ci_pipeline, project: project, status: target) }
before do
- exception_status = HasStatus::AVAILABLE_STATUSES - [target]
+ exception_status = Ci::HasStatus::AVAILABLE_STATUSES - [target]
create(:ci_pipeline, project: project, status: exception_status.first)
end
diff --git a/spec/finders/ci/pipelines_for_merge_request_finder_spec.rb b/spec/finders/ci/pipelines_for_merge_request_finder_spec.rb
index 543c289d366..ca6e0793d55 100644
--- a/spec/finders/ci/pipelines_for_merge_request_finder_spec.rb
+++ b/spec/finders/ci/pipelines_for_merge_request_finder_spec.rb
@@ -3,11 +3,97 @@
require 'spec_helper'
RSpec.describe Ci::PipelinesForMergeRequestFinder do
+ describe '#execute' do
+ include ProjectForksHelper
+
+ subject { finder.execute }
+
+ let_it_be(:developer_in_parent) { create(:user) }
+ let_it_be(:developer_in_fork) { create(:user) }
+ let_it_be(:developer_in_both) { create(:user) }
+ let_it_be(:reporter_in_parent_and_developer_in_fork) { create(:user) }
+ let_it_be(:external_user) { create(:user) }
+ let_it_be(:parent_project) { create(:project, :repository, :private) }
+ let_it_be(:forked_project) { fork_project(parent_project, nil, repository: true, target_project: create(:project, :private, :repository)) }
+
+ let(:merge_request) do
+ create(:merge_request, source_project: forked_project, source_branch: 'feature',
+ target_project: parent_project, target_branch: 'master')
+ end
+
+ let!(:pipeline_in_parent) do
+ create(:ci_pipeline, :merged_result_pipeline, merge_request: merge_request, project: parent_project)
+ end
+
+ let!(:pipeline_in_fork) do
+ create(:ci_pipeline, :merged_result_pipeline, merge_request: merge_request, project: forked_project)
+ end
+
+ let(:finder) { described_class.new(merge_request, actor) }
+
+ before_all do
+ parent_project.add_developer(developer_in_parent)
+ parent_project.add_developer(developer_in_both)
+ parent_project.add_reporter(reporter_in_parent_and_developer_in_fork)
+ forked_project.add_developer(developer_in_fork)
+ forked_project.add_developer(developer_in_both)
+ forked_project.add_developer(reporter_in_parent_and_developer_in_fork)
+ end
+
+ context 'when actor has permission to read pipelines in both parent and forked projects' do
+ let(:actor) { developer_in_both }
+
+ it 'returns all pipelines' do
+ is_expected.to eq([pipeline_in_fork, pipeline_in_parent])
+ end
+ end
+
+ context 'when actor has permission to read pipelines in both parent and forked projects' do
+ let(:actor) { reporter_in_parent_and_developer_in_fork }
+
+ it 'returns all pipelines' do
+ is_expected.to eq([pipeline_in_fork, pipeline_in_parent])
+ end
+ end
+
+ context 'when actor has permission to read pipelines in the parent project only' do
+ let(:actor) { developer_in_parent }
+
+ it 'returns pipelines in parent' do
+ is_expected.to eq([pipeline_in_parent])
+ end
+ end
+
+ context 'when actor has permission to read pipelines in the forked project only' do
+ let(:actor) { developer_in_fork }
+
+ it 'returns pipelines in fork' do
+ is_expected.to eq([pipeline_in_fork])
+ end
+ end
+
+ context 'when actor does not have permission to read pipelines' do
+ let(:actor) { external_user }
+
+ it 'returns nothing' do
+ is_expected.to be_empty
+ end
+ end
+
+ context 'when actor is nil' do
+ let(:actor) { nil }
+
+ it 'returns nothing' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
describe '#all' do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.source_project }
- subject { described_class.new(merge_request) }
+ subject { described_class.new(merge_request, nil) }
shared_examples 'returning pipelines with proper ordering' do
let!(:all_pipelines) do
@@ -134,7 +220,7 @@ RSpec.describe Ci::PipelinesForMergeRequestFinder do
branch_pipeline_2,
branch_pipeline])
- expect(described_class.new(merge_request_2).all)
+ expect(described_class.new(merge_request_2, nil).all)
.to eq([detached_merge_request_pipeline_2,
branch_pipeline_2,
branch_pipeline])
diff --git a/spec/finders/ci/runner_jobs_finder_spec.rb b/spec/finders/ci/runner_jobs_finder_spec.rb
index 7c9f762c000..3569582d70f 100644
--- a/spec/finders/ci/runner_jobs_finder_spec.rb
+++ b/spec/finders/ci/runner_jobs_finder_spec.rb
@@ -21,13 +21,13 @@ RSpec.describe Ci::RunnerJobsFinder do
end
context 'when params contains status' do
- HasStatus::AVAILABLE_STATUSES.each do |target_status|
+ Ci::HasStatus::AVAILABLE_STATUSES.each do |target_status|
context "when status is #{target_status}" do
let(:params) { { status: target_status } }
let!(:job) { create(:ci_build, runner: runner, project: project, status: target_status) }
before do
- exception_status = HasStatus::AVAILABLE_STATUSES - [target_status]
+ exception_status = Ci::HasStatus::AVAILABLE_STATUSES - [target_status]
create(:ci_build, runner: runner, project: project, status: exception_status.first)
end
diff --git a/spec/finders/ci/variables_finder_spec.rb b/spec/finders/ci/variables_finder_spec.rb
new file mode 100644
index 00000000000..cd5f950ca8e
--- /dev/null
+++ b/spec/finders/ci/variables_finder_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::VariablesFinder do
+ let!(:project) { create(:project) }
+ let!(:params) { {} }
+
+ let!(:var1) { create(:ci_variable, project: project, key: 'key1', environment_scope: 'staging') }
+ let!(:var2) { create(:ci_variable, project: project, key: 'key2', environment_scope: 'staging') }
+ let!(:var3) { create(:ci_variable, project: project, key: 'key2', environment_scope: 'production') }
+
+ describe '#initialize' do
+ subject { described_class.new(project, params) }
+
+ context 'without key filter' do
+ let!(:params) { {} }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(ArgumentError, 'Please provide params[:key]')
+ end
+ end
+ end
+
+ describe '#execute' do
+ subject { described_class.new(project.reload, params).execute }
+
+ context 'with key filter' do
+ let!(:params) { { key: 'key1' } }
+
+ it 'returns var1' do
+ expect(subject).to contain_exactly(var1)
+ end
+ end
+
+ context 'with key and environment_scope filter' do
+ let!(:params) { { key: 'key2', filter: { environment_scope: 'staging' } } }
+
+ it 'returns var2' do
+ expect(subject).to contain_exactly(var2)
+ end
+ end
+ end
+end
diff --git a/spec/finders/events_finder_spec.rb b/spec/finders/events_finder_spec.rb
index b13ef7e94e7..fe2e449f03d 100644
--- a/spec/finders/events_finder_spec.rb
+++ b/spec/finders/events_finder_spec.rb
@@ -66,29 +66,13 @@ RSpec.describe EventsFinder do
end
end
- describe 'wiki events feature flag' do
+ describe 'wiki events' do
let_it_be(:events) { create_list(:wiki_page_event, 3, project: public_project) }
subject(:finder) { described_class.new(source: public_project, target_type: 'wiki', current_user: user) }
- context 'the wiki_events feature flag is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it 'omits the wiki page events' do
- expect(finder.execute).to be_empty
- end
- end
-
- context 'the wiki_events feature flag is enabled' do
- before do
- stub_feature_flags(wiki_events: true)
- end
-
- it 'can find the wiki events' do
- expect(finder.execute).to match_array(events)
- end
+ it 'can find the wiki events' do
+ expect(finder.execute).to match_array(events)
end
end
diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb
index 7499461ad8f..14f2bb017c6 100644
--- a/spec/finders/group_projects_finder_spec.rb
+++ b/spec/finders/group_projects_finder_spec.rb
@@ -46,6 +46,18 @@ RSpec.describe GroupProjectsFinder do
context 'without subgroups projects' do
it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, private_project, public_project]) }
end
+
+ context "with min access level" do
+ let!(:shared_project_4) { create(:project, :internal, path: '8') }
+
+ before do
+ shared_project_4.project_group_links.create(group_access: Gitlab::Access::REPORTER, group: group)
+ end
+
+ let(:params) { { min_access_level: Gitlab::Access::MAINTAINER } }
+
+ it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, private_project, public_project]) }
+ end
end
end
@@ -171,6 +183,38 @@ RSpec.describe GroupProjectsFinder do
end
end
+ describe 'feature availability' do
+ let!(:project_with_issues_disabled) { create(:project, :issues_disabled, :internal, path: '9') }
+ let!(:project_with_merge_request_disabled) { create(:project, :merge_requests_disabled, :internal, path: '10') }
+
+ before do
+ project_with_issues_disabled.project_group_links.create!(group_access: Gitlab::Access::REPORTER, group: group)
+ project_with_merge_request_disabled.project_group_links.create!(group_access: Gitlab::Access::REPORTER, group: group)
+ end
+
+ context 'without issues and merge request enabled' do
+ it { is_expected.to match_array([public_project, shared_project_1, shared_project_3, project_with_issues_disabled, project_with_merge_request_disabled]) }
+ end
+
+ context 'with issues enabled' do
+ let(:params) { { with_issues_enabled: true } }
+
+ it { is_expected.to match_array([public_project, shared_project_1, shared_project_3, project_with_merge_request_disabled]) }
+ end
+
+ context 'with merge request enabled' do
+ let(:params) { { with_merge_requests_enabled: true } }
+
+ it { is_expected.to match_array([public_project, shared_project_1, shared_project_3, project_with_issues_disabled]) }
+ end
+
+ context 'with issues and merge request enabled' do
+ let(:params) { { with_merge_requests_enabled: true, with_issues_enabled: true } }
+
+ it { is_expected.to match_array([public_project, shared_project_1, shared_project_3]) }
+ end
+ end
+
describe 'limiting' do
context 'without limiting' do
it 'returns all projects' do
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index f76110e3d85..e3643698012 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -53,6 +53,21 @@ RSpec.describe MergeRequestsFinder do
expect(merge_requests).to be_empty
end
+ context 'filtering by not author ID' do
+ let(:params) { { not: { author_id: user2.id } } }
+
+ before do
+ merge_request2.update!(author: user2)
+ merge_request3.update!(author: user2)
+ end
+
+ it 'returns merge requests not created by that user' do
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request4, merge_request5)
+ end
+ end
+
it 'filters by projects' do
params = { projects: [project2.id, project3.id] }
@@ -258,6 +273,11 @@ RSpec.describe MergeRequestsFinder do
let(:expected_issuables) { [merge_request1, merge_request2] }
end
+ it_behaves_like 'assignee NOT ID filter' do
+ let(:params) { { not: { assignee_id: user.id } } }
+ let(:expected_issuables) { [merge_request3, merge_request4, merge_request5] }
+ end
+
it_behaves_like 'assignee username filter' do
before do
project2.add_developer(user3)
@@ -269,6 +289,15 @@ RSpec.describe MergeRequestsFinder do
let(:expected_issuables) { [merge_request3] }
end
+ it_behaves_like 'assignee NOT username filter' do
+ before do
+ merge_request2.assignees = [user2]
+ end
+
+ let(:params) { { not: { assignee_username: [user.username, user2.username] } } }
+ let(:expected_issuables) { [merge_request4, merge_request5] }
+ end
+
it_behaves_like 'no assignee filter' do
let_it_be(:user3) { create(:user) }
let(:expected_issuables) { [merge_request4, merge_request5] }
@@ -294,6 +323,16 @@ RSpec.describe MergeRequestsFinder do
expect(merge_requests).to contain_exactly(merge_request2, merge_request3)
end
+
+ context 'using NOT' do
+ let(:params) { { not: { milestone_title: group_milestone.title } } }
+
+ it 'returns MRs not assigned to that group milestone' do
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request4, merge_request5)
+ end
+ end
end
end
diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb
index 5610f5889e6..868b126dc28 100644
--- a/spec/finders/notes_finder_spec.rb
+++ b/spec/finders/notes_finder_spec.rb
@@ -123,7 +123,7 @@ RSpec.describe NotesFinder do
let!(:note1) { create :note_on_commit, project: project }
let!(:note2) { create :note_on_commit, project: project }
let(:commit) { note1.noteable }
- let(:params) { { project: project, target_id: commit.id, target_type: 'commit', last_fetched_at: 1.hour.ago.to_i } }
+ let(:params) { { project: project, target_id: commit.id, target_type: 'commit', last_fetched_at: 1.hour.ago } }
it 'finds all notes' do
notes = described_class.new(user, params).execute
@@ -172,7 +172,7 @@ RSpec.describe NotesFinder do
let(:confidential_issue) { create(:issue, :confidential, project: project, author: user) }
let!(:confidential_note) { create(:note, noteable: confidential_issue, project: confidential_issue.project) }
- let(:params) { { project: confidential_issue.project, target_id: confidential_issue.id, target_type: 'issue', last_fetched_at: 1.hour.ago.to_i } }
+ let(:params) { { project: confidential_issue.project, target_id: confidential_issue.id, target_type: 'issue', last_fetched_at: 1.hour.ago } }
it 'returns notes if user can see the issue' do
expect(described_class.new(user, params).execute).to eq([confidential_note])
@@ -204,7 +204,7 @@ RSpec.describe NotesFinder do
end
it 'returns the expected notes when last_fetched_at is given' do
- params = { project: project, target: commit, last_fetched_at: 1.hour.ago.to_i }
+ params = { project: project, target: commit, last_fetched_at: 1.hour.ago }
expect(described_class.new(user, params).execute).to eq([note2])
end
diff --git a/spec/finders/packages/conan/package_file_finder_spec.rb b/spec/finders/packages/conan/package_file_finder_spec.rb
new file mode 100644
index 00000000000..d0c9efa1418
--- /dev/null
+++ b/spec/finders/packages/conan/package_file_finder_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe ::Packages::Conan::PackageFileFinder do
+ let_it_be(:package) { create(:conan_package) }
+ let_it_be(:package_file) { package.package_files.first }
+ let(:package_file_name) { package_file.file_name }
+ let(:params) { {} }
+
+ RSpec.shared_examples 'package file finder examples' do
+ it { is_expected.to eq(package_file) }
+
+ context 'with conan_file_type' do
+ # conan packages contain a conanmanifest.txt file for both conan_file_types
+ let(:package_file_name) { 'conanmanifest.txt' }
+ let(:params) { { conan_file_type: :recipe_file } }
+
+ it { expect(subject.conan_file_type).to eq('recipe_file') }
+ end
+
+ context 'with conan_package_reference' do
+ let_it_be(:other_package) { create(:conan_package) }
+ let_it_be(:package_file_name) { 'conan_package.tgz' }
+ let_it_be(:package_file) { package.package_files.find_by(file_name: package_file_name) }
+
+ let(:params) do
+ { conan_package_reference: package_file.conan_file_metadatum.conan_package_reference }
+ end
+
+ it { expect(subject).to eq(package_file) }
+ end
+
+ context 'with file_name_like' do
+ let(:package_file_name) { package_file.file_name.upcase }
+ let(:params) { { with_file_name_like: true } }
+
+ it { is_expected.to eq(package_file) }
+ end
+ end
+
+ describe '#execute' do
+ subject { described_class.new(package, package_file_name, params).execute }
+
+ it_behaves_like 'package file finder examples'
+
+ context 'with unknown file_name' do
+ let(:package_file_name) { 'unknown.jpg' }
+
+ it { expect(subject).to be_nil }
+ end
+ end
+
+ describe '#execute!' do
+ subject { described_class.new(package, package_file_name, params).execute! }
+
+ it_behaves_like 'package file finder examples'
+
+ context 'with unknown file_name' do
+ let(:package_file_name) { 'unknown.jpg' }
+
+ it { expect { subject }.to raise_error(ActiveRecord::RecordNotFound) }
+ end
+ end
+end
diff --git a/spec/finders/packages/conan/package_finder_spec.rb b/spec/finders/packages/conan/package_finder_spec.rb
new file mode 100644
index 00000000000..936a0e5ff4b
--- /dev/null
+++ b/spec/finders/packages/conan/package_finder_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe ::Packages::Conan::PackageFinder do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public) }
+
+ describe '#execute' do
+ let!(:conan_package) { create(:conan_package, project: project) }
+ let!(:conan_package2) { create(:conan_package, project: project) }
+
+ subject { described_class.new(user, query: query).execute }
+
+ context 'packages that are not visible to user' do
+ let!(:non_visible_project) { create(:project, :private) }
+ let!(:non_visible_conan_package) { create(:conan_package, project: non_visible_project) }
+ let(:query) { "#{conan_package.name.split('/').first[0, 3]}%" }
+
+ it { is_expected.to eq [conan_package, conan_package2] }
+ end
+ end
+end
diff --git a/spec/finders/packages/go/module_finder_spec.rb b/spec/finders/packages/go/module_finder_spec.rb
new file mode 100644
index 00000000000..e5c8827fc8d
--- /dev/null
+++ b/spec/finders/packages/go/module_finder_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Go::ModuleFinder do
+ let_it_be(:project) { create :project }
+ let_it_be(:other_project) { create :project }
+ let(:finder) { described_class.new project, module_name }
+
+ shared_examples 'an invalid path' do
+ describe '#module_name' do
+ it 'returns the expected name' do
+ expect(finder.module_name).to eq(expected_name)
+ end
+ end
+
+ describe '#execute' do
+ it 'returns nil' do
+ expect(finder.execute).to be_nil
+ end
+ end
+ end
+
+ describe '#execute' do
+ context 'with module name equal to project name' do
+ let(:module_name) { base_url(project) }
+
+ it 'returns a module with empty path' do
+ mod = finder.execute
+ expect(mod).not_to be_nil
+ expect(mod.path).to eq('')
+ end
+ end
+
+ context 'with module name starting with project name and slash' do
+ let(:module_name) { base_url(project) + '/mod' }
+
+ it 'returns a module with non-empty path' do
+ mod = finder.execute
+ expect(mod).not_to be_nil
+ expect(mod.path).to eq('mod')
+ end
+ end
+
+ context 'with a module name not equal to and not starting with project name' do
+ let(:module_name) { base_url(other_project) }
+
+ it 'returns nil' do
+ expect(finder.execute).to be_nil
+ end
+ end
+ end
+
+ context 'with relative path component' do
+ it_behaves_like 'an invalid path' do
+ let(:module_name) { base_url(project) + '/../xyz' }
+ let(:expected_name) { base_url(project.namespace) + '/xyz' }
+ end
+ end
+
+ context 'with many relative path components' do
+ it_behaves_like 'an invalid path' do
+ let(:module_name) { base_url(project) + ('/..' * 10) + '/xyz' }
+ let(:expected_name) { ('../' * 7) + 'xyz' }
+ end
+ end
+
+ def base_url(project)
+ "#{Settings.build_gitlab_go_url}/#{project.full_path}"
+ end
+end
diff --git a/spec/finders/packages/go/version_finder_spec.rb b/spec/finders/packages/go/version_finder_spec.rb
new file mode 100644
index 00000000000..b67842d1e05
--- /dev/null
+++ b/spec/finders/packages/go/version_finder_spec.rb
@@ -0,0 +1,160 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Go::VersionFinder do
+ let_it_be(:user) { create :user }
+ let_it_be(:project) { create :project_empty_repo, creator: user, path: 'my-go-lib' }
+
+ let(:finder) { described_class.new mod }
+
+ before :all do
+ create :go_module_commit, :files, project: project, tag: 'v1.0.0', files: { 'README.md' => 'Hi' }
+ create :go_module_commit, :module, project: project, tag: 'v1.0.1'
+ create :go_module_commit, :package, project: project, tag: 'v1.0.2', path: 'pkg'
+ create :go_module_commit, :module, project: project, tag: 'v1.0.3', name: 'mod'
+ create :go_module_commit, :module, project: project, tag: 'v1.0.4', name: 'bad-mod', url: 'example.com/go-lib'
+ create :go_module_commit, :files, project: project, tag: 'c1', files: { 'y.go' => "package a\n" }
+ create :go_module_commit, :module, project: project, tag: 'c2', name: 'v2'
+ create :go_module_commit, :files, project: project, tag: 'v2.0.0', files: { 'v2/x.go' => "package a\n" }
+ end
+
+ before do
+ stub_feature_flags(go_proxy_disable_gomod_validation: false)
+ end
+
+ shared_examples '#execute' do |*expected|
+ it "returns #{expected.empty? ? 'nothing' : expected.join(', ')}" do
+ actual = finder.execute.map { |x| x.name }
+ expect(actual.to_set).to eq(expected.to_set)
+ end
+ end
+
+ shared_examples '#find with an invalid argument' do |message|
+ it "raises an argument exception: #{message}" do
+ expect { finder.find(target) }.to raise_error(ArgumentError, message)
+ end
+ end
+
+ describe '#execute' do
+ context 'for the root module' do
+ let(:mod) { create :go_module, project: project }
+
+ it_behaves_like '#execute', 'v1.0.1', 'v1.0.2', 'v1.0.3', 'v1.0.4'
+ end
+
+ context 'for the package' do
+ let(:mod) { create :go_module, project: project, path: 'pkg' }
+
+ it_behaves_like '#execute'
+ end
+
+ context 'for the submodule' do
+ let(:mod) { create :go_module, project: project, path: 'mod' }
+
+ it_behaves_like '#execute', 'v1.0.3', 'v1.0.4'
+ end
+
+ context 'for the root module v2' do
+ let(:mod) { create :go_module, project: project, path: 'v2' }
+
+ it_behaves_like '#execute', 'v2.0.0'
+ end
+
+ context 'for the bad module' do
+ let(:mod) { create :go_module, project: project, path: 'bad-mod' }
+
+ context 'with gomod checking enabled' do
+ it_behaves_like '#execute'
+ end
+
+ context 'with gomod checking disabled' do
+ before do
+ stub_feature_flags(go_proxy_disable_gomod_validation: true)
+ end
+
+ it_behaves_like '#execute', 'v1.0.4'
+ end
+ end
+ end
+
+ describe '#find' do
+ let(:mod) { create :go_module, project: project }
+
+ context 'with a ref' do
+ it 'returns a ref version' do
+ ref = project.repository.find_branch 'master'
+ v = finder.find(ref)
+ expect(v.type).to eq(:ref)
+ expect(v.ref).to eq(ref)
+ end
+ end
+
+ context 'with a semver tag' do
+ it 'returns a version with a semver' do
+ v = finder.find(project.repository.find_tag('v1.0.0'))
+ expect(v.major).to eq(1)
+ expect(v.minor).to eq(0)
+ expect(v.patch).to eq(0)
+ expect(v.prerelease).to be_nil
+ expect(v.build).to be_nil
+ end
+ end
+
+ context 'with a semver tag string' do
+ it 'returns a version with a semver' do
+ v = finder.find('v1.0.1')
+ expect(v.major).to eq(1)
+ expect(v.minor).to eq(0)
+ expect(v.patch).to eq(1)
+ expect(v.prerelease).to be_nil
+ expect(v.build).to be_nil
+ end
+ end
+
+ context 'with a commit' do
+ it 'retruns a commit version' do
+ v = finder.find(project.repository.head_commit)
+ expect(v.type).to eq(:commit)
+ end
+ end
+
+ context 'with a pseudo-version' do
+ it 'returns a pseudo version' do
+ commit = project.repository.head_commit
+ pseudo = "v0.0.0-#{commit.committed_date.strftime('%Y%m%d%H%M%S')}-#{commit.sha[0..11]}"
+ v = finder.find(pseudo)
+ expect(v.type).to eq(:pseudo)
+ expect(v.commit).to eq(commit)
+ expect(v.name).to eq(pseudo)
+ end
+ end
+
+ context 'with a string that is not a semantic version' do
+ it 'returns nil' do
+ expect(finder.find('not-a-semver')).to be_nil
+ end
+ end
+
+ context 'with a pseudo-version that does not reference a commit' do
+ it_behaves_like '#find with an invalid argument', 'invalid pseudo-version: unknown commit' do
+ let(:commit) { project.repository.head_commit }
+ let(:target) { "v0.0.0-#{commit.committed_date.strftime('%Y%m%d%H%M%S')}-#{'0' * 12}" }
+ end
+ end
+
+ context 'with a pseudo-version with a short sha' do
+ it_behaves_like '#find with an invalid argument', 'invalid pseudo-version: revision is shorter than canonical' do
+ let(:commit) { project.repository.head_commit }
+ let(:target) { "v0.0.0-#{commit.committed_date.strftime('%Y%m%d%H%M%S')}-#{commit.sha[0..10]}" }
+ end
+ end
+
+ context 'with a pseudo-version with an invalid timestamp' do
+ it_behaves_like '#find with an invalid argument', 'invalid pseudo-version: does not match version-control timestamp' do
+ let(:commit) { project.repository.head_commit }
+ let(:target) { "v0.0.0-#{'0' * 14}-#{commit.sha[0..11]}" }
+ end
+ end
+ end
+end
diff --git a/spec/finders/packages/group_packages_finder_spec.rb b/spec/finders/packages/group_packages_finder_spec.rb
new file mode 100644
index 00000000000..163c920f621
--- /dev/null
+++ b/spec/finders/packages/group_packages_finder_spec.rb
@@ -0,0 +1,156 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::GroupPackagesFinder do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, namespace: group) }
+ let(:another_group) { create(:group) }
+
+ before do
+ group.add_developer(user)
+ end
+
+ describe '#execute' do
+ let(:params) { { exclude_subgroups: false } }
+
+ subject { described_class.new(user, group, params).execute }
+
+ shared_examples 'with package type' do |package_type|
+ let(:params) { { exclude_subgroups: false, package_type: package_type } }
+
+ it { is_expected.to match_array([send("package_#{package_type}")]) }
+ end
+
+ def self.package_types
+ @package_types ||= Packages::Package.package_types.keys
+ end
+
+ context 'group has packages' do
+ let!(:package1) { create(:maven_package, project: project) }
+ let!(:package2) { create(:maven_package, project: project) }
+ let!(:package3) { create(:maven_package) }
+
+ it { is_expected.to match_array([package1, package2]) }
+
+ context 'subgroup has packages' do
+ let(:subgroup) { create(:group, parent: group) }
+ let(:subproject) { create(:project, namespace: subgroup) }
+ let!(:package4) { create(:npm_package, project: subproject) }
+
+ it { is_expected.to match_array([package1, package2, package4]) }
+
+ context 'excluding subgroups' do
+ let(:params) { { exclude_subgroups: true } }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+ end
+
+ context 'when there are processing packages' do
+ let!(:package4) { create(:nuget_package, project: project, name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+
+ context 'does not include packages without version number' do
+ let!(:package_without_version) { create(:maven_package, project: project, version: nil) }
+
+ it { is_expected.not_to include(package_without_version) }
+ end
+
+ context 'with package_name' do
+ let_it_be(:named_package) { create(:maven_package, project: project, name: 'maven') }
+ let(:params) { { package_name: package_name } }
+
+ context 'as complete name' do
+ let(:package_name) { 'maven' }
+
+ it { is_expected.to eq([named_package]) }
+ end
+
+ %w[aven mav ave].each do |filter|
+ context "for fuzzy filter #{filter}" do
+ let(:package_name) { filter }
+
+ it { is_expected.to eq([named_package]) }
+ end
+ end
+ end
+ end
+
+ context 'group has package of all types' do
+ package_types.each { |pt| let!("package_#{pt}") { create("#{pt}_package", project: project) } }
+
+ package_types.each do |package_type|
+ it_behaves_like 'with package type', package_type
+ end
+ end
+
+ context 'group has no packages' do
+ it { is_expected.to be_empty }
+ end
+
+ context 'group is nil' do
+ subject { described_class.new(user, nil).execute }
+
+ it { is_expected.to be_empty}
+ end
+
+ context 'package type is nil' do
+ let!(:package1) { create(:maven_package, project: project) }
+
+ subject { described_class.new(user, group, package_type: nil).execute }
+
+ it { is_expected.to match_array([package1])}
+ end
+
+ context 'with invalid package_type' do
+ let(:params) { { package_type: 'invalid_type' } }
+
+ it { expect { subject }.to raise_exception(described_class::InvalidPackageTypeError) }
+ end
+
+ context 'when project is public' do
+ let_it_be(:other_user) { create(:user) }
+ let(:finder) { described_class.new(other_user, group) }
+
+ before do
+ project.update!(visibility_level: ProjectFeature::ENABLED)
+ end
+
+ context 'when packages are public' do
+ before do
+ project.project_feature.update!(
+ builds_access_level: ProjectFeature::PRIVATE,
+ merge_requests_access_level: ProjectFeature::PRIVATE,
+ repository_access_level: ProjectFeature::ENABLED)
+ end
+
+ it 'returns group packages' do
+ package1 = create(:maven_package, project: project)
+ package2 = create(:maven_package, project: project)
+ create(:maven_package)
+
+ expect(finder.execute).to match_array([package1, package2])
+ end
+ end
+
+ context 'packages are members only' do
+ before do
+ project.project_feature.update!(
+ builds_access_level: ProjectFeature::PRIVATE,
+ merge_requests_access_level: ProjectFeature::PRIVATE,
+ repository_access_level: ProjectFeature::PRIVATE)
+
+ create(:maven_package, project: project)
+ create(:maven_package)
+ end
+
+ it 'filters out the project if the user doesn\'t have permission' do
+ expect(finder.execute).to be_empty
+ end
+ end
+ end
+ end
+end
diff --git a/spec/finders/packages/maven/package_finder_spec.rb b/spec/finders/packages/maven/package_finder_spec.rb
new file mode 100644
index 00000000000..239e8c10f52
--- /dev/null
+++ b/spec/finders/packages/maven/package_finder_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe ::Packages::Maven::PackageFinder do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project) { create(:project, namespace: group) }
+ let(:package) { create(:maven_package, project: project) }
+
+ before do
+ group.add_developer(user)
+ end
+
+ describe '#execute!' do
+ context 'within the project' do
+ it 'returns a package' do
+ finder = described_class.new(package.maven_metadatum.path, user, project: project)
+
+ expect(finder.execute!).to eq(package)
+ end
+
+ it 'raises an error' do
+ finder = described_class.new('com/example/my-app/1.0-SNAPSHOT', user, project: project)
+
+ expect { finder.execute! }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ context 'across all projects' do
+ it 'returns a package' do
+ finder = described_class.new(package.maven_metadatum.path, user)
+
+ expect(finder.execute!).to eq(package)
+ end
+
+ it 'raises an error' do
+ finder = described_class.new('com/example/my-app/1.0-SNAPSHOT', user)
+
+ expect { finder.execute! }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ context 'within a group' do
+ it 'returns a package' do
+ finder = described_class.new(package.maven_metadatum.path, user, group: group)
+
+ expect(finder.execute!).to eq(package)
+ end
+
+ it 'raises an error' do
+ finder = described_class.new('com/example/my-app/1.0-SNAPSHOT', user, group: group)
+
+ expect { finder.execute! }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+ end
+end
diff --git a/spec/finders/packages/npm/package_finder_spec.rb b/spec/finders/packages/npm/package_finder_spec.rb
new file mode 100644
index 00000000000..be54b1f8b18
--- /dev/null
+++ b/spec/finders/packages/npm/package_finder_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe ::Packages::Npm::PackageFinder do
+ let(:package) { create(:npm_package) }
+ let(:project) { package.project }
+ let(:package_name) { package.name }
+
+ describe '#execute!' do
+ subject { described_class.new(project, package_name).execute }
+
+ it { is_expected.to eq([package]) }
+
+ context 'with unknown package name' do
+ let(:package_name) { 'baz' }
+
+ it { is_expected.to be_empty }
+ end
+ end
+
+ describe '#find_by_version' do
+ let(:version) { package.version }
+
+ subject { described_class.new(project, package.name).find_by_version(version) }
+
+ it { is_expected.to eq(package) }
+
+ context 'with unknown version' do
+ let(:version) { 'foobar' }
+
+ it { is_expected.to be_nil }
+ end
+ end
+end
diff --git a/spec/finders/packages/nuget/package_finder_spec.rb b/spec/finders/packages/nuget/package_finder_spec.rb
new file mode 100644
index 00000000000..9295d0c7a2f
--- /dev/null
+++ b/spec/finders/packages/nuget/package_finder_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::PackageFinder do
+ let_it_be(:package1) { create(:nuget_package) }
+ let_it_be(:project) { package1.project }
+ let_it_be(:package2) { create(:nuget_package, name: package1.name, version: '2.0.0', project: project) }
+ let_it_be(:package3) { create(:nuget_package, name: 'Another.Dummy.Package', project: project) }
+ let(:package_name) { package1.name }
+ let(:package_version) { nil }
+ let(:limit) { 50 }
+
+ describe '#execute!' do
+ subject { described_class.new(project, package_name: package_name, package_version: package_version, limit: limit).execute }
+
+ it { is_expected.to match_array([package1, package2]) }
+
+ context 'with lower case package name' do
+ let(:package_name) { package1.name.downcase }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+
+ context 'with unknown package name' do
+ let(:package_name) { 'foobar' }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'with valid version' do
+ let(:package_version) { '2.0.0' }
+
+ it { is_expected.to match_array([package2]) }
+ end
+
+ context 'with unknown version' do
+ let(:package_version) { 'foobar' }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'with limit hit' do
+ let_it_be(:package4) { create(:nuget_package, name: package1.name, project: project) }
+ let_it_be(:package5) { create(:nuget_package, name: package1.name, project: project) }
+ let_it_be(:package6) { create(:nuget_package, name: package1.name, project: project) }
+ let(:limit) { 2 }
+
+ it { is_expected.to match_array([package5, package6]) }
+ end
+
+ context 'with downcase package name' do
+ let(:package_name) { package1.name.downcase }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+
+ context 'with prefix wildcard' do
+ let(:package_name) { "%#{package1.name[3..-1]}" }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+
+ context 'with suffix wildcard' do
+ let(:package_name) { "#{package1.name[0..-3]}%" }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+
+ context 'with surrounding wildcards' do
+ let(:package_name) { "%#{package1.name[3..-3]}%" }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+ end
+end
diff --git a/spec/finders/packages/package_file_finder_spec.rb b/spec/finders/packages/package_file_finder_spec.rb
new file mode 100644
index 00000000000..ab58f75fcae
--- /dev/null
+++ b/spec/finders/packages/package_file_finder_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::PackageFileFinder do
+ let_it_be(:package) { create(:maven_package) }
+ let_it_be(:package_file) { package.package_files.first }
+ let(:package_file_name) { package_file.file_name }
+ let(:params) { {} }
+
+ RSpec.shared_examples 'package file finder examples' do
+ it { is_expected.to eq(package_file) }
+
+ context 'with file_name_like' do
+ let(:package_file_name) { package_file.file_name.upcase }
+ let(:params) { { with_file_name_like: true } }
+
+ it { is_expected.to eq(package_file) }
+ end
+ end
+
+ describe '#execute' do
+ subject { described_class.new(package, package_file_name, params).execute }
+
+ it_behaves_like 'package file finder examples'
+
+ context 'with unknown file_name' do
+ let(:package_file_name) { 'unknown.jpg' }
+
+ it { expect(subject).to be_nil }
+ end
+ end
+
+ describe '#execute!' do
+ subject { described_class.new(package, package_file_name, params).execute! }
+
+ it_behaves_like 'package file finder examples'
+
+ context 'with unknown file_name' do
+ let(:package_file_name) { 'unknown.jpg' }
+
+ it { expect { subject }.to raise_error(ActiveRecord::RecordNotFound) }
+ end
+ end
+end
diff --git a/spec/finders/packages/package_finder_spec.rb b/spec/finders/packages/package_finder_spec.rb
new file mode 100644
index 00000000000..ef07e7575d1
--- /dev/null
+++ b/spec/finders/packages/package_finder_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Packages::PackageFinder do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:maven_package) { create(:maven_package, project: project) }
+
+ describe '#execute' do
+ let(:package_id) { maven_package.id }
+
+ subject { described_class.new(project, package_id).execute }
+
+ it { is_expected.to eq(maven_package) }
+
+ context 'processing packages' do
+ let_it_be(:nuget_package) { create(:nuget_package, project: project, name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) }
+ let(:package_id) { nuget_package.id }
+
+ it 'are not returned' do
+ expect { subject }.to raise_exception(ActiveRecord::RecordNotFound)
+ end
+ end
+ end
+end
diff --git a/spec/finders/packages/packages_finder_spec.rb b/spec/finders/packages/packages_finder_spec.rb
new file mode 100644
index 00000000000..925b003bb8e
--- /dev/null
+++ b/spec/finders/packages/packages_finder_spec.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Packages::PackagesFinder do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:maven_package) { create(:maven_package, project: project, created_at: 2.days.ago, name: 'maven', version: '2.0.0') }
+ let_it_be(:conan_package) { create(:conan_package, project: project, created_at: 1.day.ago, name: 'conan', version: '1.0.0') }
+
+ describe '#execute' do
+ let(:params) { {} }
+
+ subject { described_class.new(project, params).execute }
+
+ context 'with package_type' do
+ let_it_be(:npm_package1) { create(:npm_package, project: project) }
+ let_it_be(:npm_package2) { create(:npm_package, project: project) }
+
+ context 'conan packages' do
+ let(:params) { { package_type: 'conan' } }
+
+ it { is_expected.to eq([conan_package]) }
+ end
+
+ context 'npm packages' do
+ let(:params) { { package_type: 'npm' } }
+
+ it { is_expected.to match_array([npm_package1, npm_package2]) }
+ end
+ end
+
+ context 'with order_by' do
+ context 'by default is created_at' do
+ it { is_expected.to eq([maven_package, conan_package]) }
+ end
+
+ context 'order by name' do
+ let(:params) { { order_by: 'name' } }
+
+ it { is_expected.to eq([conan_package, maven_package]) }
+ end
+
+ context 'order by version' do
+ let(:params) { { order_by: 'version' } }
+
+ it { is_expected.to eq([conan_package, maven_package]) }
+ end
+
+ context 'order by type' do
+ let(:params) { { order_by: 'type' } }
+
+ it { is_expected.to eq([maven_package, conan_package]) }
+ end
+ end
+
+ context 'with sort' do
+ context 'by default is ascending' do
+ it { is_expected.to eq([maven_package, conan_package]) }
+ end
+
+ context 'can sort descended' do
+ let(:params) { { sort: 'desc' } }
+
+ it { is_expected.to eq([conan_package, maven_package]) }
+ end
+ end
+
+ context 'with package_name' do
+ let(:params) { { package_name: 'maven' } }
+
+ it { is_expected.to eq([maven_package]) }
+ end
+
+ context 'with nil params' do
+ it { is_expected.to match_array([conan_package, maven_package]) }
+ end
+
+ context 'with processing packages' do
+ let_it_be(:nuget_package) { create(:nuget_package, project: project, name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) }
+
+ it { is_expected.to match_array([conan_package, maven_package]) }
+ end
+
+ context 'does not include packages without version number' do
+ let_it_be(:package_without_version) { create(:maven_package, project: project, version: nil) }
+
+ it { is_expected.not_to include(package_without_version) }
+ end
+ end
+end
diff --git a/spec/finders/packages/tags_finder_spec.rb b/spec/finders/packages/tags_finder_spec.rb
new file mode 100644
index 00000000000..47e1d25debf
--- /dev/null
+++ b/spec/finders/packages/tags_finder_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::TagsFinder do
+ let(:package) { create(:npm_package) }
+ let(:project) { package.project }
+ let!(:tag1) { create(:packages_tag, package: package) }
+ let!(:tag2) { create(:packages_tag, package: package) }
+ let(:package_name) { package.name }
+ let(:params) { {} }
+
+ describe '#execute' do
+ subject { described_class.new(project, package_name, params).execute }
+
+ it { is_expected.to match_array([tag1, tag2]) }
+
+ context 'with package type' do
+ let(:package_maven) { create(:maven_package, project: project) }
+ let!(:tag_maven) { create(:packages_tag, package: package_maven) }
+ let(:package_name) { package_maven.name }
+ let(:params) { { package_type: package_maven.package_type } }
+
+ it { is_expected.to match_array([tag_maven]) }
+ end
+
+ context 'with blank package type' do
+ let(:params) { { package_type: ' ' } }
+
+ it { is_expected.to match_array([tag1, tag2]) }
+ end
+
+ context 'with nil package type' do
+ let(:params) { { package_type: nil } }
+
+ it { is_expected.to match_array([tag1, tag2]) }
+ end
+
+ context 'with unknown package name' do
+ let(:package_name) { 'foobar' }
+
+ it { is_expected.to be_empty }
+ end
+ end
+
+ describe '#find_by_name' do
+ let(:tag_name) { tag1.name }
+
+ subject { described_class.new(project, package_name, params).execute.find_by_name(tag_name) }
+
+ it { is_expected.to eq(tag1) }
+
+ context 'with package type' do
+ let(:package_maven) { create(:maven_package, project: project) }
+ let!(:tag_maven) { create(:packages_tag, package: package_maven) }
+ let(:package_name) { package_maven.name }
+ let(:params) { { package_type: package_maven.package_type } }
+ let(:tag_name) { tag_maven.name }
+
+ it { is_expected.to eq(tag_maven) }
+ end
+
+ context 'with unknown tag_name' do
+ let(:tag_name) { 'foobar' }
+
+ it { is_expected.to be_nil }
+ end
+ end
+end
diff --git a/spec/finders/personal_access_tokens_finder_spec.rb b/spec/finders/personal_access_tokens_finder_spec.rb
index dde4f010e41..94954f4153b 100644
--- a/spec/finders/personal_access_tokens_finder_spec.rb
+++ b/spec/finders/personal_access_tokens_finder_spec.rb
@@ -218,6 +218,24 @@ RSpec.describe PersonalAccessTokensFinder do
end
end
+ describe 'with active or expired state' do
+ before do
+ params[:state] = 'active_or_expired'
+ end
+
+ it 'includes active tokens' do
+ is_expected.to include(active_personal_access_token, active_impersonation_token)
+ end
+
+ it 'includes expired tokens' do
+ is_expected.to include(expired_personal_access_token, expired_impersonation_token)
+ end
+
+ it 'does not include revoked tokens' do
+ is_expected.not_to include(revoked_personal_access_token, revoked_impersonation_token)
+ end
+ end
+
describe 'with id' do
subject { finder(params).find_by_id(active_personal_access_token.id) }
diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb
index 929927ec1c4..bd71a8186ad 100644
--- a/spec/finders/projects_finder_spec.rb
+++ b/spec/finders/projects_finder_spec.rb
@@ -262,6 +262,17 @@ RSpec.describe ProjectsFinder, :do_not_mock_admin_mode do
it { is_expected.to match_array([public_project]) }
end
+ describe 'filter by repository_storage' do
+ let(:params) { { repository_storage: 'nfs-05' } }
+ let!(:project) { create(:project, :public) }
+
+ before do
+ project.update_columns(repository_storage: 'nfs-05')
+ end
+
+ it { is_expected.to match_array([project]) }
+ end
+
describe 'sorting' do
let(:params) { { sort: 'name_asc' } }
diff --git a/spec/finders/resource_milestone_event_finder_spec.rb b/spec/finders/resource_milestone_event_finder_spec.rb
index ff4508996e2..27e124afe2e 100644
--- a/spec/finders/resource_milestone_event_finder_spec.rb
+++ b/spec/finders/resource_milestone_event_finder_spec.rb
@@ -42,18 +42,6 @@ RSpec.describe ResourceMilestoneEventFinder do
expect(subject).to be_empty
end
- it 'paginates results' do
- milestone = create(:milestone, project: issue_project)
- create_event(milestone)
- create_event(milestone)
- issue_project.add_guest(user)
-
- paginated = described_class.new(user, issue, per_page: 1).execute
-
- expect(subject.count).to eq 2
- expect(paginated.count).to eq 1
- end
-
context 'when multiple events share the same milestone' do
it 'avoids N+1 queries' do
issue_project.add_developer(user)
@@ -71,8 +59,8 @@ RSpec.describe ResourceMilestoneEventFinder do
create_event(milestone2, :add)
create_event(milestone2, :remove)
- # 1 events + 1 milestones + 1 project + 1 user + 4 ability
- expect { described_class.new(user, issue).execute }.not_to exceed_query_limit(control_count + 7)
+ # 1 milestones + 1 project + 1 user + 4 ability
+ expect { described_class.new(user, issue).execute }.not_to exceed_query_limit(control_count + 6)
end
end
diff --git a/spec/finders/resource_state_event_finder_spec.rb b/spec/finders/resource_state_event_finder_spec.rb
new file mode 100644
index 00000000000..139ef549834
--- /dev/null
+++ b/spec/finders/resource_state_event_finder_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ResourceStateEventFinder do
+ let_it_be(:user) { create(:user) }
+
+ describe '#execute' do
+ subject { described_class.new(user, issue).execute }
+
+ let(:project) { create(:project) }
+ let(:issue) { create(:issue, project: project) }
+
+ let!(:event) { create(:resource_state_event, issue: issue) }
+
+ it 'returns events accessible by user' do
+ project.add_guest(user)
+
+ expect(subject).to eq [event]
+ end
+
+ context 'when issues are private' do
+ let(:project) { create(:project, :public, :issues_private) }
+
+ it 'does not return any events' do
+ expect(subject).to be_empty
+ end
+ end
+
+ context 'when issue is not accesible to the user' do
+ let(:project) { create(:project, :private) }
+
+ it 'does not return any events' do
+ expect(subject).to be_empty
+ end
+ end
+ end
+
+ describe '#can_read_eventable?' do
+ let(:project) { create(:project, :private) }
+
+ subject { described_class.new(user, eventable).can_read_eventable? }
+
+ context 'when eventable is an Issue' do
+ let(:eventable) { create(:issue, project: project) }
+
+ context 'when issue is readable' do
+ before do
+ project.add_developer(user)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when issue is not readable' do
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ context 'when eventable is a MergeRequest' do
+ let(:eventable) { create(:merge_request, source_project: project) }
+
+ context 'when merge request is readable' do
+ before do
+ project.add_developer(user)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when merge request is not readable' do
+ it { is_expected.to be_falsey }
+ end
+ end
+ end
+end
diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb
index 0affc832b30..6fc1cbcee0a 100644
--- a/spec/finders/snippets_finder_spec.rb
+++ b/spec/finders/snippets_finder_spec.rb
@@ -283,6 +283,12 @@ RSpec.describe SnippetsFinder do
it 'returns only personal snippets when the user cannot read cross project' do
expect(described_class.new(user).execute).to contain_exactly(private_personal_snippet, internal_personal_snippet, public_personal_snippet)
end
+
+ context 'when only project snippets are required' do
+ it 'returns no records' do
+ expect(described_class.new(user, only_project: true).execute).to be_empty
+ end
+ end
end
context 'when project snippets are disabled' do
@@ -295,6 +301,22 @@ RSpec.describe SnippetsFinder do
expect(finder.execute).to be_empty
end
end
+
+ context 'no sort param is provided' do
+ it 'returns snippets sorted by id' do
+ snippets = described_class.new(admin).execute
+
+ expect(snippets.ids).to eq(Snippet.order_id_desc.ids)
+ end
+ end
+
+ context 'sort param is provided' do
+ it 'returns snippets sorted by sort param' do
+ snippets = described_class.new(admin, sort: 'updated_desc').execute
+
+ expect(snippets.ids).to eq(Snippet.order_updated_desc.ids)
+ end
+ end
end
it_behaves_like 'snippet visibility'
diff --git a/spec/finders/todos_finder_spec.rb b/spec/finders/todos_finder_spec.rb
index 4123783d828..f6796398782 100644
--- a/spec/finders/todos_finder_spec.rb
+++ b/spec/finders/todos_finder_spec.rb
@@ -37,16 +37,63 @@ RSpec.describe TodosFinder do
end
context 'when filtering by type' do
- it 'returns correct todos when filtered by a type' do
+ it 'returns todos by type when filtered by a single type' do
todos = finder.new(user, { type: 'Issue' }).execute
expect(todos).to match_array([todo1])
end
- it 'returns the correct todos when filtering for multiple types' do
+ it 'returns todos by type when filtered by multiple types' do
+ design_todo = create(:todo, user: user, group: group, target: create(:design))
+
todos = finder.new(user, { type: %w[Issue MergeRequest] }).execute
- expect(todos).to match_array([todo1, todo2])
+ expect(todos).to contain_exactly(todo1, todo2)
+ expect(todos).not_to include(design_todo)
+ end
+
+ it 'returns all todos when type is nil' do
+ todos = finder.new(user, { type: nil }).execute
+
+ expect(todos).to contain_exactly(todo1, todo2)
+ end
+
+ it 'returns all todos when type is an empty collection' do
+ todos = finder.new(user, { type: [] }).execute
+
+ expect(todos).to contain_exactly(todo1, todo2)
+ end
+
+ it 'returns all todos when type is blank' do
+ todos = finder.new(user, { type: '' }).execute
+
+ expect(todos).to contain_exactly(todo1, todo2)
+ end
+
+ it 'returns todos by type when blank type is in type collection' do
+ todos = finder.new(user, { type: ['', 'MergeRequest'] }).execute
+
+ expect(todos).to contain_exactly(todo2)
+ end
+
+ it 'returns todos of all types when only blanks are in a collection' do
+ todos = finder.new(user, { type: ['', ''] }).execute
+
+ expect(todos).to contain_exactly(todo1, todo2)
+ end
+
+ it 'returns all todos when no type param' do
+ todos = finder.new(user).execute
+
+ expect(todos).to contain_exactly(todo1, todo2)
+ end
+
+ it 'raises an argument error when invalid type is passed' do
+ create(:todo, user: user, group: group, target: create(:design))
+
+ todos_finder = finder.new(user, { type: %w[Issue MergeRequest NotAValidType] })
+
+ expect { todos_finder.execute }.to raise_error(ArgumentError)
end
end
diff --git a/spec/finders/user_recent_events_finder_spec.rb b/spec/finders/user_recent_events_finder_spec.rb
index 04ba05c68e4..559d1004b4b 100644
--- a/spec/finders/user_recent_events_finder_spec.rb
+++ b/spec/finders/user_recent_events_finder_spec.rb
@@ -3,8 +3,8 @@
require 'spec_helper'
RSpec.describe UserRecentEventsFinder do
- let(:current_user) { create(:user) }
- let(:project_owner) { create(:user) }
+ let_it_be(:project_owner, reload: true) { create(:user) }
+ let_it_be(:current_user, reload: true) { create(:user) }
let(:private_project) { create(:project, :private, creator: project_owner) }
let(:internal_project) { create(:project, :internal, creator: project_owner) }
let(:public_project) { create(:project, :public, creator: project_owner) }
@@ -36,5 +36,17 @@ RSpec.describe UserRecentEventsFinder do
expect(finder.execute).to be_empty
end
+
+ describe 'design activity events' do
+ let_it_be(:event_a) { create(:design_event, author: project_owner) }
+ let_it_be(:event_b) { create(:design_event, author: project_owner) }
+
+ it 'only includes design events', :aggregate_failures do
+ events = finder.execute
+
+ expect(events).to include(event_a)
+ expect(events).to include(event_b)
+ end
+ end
end
end
diff --git a/spec/fixtures/api/graphql/introspection.graphql b/spec/fixtures/api/graphql/introspection.graphql
index 7b712068fcd..6b6de2efbaf 100644
--- a/spec/fixtures/api/graphql/introspection.graphql
+++ b/spec/fixtures/api/graphql/introspection.graphql
@@ -1,9 +1,15 @@
# pulled from GraphiQL query
query IntrospectionQuery {
__schema {
- queryType { name }
- mutationType { name }
- subscriptionType { name }
+ queryType {
+ name
+ }
+ mutationType {
+ name
+ }
+ subscriptionType {
+ name
+ }
types {
...FullType
}
@@ -54,7 +60,9 @@ fragment FullType on __Type {
fragment InputValue on __InputValue {
name
description
- type { ...TypeRef }
+ type {
+ ...TypeRef
+ }
defaultValue
}
diff --git a/spec/fixtures/api/schemas/entities/dag_job.json b/spec/fixtures/api/schemas/entities/dag_job.json
new file mode 100644
index 00000000000..171ac23ca06
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/dag_job.json
@@ -0,0 +1,10 @@
+{
+ "type": "object",
+ "required": ["name", "scheduling_type"],
+ "properties": {
+ "name": { "type": "string" },
+ "scheduling_type": { "type": ["string", null] },
+ "needs": { "type": "array" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/entities/dag_job_group.json b/spec/fixtures/api/schemas/entities/dag_job_group.json
new file mode 100644
index 00000000000..69a4e69fc63
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/dag_job_group.json
@@ -0,0 +1,13 @@
+{
+ "type": "object",
+ "required": ["name", "size", "jobs"],
+ "properties": {
+ "name": { "type": "string" },
+ "size": { "type": "integer" },
+ "jobs": {
+ "type": "array",
+ "items": { "$ref": "dag_job.json" }
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/entities/dag_pipeline.json b/spec/fixtures/api/schemas/entities/dag_pipeline.json
new file mode 100644
index 00000000000..a661577d040
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/dag_pipeline.json
@@ -0,0 +1,11 @@
+{
+ "type": "object",
+ "required": ["stages"],
+ "properties": {
+ "stages": {
+ "type": "array",
+ "items": { "$ref": "dag_stage.json" }
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/entities/dag_stage.json b/spec/fixtures/api/schemas/entities/dag_stage.json
new file mode 100644
index 00000000000..6a7b1424ec5
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/dag_stage.json
@@ -0,0 +1,11 @@
+{
+ "type": "object",
+ "required": ["name", "groups"],
+ "properties": {
+ "name": { "type": "string" },
+ "groups": {
+ "type": "array",
+ "items": { "$ref": "dag_job_group.json" }
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/evidences/build_artifact.json b/spec/fixtures/api/schemas/evidences/build_artifact.json
new file mode 100644
index 00000000000..850fb8c45a8
--- /dev/null
+++ b/spec/fixtures/api/schemas/evidences/build_artifact.json
@@ -0,0 +1,10 @@
+{
+ "type": "object",
+ "required": [
+ "url"
+ ],
+ "properties": {
+ "url": { "type": "string" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/evidences/release.json b/spec/fixtures/api/schemas/evidences/release.json
index 37eb9a9b5c0..529abfe490e 100644
--- a/spec/fixtures/api/schemas/evidences/release.json
+++ b/spec/fixtures/api/schemas/evidences/release.json
@@ -19,6 +19,10 @@
"milestones": {
"type": "array",
"items": { "$ref": "milestone.json" }
+ },
+ "report_artifacts": {
+ "type": "array",
+ "items": { "$ref": "build_artifact.json" }
}
},
"additionalProperties": false
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/composer/index.json b/spec/fixtures/api/schemas/public_api/v4/packages/composer/index.json
new file mode 100644
index 00000000000..2245b39cabe
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/composer/index.json
@@ -0,0 +1,29 @@
+{
+ "type": "object",
+ "required": ["packages", "provider-includes", "providers-url"],
+ "properties": {
+ "packages": {
+ "type": "array",
+ "items": { "type": "integer" }
+ },
+ "providers-url": {
+ "type": "string"
+ },
+ "provider-includes": {
+ "type": "object",
+ "required": ["p/%hash%.json"],
+ "properties": {
+ "p/%hash%.json": {
+ "type": "object",
+ "required": ["sha256"],
+ "properties": {
+ "sha256": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/composer/package.json b/spec/fixtures/api/schemas/public_api/v4/packages/composer/package.json
new file mode 100644
index 00000000000..324a8a4e00c
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/composer/package.json
@@ -0,0 +1,65 @@
+{
+ "type": "object",
+ "required": [
+ "packages"
+ ],
+ "properties": {
+ "packages": {
+ "type": "object",
+ "propertyNames": {
+ "pattern": "^[A-Za-z_]+"
+ },
+ "patternProperties": {
+ "^[A-Za-z_]+": {
+ "type": "object",
+ "propertyNames": {
+ "pattern": "^[A-Za-z_0-9.]+"
+ },
+ "patternProperties": {
+ "^[A-Za-z_0-9.]+": {
+ "type": "object",
+ "required": [
+ "dist",
+ "uid",
+ "version"
+ ],
+ "properties": {
+ "uid": {
+ "type": "integer"
+ },
+ "version": {
+ "type": "string"
+ },
+ "dist": {
+ "type": "object",
+ "required": [
+ "type",
+ "url",
+ "reference",
+ "shasum"
+ ],
+ "properties": {
+ "type": {
+ "type": "string"
+ },
+ "url": {
+ "type": "string"
+ },
+ "reference": {
+ "type": "string"
+ },
+ "shasum": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/composer/provider.json b/spec/fixtures/api/schemas/public_api/v4/packages/composer/provider.json
new file mode 100644
index 00000000000..5335fa9ad64
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/composer/provider.json
@@ -0,0 +1,25 @@
+{
+ "type": "object",
+ "required": ["providers"],
+ "properties": {
+ "providers": {
+ "type": "object",
+ "propertyNames": {
+ "pattern": "^[A-Za-z_]+"
+ },
+ "patternProperties": {
+ "^[A-Za-z_]+": {
+ "type": "object",
+ "required": ["sha256"],
+ "properties": {
+ "sha256": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/group_package.json b/spec/fixtures/api/schemas/public_api/v4/packages/group_package.json
new file mode 100644
index 00000000000..f18e314a287
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/group_package.json
@@ -0,0 +1,33 @@
+{
+ "type": "object",
+ "required": ["name", "version", "package_type", "_links", "project_id", "project_path"],
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "version": {
+ "type": "string"
+ },
+ "package_type": {
+ "type": "string"
+ },
+ "_links": {
+ "type": "object",
+ "required": ["web_path"],
+ "properties": {
+ "details": {
+ "type": "string"
+ }
+ }
+ },
+ "created_at": {
+ "type": "string"
+ },
+ "project_id": {
+ "type": "integer"
+ },
+ "project_path": {
+ "type": "string"
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/group_packages.json b/spec/fixtures/api/schemas/public_api/v4/packages/group_packages.json
new file mode 100644
index 00000000000..3169bbc8f25
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/group_packages.json
@@ -0,0 +1,4 @@
+{
+ "type": "array",
+ "items": { "$ref": "./group_package.json" }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/npm_package.json b/spec/fixtures/api/schemas/public_api/v4/packages/npm_package.json
new file mode 100644
index 00000000000..d7e8a872abe
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/npm_package.json
@@ -0,0 +1,8 @@
+{
+ "type": "object",
+ "required" : ["name", "versions"],
+ "properties" : {
+ "name": { "type": "string" },
+ "versions": { "type": "object" }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/npm_package_tags.json b/spec/fixtures/api/schemas/public_api/v4/packages/npm_package_tags.json
new file mode 100644
index 00000000000..db64fe1de23
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/npm_package_tags.json
@@ -0,0 +1,7 @@
+{
+ "type": "object",
+ "properties" : {
+ "$tag": { "type": "string" },
+ "$version": { "type": "string" }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/npm_package_version.json b/spec/fixtures/api/schemas/public_api/v4/packages/npm_package_version.json
new file mode 100644
index 00000000000..3e74dc0a1c2
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/npm_package_version.json
@@ -0,0 +1,46 @@
+{
+ "type": "object",
+ "required": ["name", "version", "dist"],
+ "properties" : {
+ "name": { "type": "string" },
+ "version": { "type": "string" },
+ "dist": {
+ "type": "object",
+ "required": ["shasum", "tarball"],
+ "properties" : {
+ "shasum": { "type": "string" },
+ "tarball": { "type": "string" }
+ }
+ },
+ "dependencies": {
+ "type": "object",
+ "patternProperties": {
+ ".{1,}": { "type": "string" }
+ }
+ },
+ "devDependencies": {
+ "type": "object",
+ "patternProperties": {
+ ".{1,}": { "type": "string" }
+ }
+ },
+ "bundleDependencies": {
+ "type": "object",
+ "patternProperties": {
+ ".{1,}": { "type": "string" }
+ }
+ },
+ "peerDependencies": {
+ "type": "object",
+ "patternProperties": {
+ ".{1,}": { "type": "string" }
+ }
+ },
+ "deprecated": {
+ "type": "object",
+ "patternProperties": {
+ ".{1,}": { "type": "string" }
+ }
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/nuget/dependency_group.json b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/dependency_group.json
new file mode 100644
index 00000000000..87dc2794b61
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/dependency_group.json
@@ -0,0 +1,22 @@
+{
+ "type": "object",
+ "required": ["@id", "@type", "dependencies"],
+ "properties": {
+ "@id": { "type": "string" },
+ "@type": { "const": "PackageDependencyGroup" },
+ "targetFramework": { "type": "string" },
+ "dependencies": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["@id", "@type", "id", "range"],
+ "properties": {
+ "@id": { "type": "string" },
+ "@type": { "const": "PackageDependency" },
+ "id": { "type": "string" },
+ "range": { "type": "string" }
+ }
+ }
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/nuget/download_versions.json b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/download_versions.json
new file mode 100644
index 00000000000..ab40dfbbc4c
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/download_versions.json
@@ -0,0 +1,10 @@
+{
+ "type": "object",
+ "required": ["versions"],
+ "properties": {
+ "versions": {
+ "type": "array",
+ "items": { "type": "string" }
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/nuget/package_metadata.json b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/package_metadata.json
new file mode 100644
index 00000000000..1244cbe474e
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/package_metadata.json
@@ -0,0 +1,28 @@
+{
+ "type": "object",
+ "required": ["@id", "packageContent", "catalogEntry"],
+ "properties": {
+ "@id": { "type": "string" },
+ "packageContent": { "type": "string" },
+ "catalogEntry": {
+ "type": "object",
+ "required": ["@id", "authors", "dependencyGroups", "id", "packageContent", "summary", "version"],
+ "properties": {
+ "@id": { "type": "string" },
+ "authors": { "const": "" },
+ "id": { "type": "string" },
+ "packageContent": { "type": "string" },
+ "summary": { "const": "" },
+ "tags": { "type": "string" },
+ "projectUrl": { "type": "string" },
+ "licenseUrl": { "type": "string" },
+ "iconUrl": { "type": "string" },
+ "version": { "type": "string" },
+ "dependencyGroups": {
+ "type": "array",
+ "items": { "$ref": "./dependency_group.json" }
+ }
+ }
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/nuget/packages_metadata.json b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/packages_metadata.json
new file mode 100644
index 00000000000..724df5a437d
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/packages_metadata.json
@@ -0,0 +1,54 @@
+{
+ "type": "object",
+ "required": ["count", "items"],
+ "properties": {
+ "count": { "const": 0 },
+ "items": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["lower", "upper", "count", "items"],
+ "properties": {
+ "lower": { "type": "string" },
+ "upper": { "type": "string" },
+ "count": { "type": "integer" },
+ "items": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["@id", "packageContent", "catalogEntry"],
+ "properties": {
+ "@id": { "type": "string" },
+ "packageContent": { "type": "string" },
+ "catalogEntry": {
+ "type": "object",
+ "required": ["@id", "authors", "dependencyGroups", "id", "packageContent", "summary", "version"],
+ "properties": {
+ "@id": { "type": "string" },
+ "authors": { "const": "" },
+ "id": { "type": "string" },
+ "packageContent": { "type": "string" },
+ "summary": { "const": "" },
+ "tags": { "type": "string" },
+ "projectUrl": { "type": "string" },
+ "licenseUrl": { "type": "string" },
+ "iconUrl": { "type": "string" },
+ "version": { "type": "string" },
+ "dependencyGroups": {
+ "type": "array",
+ "items": { "$ref": "./dependency_group.json" }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+
+
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/nuget/search.json b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/search.json
new file mode 100644
index 00000000000..73d0927e24c
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/search.json
@@ -0,0 +1,39 @@
+{
+ "type": "object",
+ "required": ["totalHits", "data"],
+ "properties": {
+ "totalHits": { "type": "integer" },
+ "data": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["@type", "authors", "id", "summary", "title", "totalDownloads", "verified", "versions"],
+ "properties": {
+ "@type": { "const": "Package" },
+ "authors": { "const": "" },
+ "id": { "type": "string" },
+ "summary": { "const": "" },
+ "title": { "type": "string" },
+ "totalDownloads": { "const": 0 },
+ "verified": { "const": true },
+ "tags": { "type": "string" },
+ "projectUrl": { "type": "string" },
+ "licenseUrl": { "type": "string" },
+ "iconUrl": { "type": "string" },
+ "versions": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["@id", "version", "downloads"],
+ "properties": {
+ "@id": { "type": "string" },
+ "version": { "type": "string" },
+ "downloads": { "const": 0 }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/nuget/service_index.json b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/service_index.json
new file mode 100644
index 00000000000..405018f8c37
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/nuget/service_index.json
@@ -0,0 +1,19 @@
+{
+ "type": "object",
+ "required": ["version", "resources"],
+ "properties": {
+ "version": { "const": "3.0.0" },
+ "resources": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["@id", "@type", "comment"],
+ "properties": {
+ "@id": { "type": "string" },
+ "@type": { "type": "string" },
+ "comment": { "type": "string" }
+ }
+ }
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/package.json b/spec/fixtures/api/schemas/public_api/v4/packages/package.json
new file mode 100644
index 00000000000..757e5fd26b6
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/package.json
@@ -0,0 +1,41 @@
+{
+ "type": "object",
+ "required": [
+ "name",
+ "version",
+ "package_type",
+ "_links",
+ "versions"
+ ],
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "version": {
+ "type": "string"
+ },
+ "package_type": {
+ "type": "string"
+ },
+ "_links": {
+ "type": "object",
+ "required": [
+ "web_path"
+ ],
+ "properties": {
+ "details": {
+ "type": "string"
+ }
+ }
+ },
+ "created_at": {
+ "type": "string"
+ },
+ "versions": {
+ "type": "array",
+ "items": {
+ "$ref": "package_version.json"
+ }
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/package_files.json b/spec/fixtures/api/schemas/public_api/v4/packages/package_files.json
new file mode 100644
index 00000000000..f057adba65c
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/package_files.json
@@ -0,0 +1,13 @@
+{
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required" : ["id", "package_id", "file_name"],
+ "properties" : {
+ "id": { "type": "integer" },
+ "package_id": { "type": "integer" },
+ "file_name": { "type": "string" },
+ "file_sha1": { "type": "string" }
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/package_version.json b/spec/fixtures/api/schemas/public_api/v4/packages/package_version.json
new file mode 100644
index 00000000000..72f2a2121ad
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/package_version.json
@@ -0,0 +1,19 @@
+{
+ "type": "object",
+ "required": [
+ "version",
+ "created_at",
+ "pipeline"
+ ],
+ "properties": {
+ "version": {
+ "type": "string"
+ },
+ "created_at": {
+ "type": "string"
+ },
+ "pipeline": {
+ "$ref": "../pipeline.json"
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/package_with_build.json b/spec/fixtures/api/schemas/public_api/v4/packages/package_with_build.json
new file mode 100644
index 00000000000..de3ef94138e
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/package_with_build.json
@@ -0,0 +1,10 @@
+{
+ "type": "object",
+ "required": ["name", "version", "package_type", "pipeline"],
+ "properties": {
+ "name": { "type": "string" },
+ "version": { "type": "string" },
+ "package_type": { "type": "string" },
+ "pipeline": { "$ref": "../pipeline.json" }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/packages.json b/spec/fixtures/api/schemas/public_api/v4/packages/packages.json
new file mode 100644
index 00000000000..66364da4fdb
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/packages.json
@@ -0,0 +1,4 @@
+{
+ "type": "array",
+ "items": { "$ref": "./package.json" }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/pipeline.json b/spec/fixtures/api/schemas/public_api/v4/pipeline.json
new file mode 100644
index 00000000000..f83844a115d
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/pipeline.json
@@ -0,0 +1,27 @@
+{
+ "type": "object",
+ "required": ["id", "sha", "ref", "status", "created_at", "updated_at", "web_url"],
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "sha": {
+ "type": "string"
+ },
+ "ref": {
+ "type": "string"
+ },
+ "status": {
+ "type": "string"
+ },
+ "created_at": {
+ "type": "string"
+ },
+ "updated_at": {
+ "type": "string"
+ },
+ "web_url": {
+ "type": "string"
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/snippets.json b/spec/fixtures/api/schemas/public_api/v4/snippets.json
index 7baa24a6f1f..de658e01657 100644
--- a/spec/fixtures/api/schemas/public_api/v4/snippets.json
+++ b/spec/fixtures/api/schemas/public_api/v4/snippets.json
@@ -7,6 +7,16 @@
"project_id": { "type": ["integer", "null"] },
"title": { "type": "string" },
"file_name": { "type": ["string", "null"] },
+ "files" : {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "path": { "type": "string" },
+ "raw_url": { "type": "string" }
+ }
+ }
+ },
"description": { "type": ["string", "null"] },
"visibility": { "type": "string" },
"web_url": { "type": "string" },
diff --git a/spec/fixtures/clusters/ca_certificate.pem b/spec/fixtures/clusters/ca_certificate.pem
new file mode 100644
index 00000000000..9e6810ab70c
--- /dev/null
+++ b/spec/fixtures/clusters/ca_certificate.pem
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
+RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
+PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
+xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
+Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
+hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
+EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
+FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
+nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
+eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
+hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
+Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
+vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
++OkuE6N36B9K
+-----END CERTIFICATE-----
diff --git a/spec/fixtures/clusters/chain_certificates.pem b/spec/fixtures/clusters/chain_certificates.pem
new file mode 100644
index 00000000000..b8e64d58ee7
--- /dev/null
+++ b/spec/fixtures/clusters/chain_certificates.pem
@@ -0,0 +1,100 @@
+-----BEGIN CERTIFICATE-----
+MIIItjCCB56gAwIBAgIQCu5Ga1hR41iahM0SWhyeNjANBgkqhkiG9w0BAQsFADB1
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk
+IFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTE5MTIwNDAwMDAwMFoXDTIxMTIwODEy
+MDAwMFowgb0xHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB
+BAGCNzwCAQMTAlVTMRUwEwYLKwYBBAGCNzwCAQITBFV0YWgxFTATBgNVBAUTDDUy
+OTk1MzctMDE0MjELMAkGA1UEBhMCVVMxDTALBgNVBAgTBFV0YWgxDTALBgNVBAcT
+BExlaGkxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMRUwEwYDVQQDEwxkaWdpY2Vy
+dC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAeRYb/RLbljGZ
+IB//DrEdyKYMQqqaJwBlrr3t2paAWNuDJizvVkTMIzdJesI1pA58Myenxp5Dp8GJ
+u/VhBf//v/HAZHUE4xwu104Fg6A1BwUEKgVKERf+7kTt17Lf9fcMIjMyL+FeyPXb
+DOFbH+ej/nYaneFLch2j2xWZg1+Thk0qBlGE8WWAK+fvbEuM0SOeH9RkYFCNGPRS
+KsLn0GvaCnnD4LfNDyMqYop0IpaqXoREEnkRv1MVSOw+hBj497wnnO+/GZegfzwU
+iS60h+PjlDfmdCP18qOS7tRd0qnfU3N3S+PYEd3R63LMcIfbgXNEEWBNKpiH9+8f
+eXq6bXKPAgMBAAGjggT3MIIE8zAfBgNVHSMEGDAWgBQ901Cl1qCt7vNKYApl0yHU
++PjWDzAdBgNVHQ4EFgQUTx0XO7HqD5DOhwlm2p+70uYPBmgwggGjBgNVHREEggGa
+MIIBloIMZGlnaWNlcnQuY29tggl0aGF3dGUuZGWCC2ZyZWVzc2wuY29tggxyYXBp
+ZHNzbC5jb22CDGdlb3RydXN0LmNvbYIJdGhhd3RlLmZyggp0aGF3dGUuY29tghB3
+d3cucmFwaWRzc2wuY29tghB3d3cuZ2VvdHJ1c3QuY29tgg13d3cudGhhd3RlLmZy
+gg13d3cudGhhd3RlLmRlgg53d3cudGhhd3RlLmNvbYIQd3d3LmRpZ2ljZXJ0LmNv
+bYIYa2ItaW50ZXJuYWwuZGlnaWNlcnQuY29tghprbm93bGVkZ2ViYXNlLmRpZ2lj
+ZXJ0LmNvbYIWa25vd2xlZGdlLmRpZ2ljZXJ0LmNvbYIPa2guZGlnaWNlcnQuY29t
+ghlrbm93bGVkZ2VodWIuZGlnaWNlcnQuY29tghh3ZWJzZWN1cml0eS5kaWdpY2Vy
+dC5jb22CFGNvbnRlbnQuZGlnaWNlcnQuY29tgg93d3cuZnJlZXNzbC5jb22CHHd3
+dy53ZWJzZWN1cml0eS5kaWdpY2VydC5jb20wDgYDVR0PAQH/BAQDAgWgMB0GA1Ud
+JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjB1BgNVHR8EbjBsMDSgMqAwhi5odHRw
+Oi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1ldi1zZXJ2ZXItZzIuY3JsMDSgMqAw
+hi5odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1ldi1zZXJ2ZXItZzIuY3Js
+MEsGA1UdIAREMEIwNwYJYIZIAYb9bAIBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v
+d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwBwYFZ4EMAQEwgYgGCCsGAQUFBwEBBHwwejAk
+BggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFIGCCsGAQUFBzAC
+hkZodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyRXh0ZW5k
+ZWRWYWxpZGF0aW9uU2VydmVyQ0EuY3J0MAwGA1UdEwEB/wQCMAAwggF8BgorBgEE
+AdZ5AgQCBIIBbASCAWgBZgB1AKS5CZC0GFgUh7sTosxncAo8NZgE+RvfuON3zQ7I
+DdwQAAABbtLkOs4AAAQDAEYwRAIgQ7gh393PInhYfPOhg/lF9yZNRdvjBeufFoG8
+VnBuPNMCIBP8YGC83ig5ttw3ipSRjH0bKj4Ak5O4rynoql9Dy8x3AHYAVhQGmi/X
+wuzT9eG9RLI+x0Z2ubyZEVzA75SYVdaJ0N0AAAFu0uQ7VgAABAMARzBFAiEAhzE7
+1c48wn3s/30IB4WgxfpLburH0Ku8cchv8QeqcgACIBrWpUlDD18AOfkPCOcB2kWU
+vRXsdptVm3jPeU5TtDSoAHUAu9nfvB+KcbWTlCOXqpJ7RzhXlQqrUugakJZkNo4e
+0YUAAAFu0uQ60gAABAMARjBEAiBBpH5m7ntGKFTOFgSLcFXRDg66xJqerMy0gOHj
+4TIBYAIgfFABPNy6P61hjiOWwjq73lvoEdAyh18GeFHIp0BgsWEwDQYJKoZIhvcN
+AQELBQADggEBAInaSEqteyQA1zUKiXVqgffhHKZsUq9UnMows6X+UoFPoby9xqm6
+IaY/77zaFZYwXJlP/SvrlbgTLHAdir3y38uhAlfPX4iRuwggOpFFF5hqDckzCm91
+ocGnoG6sUY5mOqKu2vIcZkUQDe+K5gOxI6ME/4YwzWCIcTmBPQ6NQmqiFLPoQty1
+gdbGCcLQNFCuNq4n5OK2NmBjcbtyT4gglat7C4+KV8RkEubZ+MkXzyDkpEXjjzsK
+7iuNB0hRgyyhGzHrlZ/l0OLoT0Cb4I5PzzRSseFEyPKCC1WSF7aE9rFfUqhpqSAT
+7NV7SEijYyFFtuZfz9RGglcqnRlAfgTy+tU=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEtjCCA56gAwIBAgIQDHmpRLCMEZUgkmFf4msdgzANBgkqhkiG9w0BAQsFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowdTEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTE0MDIGA1UEAxMrRGlnaUNlcnQgU0hBMiBFeHRlbmRlZCBW
+YWxpZGF0aW9uIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBANdTpARR+JmmFkhLZyeqk0nQOe0MsLAAh/FnKIaFjI5j2ryxQDji0/XspQUY
+uD0+xZkXMuwYjPrxDKZkIYXLBxA0sFKIKx9om9KxjxKws9LniB8f7zh3VFNfgHk/
+LhqqqB5LKw2rt2O5Nbd9FLxZS99RStKh4gzikIKHaq7q12TWmFXo/a8aUGxUvBHy
+/Urynbt/DvTVvo4WiRJV2MBxNO723C3sxIclho3YIeSwTQyJ3DkmF93215SF2AQh
+cJ1vb/9cuhnhRctWVyh+HA1BV6q3uCe7seT6Ku8hI3UarS2bhjWMnHe1c63YlC3k
+8wyd7sFOYn4XwHGeLN7x+RAoGTMCAwEAAaOCAUkwggFFMBIGA1UdEwEB/wQIMAYB
+Af8CAQAwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF
+BQcDAjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp
+Z2ljZXJ0LmNvbTBLBgNVHR8ERDBCMECgPqA8hjpodHRwOi8vY3JsNC5kaWdpY2Vy
+dC5jb20vRGlnaUNlcnRIaWdoQXNzdXJhbmNlRVZSb290Q0EuY3JsMD0GA1UdIAQ2
+MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5j
+b20vQ1BTMB0GA1UdDgQWBBQ901Cl1qCt7vNKYApl0yHU+PjWDzAfBgNVHSMEGDAW
+gBSxPsNpA/i/RwHUmCYaCALvY2QrwzANBgkqhkiG9w0BAQsFAAOCAQEAnbbQkIbh
+hgLtxaDwNBx0wY12zIYKqPBKikLWP8ipTa18CK3mtlC4ohpNiAexKSHc59rGPCHg
+4xFJcKx6HQGkyhE6V6t9VypAdP3THYUYUN9XR3WhfVUgLkc3UHKMf4Ib0mKPLQNa
+2sPIoc4sUqIAY+tzunHISScjl2SFnjgOrWNoPLpSgVh5oywM395t6zHyuqB8bPEs
+1OG9d4Q3A84ytciagRpKkk47RpqF/oOi+Z6Mo8wNXrM9zwR4jxQUezKcxwCmXMS1
+oVWNWlZopCJwqjyBcdmdqEU79OX2olHdx3ti6G8MdOu42vi/hw15UJGQmxg7kVkn
+8TUoE6smftX3eg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
+RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
+PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
+xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
+Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
+hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
+EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
+FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
+nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
+eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
+hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
+Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
+vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
++OkuE6N36B9K
+-----END CERTIFICATE-----
diff --git a/spec/fixtures/clusters/intermediate_certificate.pem b/spec/fixtures/clusters/intermediate_certificate.pem
new file mode 100644
index 00000000000..8a81175b746
--- /dev/null
+++ b/spec/fixtures/clusters/intermediate_certificate.pem
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE-----
+MIIEtjCCA56gAwIBAgIQDHmpRLCMEZUgkmFf4msdgzANBgkqhkiG9w0BAQsFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowdTEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTE0MDIGA1UEAxMrRGlnaUNlcnQgU0hBMiBFeHRlbmRlZCBW
+YWxpZGF0aW9uIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBANdTpARR+JmmFkhLZyeqk0nQOe0MsLAAh/FnKIaFjI5j2ryxQDji0/XspQUY
+uD0+xZkXMuwYjPrxDKZkIYXLBxA0sFKIKx9om9KxjxKws9LniB8f7zh3VFNfgHk/
+LhqqqB5LKw2rt2O5Nbd9FLxZS99RStKh4gzikIKHaq7q12TWmFXo/a8aUGxUvBHy
+/Urynbt/DvTVvo4WiRJV2MBxNO723C3sxIclho3YIeSwTQyJ3DkmF93215SF2AQh
+cJ1vb/9cuhnhRctWVyh+HA1BV6q3uCe7seT6Ku8hI3UarS2bhjWMnHe1c63YlC3k
+8wyd7sFOYn4XwHGeLN7x+RAoGTMCAwEAAaOCAUkwggFFMBIGA1UdEwEB/wQIMAYB
+Af8CAQAwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF
+BQcDAjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp
+Z2ljZXJ0LmNvbTBLBgNVHR8ERDBCMECgPqA8hjpodHRwOi8vY3JsNC5kaWdpY2Vy
+dC5jb20vRGlnaUNlcnRIaWdoQXNzdXJhbmNlRVZSb290Q0EuY3JsMD0GA1UdIAQ2
+MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5j
+b20vQ1BTMB0GA1UdDgQWBBQ901Cl1qCt7vNKYApl0yHU+PjWDzAfBgNVHSMEGDAW
+gBSxPsNpA/i/RwHUmCYaCALvY2QrwzANBgkqhkiG9w0BAQsFAAOCAQEAnbbQkIbh
+hgLtxaDwNBx0wY12zIYKqPBKikLWP8ipTa18CK3mtlC4ohpNiAexKSHc59rGPCHg
+4xFJcKx6HQGkyhE6V6t9VypAdP3THYUYUN9XR3WhfVUgLkc3UHKMf4Ib0mKPLQNa
+2sPIoc4sUqIAY+tzunHISScjl2SFnjgOrWNoPLpSgVh5oywM395t6zHyuqB8bPEs
+1OG9d4Q3A84ytciagRpKkk47RpqF/oOi+Z6Mo8wNXrM9zwR4jxQUezKcxwCmXMS1
+oVWNWlZopCJwqjyBcdmdqEU79OX2olHdx3ti6G8MdOu42vi/hw15UJGQmxg7kVkn
+8TUoE6smftX3eg==
+-----END CERTIFICATE-----
diff --git a/spec/fixtures/clusters/root_certificate.pem b/spec/fixtures/clusters/root_certificate.pem
new file mode 100644
index 00000000000..40107bd837d
--- /dev/null
+++ b/spec/fixtures/clusters/root_certificate.pem
@@ -0,0 +1,49 @@
+-----BEGIN CERTIFICATE-----
+MIIItjCCB56gAwIBAgIQCu5Ga1hR41iahM0SWhyeNjANBgkqhkiG9w0BAQsFADB1
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk
+IFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTE5MTIwNDAwMDAwMFoXDTIxMTIwODEy
+MDAwMFowgb0xHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB
+BAGCNzwCAQMTAlVTMRUwEwYLKwYBBAGCNzwCAQITBFV0YWgxFTATBgNVBAUTDDUy
+OTk1MzctMDE0MjELMAkGA1UEBhMCVVMxDTALBgNVBAgTBFV0YWgxDTALBgNVBAcT
+BExlaGkxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMRUwEwYDVQQDEwxkaWdpY2Vy
+dC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAeRYb/RLbljGZ
+IB//DrEdyKYMQqqaJwBlrr3t2paAWNuDJizvVkTMIzdJesI1pA58Myenxp5Dp8GJ
+u/VhBf//v/HAZHUE4xwu104Fg6A1BwUEKgVKERf+7kTt17Lf9fcMIjMyL+FeyPXb
+DOFbH+ej/nYaneFLch2j2xWZg1+Thk0qBlGE8WWAK+fvbEuM0SOeH9RkYFCNGPRS
+KsLn0GvaCnnD4LfNDyMqYop0IpaqXoREEnkRv1MVSOw+hBj497wnnO+/GZegfzwU
+iS60h+PjlDfmdCP18qOS7tRd0qnfU3N3S+PYEd3R63LMcIfbgXNEEWBNKpiH9+8f
+eXq6bXKPAgMBAAGjggT3MIIE8zAfBgNVHSMEGDAWgBQ901Cl1qCt7vNKYApl0yHU
++PjWDzAdBgNVHQ4EFgQUTx0XO7HqD5DOhwlm2p+70uYPBmgwggGjBgNVHREEggGa
+MIIBloIMZGlnaWNlcnQuY29tggl0aGF3dGUuZGWCC2ZyZWVzc2wuY29tggxyYXBp
+ZHNzbC5jb22CDGdlb3RydXN0LmNvbYIJdGhhd3RlLmZyggp0aGF3dGUuY29tghB3
+d3cucmFwaWRzc2wuY29tghB3d3cuZ2VvdHJ1c3QuY29tgg13d3cudGhhd3RlLmZy
+gg13d3cudGhhd3RlLmRlgg53d3cudGhhd3RlLmNvbYIQd3d3LmRpZ2ljZXJ0LmNv
+bYIYa2ItaW50ZXJuYWwuZGlnaWNlcnQuY29tghprbm93bGVkZ2ViYXNlLmRpZ2lj
+ZXJ0LmNvbYIWa25vd2xlZGdlLmRpZ2ljZXJ0LmNvbYIPa2guZGlnaWNlcnQuY29t
+ghlrbm93bGVkZ2VodWIuZGlnaWNlcnQuY29tghh3ZWJzZWN1cml0eS5kaWdpY2Vy
+dC5jb22CFGNvbnRlbnQuZGlnaWNlcnQuY29tgg93d3cuZnJlZXNzbC5jb22CHHd3
+dy53ZWJzZWN1cml0eS5kaWdpY2VydC5jb20wDgYDVR0PAQH/BAQDAgWgMB0GA1Ud
+JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjB1BgNVHR8EbjBsMDSgMqAwhi5odHRw
+Oi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1ldi1zZXJ2ZXItZzIuY3JsMDSgMqAw
+hi5odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1ldi1zZXJ2ZXItZzIuY3Js
+MEsGA1UdIAREMEIwNwYJYIZIAYb9bAIBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v
+d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwBwYFZ4EMAQEwgYgGCCsGAQUFBwEBBHwwejAk
+BggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFIGCCsGAQUFBzAC
+hkZodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyRXh0ZW5k
+ZWRWYWxpZGF0aW9uU2VydmVyQ0EuY3J0MAwGA1UdEwEB/wQCMAAwggF8BgorBgEE
+AdZ5AgQCBIIBbASCAWgBZgB1AKS5CZC0GFgUh7sTosxncAo8NZgE+RvfuON3zQ7I
+DdwQAAABbtLkOs4AAAQDAEYwRAIgQ7gh393PInhYfPOhg/lF9yZNRdvjBeufFoG8
+VnBuPNMCIBP8YGC83ig5ttw3ipSRjH0bKj4Ak5O4rynoql9Dy8x3AHYAVhQGmi/X
+wuzT9eG9RLI+x0Z2ubyZEVzA75SYVdaJ0N0AAAFu0uQ7VgAABAMARzBFAiEAhzE7
+1c48wn3s/30IB4WgxfpLburH0Ku8cchv8QeqcgACIBrWpUlDD18AOfkPCOcB2kWU
+vRXsdptVm3jPeU5TtDSoAHUAu9nfvB+KcbWTlCOXqpJ7RzhXlQqrUugakJZkNo4e
+0YUAAAFu0uQ60gAABAMARjBEAiBBpH5m7ntGKFTOFgSLcFXRDg66xJqerMy0gOHj
+4TIBYAIgfFABPNy6P61hjiOWwjq73lvoEdAyh18GeFHIp0BgsWEwDQYJKoZIhvcN
+AQELBQADggEBAInaSEqteyQA1zUKiXVqgffhHKZsUq9UnMows6X+UoFPoby9xqm6
+IaY/77zaFZYwXJlP/SvrlbgTLHAdir3y38uhAlfPX4iRuwggOpFFF5hqDckzCm91
+ocGnoG6sUY5mOqKu2vIcZkUQDe+K5gOxI6ME/4YwzWCIcTmBPQ6NQmqiFLPoQty1
+gdbGCcLQNFCuNq4n5OK2NmBjcbtyT4gglat7C4+KV8RkEubZ+MkXzyDkpEXjjzsK
+7iuNB0hRgyyhGzHrlZ/l0OLoT0Cb4I5PzzRSseFEyPKCC1WSF7aE9rFfUqhpqSAT
+7NV7SEijYyFFtuZfz9RGglcqnRlAfgTy+tU=
+-----END CERTIFICATE-----
diff --git a/spec/fixtures/emails/service_desk.eml b/spec/fixtures/emails/service_desk.eml
new file mode 100644
index 00000000000..0db1270bc64
--- /dev/null
+++ b/spec/fixtures/emails/service_desk.eml
@@ -0,0 +1,28 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+email-test-project_id-issue-@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: incoming+email-test-project_id-issue-@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: The message subject! @all
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+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
+
+Service desk stuff!
+
+```
+a = b
+```
+
+/label ~label1
+/assign @user1
+/close
diff --git a/spec/fixtures/emails/service_desk_custom_address.eml b/spec/fixtures/emails/service_desk_custom_address.eml
new file mode 100644
index 00000000000..3293dd48303
--- /dev/null
+++ b/spec/fixtures/emails/service_desk_custom_address.eml
@@ -0,0 +1,27 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <support+project_slug-project_key@example.com>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: support+project_slug-project_key@example.com
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: The message subject! @all
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+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
+
+Service desk stuff!
+
+```
+a = b
+```
+
+/label ~label1
+/assign @user1
+/close
diff --git a/spec/fixtures/emails/service_desk_forwarded.eml b/spec/fixtures/emails/service_desk_forwarded.eml
new file mode 100644
index 00000000000..56987972808
--- /dev/null
+++ b/spec/fixtures/emails/service_desk_forwarded.eml
@@ -0,0 +1,30 @@
+Delivered-To: incoming+email-test-project_id-issue-@appmail.adventuretime.ooo
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+email-test-project_id-issue-@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: support@adventuretime.ooo
+Delivered-To: support@adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: The message subject! @all
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+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
+
+Service desk stuff!
+
+```
+a = b
+```
+
+/label ~label1
+/assign @user1
+/close
diff --git a/spec/fixtures/emails/service_desk_forwarded_new_issue.eml b/spec/fixtures/emails/service_desk_forwarded_new_issue.eml
new file mode 100644
index 00000000000..4eedb24b32d
--- /dev/null
+++ b/spec/fixtures/emails/service_desk_forwarded_new_issue.eml
@@ -0,0 +1,29 @@
+Delivered-To: incoming+email-test-project_id-issue-@appmail.adventuretime.ooo
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+email-test-project_id-issue-@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: support@adventuretime.ooo
+Delivered-To: support@adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: The message subject! @all
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+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
+
+Service desk stuff!
+
+---------- Forwarded message ---------
+From: Jake the Dog <jake@adventuretime.ooo>
+To: <jake@adventuretime.ooo>
+
+
+forwarded content
diff --git a/spec/fixtures/emails/service_desk_legacy.eml b/spec/fixtures/emails/service_desk_legacy.eml
new file mode 100644
index 00000000000..fc9178c937b
--- /dev/null
+++ b/spec/fixtures/emails/service_desk_legacy.eml
@@ -0,0 +1,28 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: incoming+email/test@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: The message subject! @all
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+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
+
+Service desk stuff!
+
+```
+a = b
+```
+
+/label ~label1
+/assign @user1
+/close
diff --git a/spec/fixtures/emails/service_desk_sender_and_from.eml b/spec/fixtures/emails/service_desk_sender_and_from.eml
new file mode 100644
index 00000000000..987c24d70bf
--- /dev/null
+++ b/spec/fixtures/emails/service_desk_sender_and_from.eml
@@ -0,0 +1,27 @@
+Delivered-To: incoming+email-test-project_id-issue-@appmail.adventuretime.ooo
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+email-test-project_id-issue-@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Finn the Human <finn@adventuretime.ooo>
+Sender: Jake the Dog <jake@adventuretime.ooo>
+To: support@adventuretime.ooo
+Delivered-To: support@adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: The message subject! @all
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+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
+
+Service desk stuff!
+
+```
+a = b
+```
diff --git a/spec/fixtures/emails/valid_reply_with_quick_actions.eml b/spec/fixtures/emails/valid_reply_with_quick_actions.eml
new file mode 100644
index 00000000000..cd00b6eb8b8
--- /dev/null
+++ b/spec/fixtures/emails/valid_reply_with_quick_actions.eml
@@ -0,0 +1,45 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+In-Reply-To: <issue_1@localhost>
+References: <reply-59d8df8370b7e95c5a49fbf86aeb2c93@localhost> <issue_1@localhost>
+Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux'
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+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
+
+I could not disagree more. I am obviously biased but adventure time is the
+greatest show ever created. Everyone should watch it.
+
+- Jake out
+
+/close
+/title test
+
+
+On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta
+<reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo> wrote:
+>
+>
+>
+> eviltrout posted in 'Adventure Time Sux' on Discourse Meta:
+>
+> ---
+> hey guys everyone knows adventure time sucks!
+>
+> ---
+> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3
+>
+> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences).
+>
diff --git a/spec/fixtures/gitlab/import_export/corrupted_project_export.tar.gz b/spec/fixtures/gitlab/import_export/corrupted_project_export.tar.gz
index cac16cf9cd8..e99136e96b7 100644
--- a/spec/fixtures/gitlab/import_export/corrupted_project_export.tar.gz
+++ b/spec/fixtures/gitlab/import_export/corrupted_project_export.tar.gz
Binary files differ
diff --git a/spec/fixtures/gitlab/import_export/lightweight_project_export.tar.gz b/spec/fixtures/gitlab/import_export/lightweight_project_export.tar.gz
index c01402954dd..e3ec4f603b9 100644
--- a/spec/fixtures/gitlab/import_export/lightweight_project_export.tar.gz
+++ b/spec/fixtures/gitlab/import_export/lightweight_project_export.tar.gz
Binary files differ
diff --git a/spec/fixtures/helm/helm_list_v2_cilium_deployed.json.gz b/spec/fixtures/helm/helm_list_v2_cilium_deployed.json.gz
new file mode 100644
index 00000000000..a343356c95c
--- /dev/null
+++ b/spec/fixtures/helm/helm_list_v2_cilium_deployed.json.gz
Binary files differ
diff --git a/spec/fixtures/helm/helm_list_v2_cilium_failed.json.gz b/spec/fixtures/helm/helm_list_v2_cilium_failed.json.gz
new file mode 100644
index 00000000000..f7faff2ca19
--- /dev/null
+++ b/spec/fixtures/helm/helm_list_v2_cilium_failed.json.gz
Binary files differ
diff --git a/spec/fixtures/helm/helm_list_v2_cilium_missing.json.gz b/spec/fixtures/helm/helm_list_v2_cilium_missing.json.gz
new file mode 100644
index 00000000000..20cac36287b
--- /dev/null
+++ b/spec/fixtures/helm/helm_list_v2_cilium_missing.json.gz
Binary files differ
diff --git a/spec/fixtures/lib/gitlab/import_export/complex/project.json b/spec/fixtures/lib/gitlab/import_export/complex/project.json
index 0785da9c1bf..d88b2ebc83a 100644
--- a/spec/fixtures/lib/gitlab/import_export/complex/project.json
+++ b/spec/fixtures/lib/gitlab/import_export/complex/project.json
@@ -7007,395 +7007,6 @@
"enabled": false
},
"deploy_keys": [],
- "services": [
- {
- "id": 101,
- "title": "YouTrack",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.327Z",
- "updated_at": "2016-06-14T15:01:51.327Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "YoutrackService",
- "category": "issue_tracker",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 100,
- "title": "JetBrains TeamCity CI",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.315Z",
- "updated_at": "2016-06-14T15:01:51.315Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "TeamcityService",
- "category": "ci",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 99,
- "title": "Slack",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.303Z",
- "updated_at": "2016-06-14T15:01:51.303Z",
- "active": false,
- "properties": {
- "notify_only_broken_pipelines": true
- },
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "pipeline_events": true,
- "type": "SlackService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 98,
- "title": "Redmine",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.289Z",
- "updated_at": "2016-06-14T15:01:51.289Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "RedmineService",
- "category": "issue_tracker",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 97,
- "title": "Pushover",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.277Z",
- "updated_at": "2016-06-14T15:01:51.277Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "PushoverService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 96,
- "title": "PivotalTracker",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.267Z",
- "updated_at": "2016-06-14T15:01:51.267Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "PivotalTrackerService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 95,
- "title": "Jira",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.255Z",
- "updated_at": "2016-06-14T15:01:51.255Z",
- "active": false,
- "properties": {
- "api_url": "",
- "jira_issue_transition_id": "2"
- },
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "JiraService",
- "category": "issue_tracker",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 94,
- "title": "Irker (IRC gateway)",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.232Z",
- "updated_at": "2016-06-14T15:01:51.232Z",
- "active": true,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "IrkerService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 93,
- "title": "HipChat",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.219Z",
- "updated_at": "2016-06-14T15:01:51.219Z",
- "active": false,
- "properties": {
- "notify_only_broken_pipelines": true
- },
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "pipeline_events": true,
- "type": "HipchatService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 91,
- "title": "Flowdock",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.182Z",
- "updated_at": "2016-06-14T15:01:51.182Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "FlowdockService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 90,
- "title": "External Wiki",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.166Z",
- "updated_at": "2016-06-14T15:01:51.166Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "ExternalWikiService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 89,
- "title": "Emails on push",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.153Z",
- "updated_at": "2016-06-14T15:01:51.153Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "EmailsOnPushService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 88,
- "title": "Drone CI",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.139Z",
- "updated_at": "2016-06-14T15:01:51.139Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "DroneCiService",
- "category": "ci",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 87,
- "title": "Custom Issue Tracker",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.125Z",
- "updated_at": "2016-06-14T15:01:51.125Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "CustomIssueTrackerService",
- "category": "issue_tracker",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 86,
- "title": "Campfire",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.113Z",
- "updated_at": "2016-06-14T15:01:51.113Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "CampfireService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 84,
- "title": "Buildkite",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.080Z",
- "updated_at": "2016-06-14T15:01:51.080Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "BuildkiteService",
- "category": "ci",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 83,
- "title": "Atlassian Bamboo CI",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.067Z",
- "updated_at": "2016-06-14T15:01:51.067Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "BambooService",
- "category": "ci",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 82,
- "title": "Assembla",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.047Z",
- "updated_at": "2016-06-14T15:01:51.047Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "AssemblaService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 81,
- "title": "Asana",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.031Z",
- "updated_at": "2016-06-14T15:01:51.031Z",
- "active": false,
- "properties": {},
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "AsanaService",
- "category": "common",
- "default": false,
- "wiki_page_events": true
- }
- ],
"hooks": [],
"protected_branches": [
{
diff --git a/spec/fixtures/lib/gitlab/import_export/complex/tree/project/services.ndjson b/spec/fixtures/lib/gitlab/import_export/complex/tree/project/services.ndjson
index 6d6afd3af0b..e5d39512255 100644
--- a/spec/fixtures/lib/gitlab/import_export/complex/tree/project/services.ndjson
+++ b/spec/fixtures/lib/gitlab/import_export/complex/tree/project/services.ndjson
@@ -1,19 +1,19 @@
-{"id":101,"title":"YouTrack","project_id":5,"created_at":"2016-06-14T15:01:51.327Z","updated_at":"2016-06-14T15:01:51.327Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"YoutrackService","category":"issue_tracker","default":false,"wiki_page_events":true}
-{"id":100,"title":"JetBrains TeamCity CI","project_id":5,"created_at":"2016-06-14T15:01:51.315Z","updated_at":"2016-06-14T15:01:51.315Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"TeamcityService","category":"ci","default":false,"wiki_page_events":true}
-{"id":99,"title":"Slack","project_id":5,"created_at":"2016-06-14T15:01:51.303Z","updated_at":"2016-06-14T15:01:51.303Z","active":false,"properties":{"notify_only_broken_pipelines":true},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"pipeline_events":true,"type":"SlackService","category":"common","default":false,"wiki_page_events":true}
-{"id":98,"title":"Redmine","project_id":5,"created_at":"2016-06-14T15:01:51.289Z","updated_at":"2016-06-14T15:01:51.289Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"RedmineService","category":"issue_tracker","default":false,"wiki_page_events":true}
-{"id":97,"title":"Pushover","project_id":5,"created_at":"2016-06-14T15:01:51.277Z","updated_at":"2016-06-14T15:01:51.277Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"PushoverService","category":"common","default":false,"wiki_page_events":true}
-{"id":96,"title":"PivotalTracker","project_id":5,"created_at":"2016-06-14T15:01:51.267Z","updated_at":"2016-06-14T15:01:51.267Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"PivotalTrackerService","category":"common","default":false,"wiki_page_events":true}
-{"id":95,"title":"Jira","project_id":5,"created_at":"2016-06-14T15:01:51.255Z","updated_at":"2016-06-14T15:01:51.255Z","active":false,"properties":{"api_url":"","jira_issue_transition_id":"2"},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"JiraService","category":"issue_tracker","default":false,"wiki_page_events":true}
-{"id":94,"title":"Irker (IRC gateway)","project_id":5,"created_at":"2016-06-14T15:01:51.232Z","updated_at":"2016-06-14T15:01:51.232Z","active":true,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"IrkerService","category":"common","default":false,"wiki_page_events":true}
-{"id":93,"title":"HipChat","project_id":5,"created_at":"2016-06-14T15:01:51.219Z","updated_at":"2016-06-14T15:01:51.219Z","active":false,"properties":{"notify_only_broken_pipelines":true},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"pipeline_events":true,"type":"HipchatService","category":"common","default":false,"wiki_page_events":true}
-{"id":91,"title":"Flowdock","project_id":5,"created_at":"2016-06-14T15:01:51.182Z","updated_at":"2016-06-14T15:01:51.182Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"FlowdockService","category":"common","default":false,"wiki_page_events":true}
-{"id":90,"title":"External Wiki","project_id":5,"created_at":"2016-06-14T15:01:51.166Z","updated_at":"2016-06-14T15:01:51.166Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"ExternalWikiService","category":"common","default":false,"wiki_page_events":true}
-{"id":89,"title":"Emails on push","project_id":5,"created_at":"2016-06-14T15:01:51.153Z","updated_at":"2016-06-14T15:01:51.153Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"EmailsOnPushService","category":"common","default":false,"wiki_page_events":true}
-{"id":88,"title":"Drone CI","project_id":5,"created_at":"2016-06-14T15:01:51.139Z","updated_at":"2016-06-14T15:01:51.139Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"DroneCiService","category":"ci","default":false,"wiki_page_events":true}
-{"id":87,"title":"Custom Issue Tracker","project_id":5,"created_at":"2016-06-14T15:01:51.125Z","updated_at":"2016-06-14T15:01:51.125Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"CustomIssueTrackerService","category":"issue_tracker","default":false,"wiki_page_events":true}
-{"id":86,"title":"Campfire","project_id":5,"created_at":"2016-06-14T15:01:51.113Z","updated_at":"2016-06-14T15:01:51.113Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"CampfireService","category":"common","default":false,"wiki_page_events":true}
-{"id":84,"title":"Buildkite","project_id":5,"created_at":"2016-06-14T15:01:51.080Z","updated_at":"2016-06-14T15:01:51.080Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"BuildkiteService","category":"ci","default":false,"wiki_page_events":true}
-{"id":83,"title":"Atlassian Bamboo CI","project_id":5,"created_at":"2016-06-14T15:01:51.067Z","updated_at":"2016-06-14T15:01:51.067Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"BambooService","category":"ci","default":false,"wiki_page_events":true}
-{"id":82,"title":"Assembla","project_id":5,"created_at":"2016-06-14T15:01:51.047Z","updated_at":"2016-06-14T15:01:51.047Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"AssemblaService","category":"common","default":false,"wiki_page_events":true}
-{"id":81,"title":"Asana","project_id":5,"created_at":"2016-06-14T15:01:51.031Z","updated_at":"2016-06-14T15:01:51.031Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"AsanaService","category":"common","default":false,"wiki_page_events":true}
+{"id":101,"project_id":5,"created_at":"2016-06-14T15:01:51.327Z","updated_at":"2016-06-14T15:01:51.327Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"YoutrackService","category":"issue_tracker","default":false,"wiki_page_events":true}
+{"id":100,"project_id":5,"created_at":"2016-06-14T15:01:51.315Z","updated_at":"2016-06-14T15:01:51.315Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"TeamcityService","category":"ci","default":false,"wiki_page_events":true}
+{"id":99,"project_id":5,"created_at":"2016-06-14T15:01:51.303Z","updated_at":"2016-06-14T15:01:51.303Z","active":false,"properties":{"notify_only_broken_pipelines":true},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"pipeline_events":true,"type":"SlackService","category":"common","default":false,"wiki_page_events":true}
+{"id":98,"project_id":5,"created_at":"2016-06-14T15:01:51.289Z","updated_at":"2016-06-14T15:01:51.289Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"RedmineService","category":"issue_tracker","default":false,"wiki_page_events":true}
+{"id":97,"project_id":5,"created_at":"2016-06-14T15:01:51.277Z","updated_at":"2016-06-14T15:01:51.277Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"PushoverService","category":"common","default":false,"wiki_page_events":true}
+{"id":96,"project_id":5,"created_at":"2016-06-14T15:01:51.267Z","updated_at":"2016-06-14T15:01:51.267Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"PivotalTrackerService","category":"common","default":false,"wiki_page_events":true}
+{"id":95,"project_id":5,"created_at":"2016-06-14T15:01:51.255Z","updated_at":"2016-06-14T15:01:51.255Z","active":false,"properties":{"api_url":"","jira_issue_transition_id":"2"},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"JiraService","category":"issue_tracker","default":false,"wiki_page_events":true}
+{"id":94,"project_id":5,"created_at":"2016-06-14T15:01:51.232Z","updated_at":"2016-06-14T15:01:51.232Z","active":true,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"IrkerService","category":"common","default":false,"wiki_page_events":true}
+{"id":93,"project_id":5,"created_at":"2016-06-14T15:01:51.219Z","updated_at":"2016-06-14T15:01:51.219Z","active":false,"properties":{"notify_only_broken_pipelines":true},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"pipeline_events":true,"type":"HipchatService","category":"common","default":false,"wiki_page_events":true}
+{"id":91,"project_id":5,"created_at":"2016-06-14T15:01:51.182Z","updated_at":"2016-06-14T15:01:51.182Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"FlowdockService","category":"common","default":false,"wiki_page_events":true}
+{"id":90,"project_id":5,"created_at":"2016-06-14T15:01:51.166Z","updated_at":"2016-06-14T15:01:51.166Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"ExternalWikiService","category":"common","default":false,"wiki_page_events":true}
+{"id":89,"project_id":5,"created_at":"2016-06-14T15:01:51.153Z","updated_at":"2016-06-14T15:01:51.153Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"EmailsOnPushService","category":"common","default":false,"wiki_page_events":true}
+{"id":88,"project_id":5,"created_at":"2016-06-14T15:01:51.139Z","updated_at":"2016-06-14T15:01:51.139Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"DroneCiService","category":"ci","default":false,"wiki_page_events":true}
+{"id":87,"project_id":5,"created_at":"2016-06-14T15:01:51.125Z","updated_at":"2016-06-14T15:01:51.125Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"CustomIssueTrackerService","category":"issue_tracker","default":false,"wiki_page_events":true}
+{"id":86,"project_id":5,"created_at":"2016-06-14T15:01:51.113Z","updated_at":"2016-06-14T15:01:51.113Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"CampfireService","category":"common","default":false,"wiki_page_events":true}
+{"id":84,"project_id":5,"created_at":"2016-06-14T15:01:51.080Z","updated_at":"2016-06-14T15:01:51.080Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"BuildkiteService","category":"ci","default":false,"wiki_page_events":true}
+{"id":83,"project_id":5,"created_at":"2016-06-14T15:01:51.067Z","updated_at":"2016-06-14T15:01:51.067Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"BambooService","category":"ci","default":false,"wiki_page_events":true}
+{"id":82,"project_id":5,"created_at":"2016-06-14T15:01:51.047Z","updated_at":"2016-06-14T15:01:51.047Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"AssemblaService","category":"common","default":false,"wiki_page_events":true}
+{"id":81,"project_id":5,"created_at":"2016-06-14T15:01:51.031Z","updated_at":"2016-06-14T15:01:51.031Z","active":false,"properties":{},"template":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"AsanaService","category":"common","default":false,"wiki_page_events":true}
diff --git a/spec/fixtures/lib/gitlab/import_export/designs/project.json b/spec/fixtures/lib/gitlab/import_export/designs/project.json
index 28eaa38d387..ebc08868d9e 100644
--- a/spec/fixtures/lib/gitlab/import_export/designs/project.json
+++ b/spec/fixtures/lib/gitlab/import_export/designs/project.json
@@ -456,9 +456,6 @@
"pipeline_schedules":[
],
- "services":[
-
- ],
"protected_branches":[
],
diff --git a/spec/fixtures/lib/gitlab/import_export/light/project.json b/spec/fixtures/lib/gitlab/import_export/light/project.json
index 326a2cef9ff..963cdb342b5 100644
--- a/spec/fixtures/lib/gitlab/import_export/light/project.json
+++ b/spec/fixtures/lib/gitlab/import_export/light/project.json
@@ -141,50 +141,6 @@
]
}
],
- "services": [
- {
- "id": 100,
- "title": "JetBrains TeamCity CI",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.315Z",
- "updated_at": "2016-06-14T15:01:51.315Z",
- "active": false,
- "properties": {},
- "template": true,
- "instance": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "TeamcityService",
- "category": "ci",
- "default": false,
- "wiki_page_events": true
- },
- {
- "id": 101,
- "title": "Jira",
- "project_id": 5,
- "created_at": "2016-06-14T15:01:51.315Z",
- "updated_at": "2016-06-14T15:01:51.315Z",
- "active": false,
- "properties": {},
- "template": false,
- "instance": true,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "job_events": true,
- "type": "JiraService",
- "category": "ci",
- "default": false,
- "wiki_page_events": true
- }
- ],
"snippets": [],
"hooks": [],
"custom_attributes": [
diff --git a/spec/fixtures/lib/gitlab/import_export/light/tree/project/services.ndjson b/spec/fixtures/lib/gitlab/import_export/light/tree/project/services.ndjson
index c5ae6bf4b04..414b68dacd7 100644
--- a/spec/fixtures/lib/gitlab/import_export/light/tree/project/services.ndjson
+++ b/spec/fixtures/lib/gitlab/import_export/light/tree/project/services.ndjson
@@ -1,2 +1,2 @@
-{"id":100,"title":"JetBrains TeamCity CI","project_id":5,"created_at":"2016-06-14T15:01:51.315Z","updated_at":"2016-06-14T15:01:51.315Z","active":false,"properties":{},"template":true,"instance":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"TeamcityService","category":"ci","default":false,"wiki_page_events":true}
-{"id":101,"title":"Jira","project_id":5,"created_at":"2016-06-14T15:01:51.315Z","updated_at":"2016-06-14T15:01:51.315Z","active":false,"properties":{},"template":false,"instance":true,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"JiraService","category":"ci","default":false,"wiki_page_events":true}
+{"id":100,"project_id":5,"created_at":"2016-06-14T15:01:51.315Z","updated_at":"2016-06-14T15:01:51.315Z","active":false,"properties":{},"template":true,"instance":false,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"TeamcityService","category":"ci","default":false,"wiki_page_events":true}
+{"id":101,"project_id":5,"created_at":"2016-06-14T15:01:51.315Z","updated_at":"2016-06-14T15:01:51.315Z","active":false,"properties":{},"template":false,"instance":true,"push_events":true,"issues_events":true,"merge_requests_events":true,"tag_push_events":true,"note_events":true,"job_events":true,"type":"JiraService","category":"ci","default":false,"wiki_page_events":true}
diff --git a/spec/fixtures/lib/gitlab/import_export/with_invalid_records/project.json b/spec/fixtures/lib/gitlab/import_export/with_invalid_records/project.json
index a6e6ba43bdc..b9e791ee85a 100644
--- a/spec/fixtures/lib/gitlab/import_export/with_invalid_records/project.json
+++ b/spec/fixtures/lib/gitlab/import_export/with_invalid_records/project.json
@@ -32,7 +32,6 @@
],
"labels": [],
"issues": [],
- "services": [],
"snippets": [],
"hooks": []
}
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml b/spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml
index 5177de66fcf..1e41ef669d1 100644
--- a/spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml
@@ -13,6 +13,23 @@ templating:
options:
default_value: 'default'
text_variable_simple_syntax: 'default value'
+ custom_variable_simple_syntax: ['value1', 'value2', 'value3']
+ custom_variable_full_syntax:
+ label: 'Variable 2'
+ type: custom
+ options:
+ values:
+ - value: 'value option 1'
+ text: 'Option 1'
+ - value: 'value_option_2'
+ text: 'Option 2'
+ default: true
+ metric_label_values_variable:
+ label: 'Variable 3'
+ type: metric_label_values
+ options:
+ series_selector: 'backend:haproxy_backend_availability:ratio{env="{{env}}"}'
+ label: 'backend'
panel_groups:
- group: Group A
priority: 1
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_full_syntax.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_full_syntax.json
new file mode 100644
index 00000000000..e251e59de29
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_full_syntax.json
@@ -0,0 +1,12 @@
+{
+ "type": "object",
+ "required": [
+ "type", "options"
+ ],
+ "properties": {
+ "type": { "enum": ["custom"] },
+ "label": { "type": "string" },
+ "options": { "$ref": "custom_variable_options.json" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_options.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_options.json
new file mode 100644
index 00000000000..f351d3ba340
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_options.json
@@ -0,0 +1,11 @@
+{
+ "type": "object",
+ "required": ["values"],
+ "properties": {
+ "values": {
+ "type": "array",
+ "items": { "$ref": "custom_variable_values.json" }
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_values.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_values.json
new file mode 100644
index 00000000000..430d66a9691
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/custom_variable_values.json
@@ -0,0 +1,10 @@
+{
+ "type": "object",
+ "required": ["value"],
+ "properties": {
+ "value": { "type": "string" },
+ "text": { "type": "string" },
+ "default": { "type": "boolean" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metric_label_values_variable_full_syntax.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metric_label_values_variable_full_syntax.json
new file mode 100644
index 00000000000..6eb2c0e51e2
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metric_label_values_variable_full_syntax.json
@@ -0,0 +1,12 @@
+{
+ "type": "object",
+ "required": [
+ "type", "options"
+ ],
+ "properties": {
+ "type": { "enum": "metric_label_values" },
+ "label": { "type": "string" },
+ "options": { "$ref": "metric_label_values_variable_options.json" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metric_label_values_variable_options.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metric_label_values_variable_options.json
new file mode 100644
index 00000000000..304372ed876
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metric_label_values_variable_options.json
@@ -0,0 +1,12 @@
+{
+ "type": "object",
+ "required": [
+ "series_selector", "label", "prometheus_endpoint_path"
+ ],
+ "properties": {
+ "series_selector": { "type": "string" },
+ "label": { "type": "string" },
+ "prometheus_endpoint_path": { "type": "string" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/text_variable_full_syntax.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/text_variable_full_syntax.json
index f5090dc8c88..1818b2775f0 100644
--- a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/text_variable_full_syntax.json
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/text_variable_full_syntax.json
@@ -4,7 +4,7 @@
"type", "options"
],
"properties": {
- "type": { "type": "string" },
+ "type": { "enum": ["text"] },
"label": { "type": "string" },
"options": { "$ref": "text_variable_options.json" }
},
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/variables.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/variables.json
index f3e0dd555a6..aec129111e0 100644
--- a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/variables.json
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/variables.json
@@ -4,7 +4,13 @@
"^[a-zA-Z0-9_]*$": {
"anyOf": [
{ "$ref": "text_variable_full_syntax.json" },
- { "type": "string" }
+ { "type": "string" },
+ {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ { "$ref": "custom_variable_full_syntax.json" },
+ { "$ref": "metric_label_values_variable_full_syntax.json" }
]
}
},
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/blockquote.json b/spec/fixtures/lib/kramdown/atlassian_document_format/blockquote.json
new file mode 100644
index 00000000000..5ad414b43b9
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/blockquote.json
@@ -0,0 +1,24 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "blockquote",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is a sample quote"
+ },
+ {
+ "type": "text",
+ "text": " with lorem ipsum dolor sit amet..."
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/blockquote.md b/spec/fixtures/lib/kramdown/atlassian_document_format/blockquote.md
new file mode 100644
index 00000000000..9564e11fc3b
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/blockquote.md
@@ -0,0 +1,2 @@
+> This is a sample quote with lorem ipsum dolor sit amet...
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/bullet_list.json b/spec/fixtures/lib/kramdown/atlassian_document_format/bullet_list.json
new file mode 100644
index 00000000000..0e2b34b0790
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/bullet_list.json
@@ -0,0 +1,72 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "bulletList",
+ "content": [
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Item 1"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Item 2",
+ "marks": [
+ {
+ "type": "strong"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "bulletList",
+ "content": [
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Sub-item 1"
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Sub-item 1 paragraph"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/bullet_list.md b/spec/fixtures/lib/kramdown/atlassian_document_format/bullet_list.md
new file mode 100644
index 00000000000..b7de76b973f
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/bullet_list.md
@@ -0,0 +1,8 @@
+* Item 1
+
+* **Item 2**
+
+ * Sub-item 1
+
+ Sub-item 1 paragraph
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/code_block.json b/spec/fixtures/lib/kramdown/atlassian_document_format/code_block.json
new file mode 100644
index 00000000000..007493bdf49
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/code_block.json
@@ -0,0 +1,89 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "codeBlock",
+ "attrs": {
+ "language": "javascript"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "export function makeIssue({ parentIssue, project, users }) {\n\n const issueType = pickRandom(project.issueTypes)\n\n console.log(data)\n\n return data\n}"
+ }
+ ]
+ },
+ {
+ "type": "codeBlock",
+ "attrs": {
+ "language": "css"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": ".overflow { overflow: hidden; }"
+ }
+ ]
+ },
+ {
+ "type": "bulletList",
+ "content": [
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Item 1"
+ }
+ ]
+ },
+ {
+ "type": "codeBlock",
+ "content": [
+ {
+ "type": "text",
+ "text": "public DemoClass()\n{\n // assign default value\n x = 0;\n}"
+ }
+ ]
+ },
+ {
+ "type": "orderedList",
+ "content": [
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list Item 1"
+ }
+ ]
+ },
+ {
+ "type": "codeBlock",
+ "attrs": {
+ "language": "ruby"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "def test\n # assign default value\n x = 0\nend"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/code_block.md b/spec/fixtures/lib/kramdown/atlassian_document_format/code_block.md
new file mode 100644
index 00000000000..c89710afd36
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/code_block.md
@@ -0,0 +1,38 @@
+```javascript
+export function makeIssue({ parentIssue, project, users }) {
+
+ const issueType = pickRandom(project.issueTypes)
+
+ console.log(data)
+
+ return data
+}
+```
+
+<!-- -->
+
+```css
+.overflow { overflow: hidden; }
+```
+
+
+* Item 1
+
+ ```java
+ public DemoClass()
+ {
+ // assign default value
+ x = 0;
+ }
+ ```
+
+
+ 1. Number list Item 1
+
+ ```ruby
+ def test
+ # assign default value
+ x = 0
+ end
+ ```
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/code_mark.json b/spec/fixtures/lib/kramdown/atlassian_document_format/code_mark.json
new file mode 100644
index 00000000000..884715709f7
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/code_mark.json
@@ -0,0 +1,24 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is some "
+ },
+ {
+ "type": "text",
+ "text": "inline code",
+ "marks": [
+ {
+ "type": "code"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/code_mark.md b/spec/fixtures/lib/kramdown/atlassian_document_format/code_mark.md
new file mode 100644
index 00000000000..a52d00bfd38
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/code_mark.md
@@ -0,0 +1,2 @@
+This is some `inline code`
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/complex_document.json b/spec/fixtures/lib/kramdown/atlassian_document_format/complex_document.json
new file mode 100644
index 00000000000..c69ad6b4260
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/complex_document.json
@@ -0,0 +1,964 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is a complex issue…and this is normal text"
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "rule"
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Color - Dark Gray"
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Color - "
+ },
+ {
+ "type": "text",
+ "text": "Light Gray",
+ "marks": [
+ {
+ "type": "textColor",
+ "attrs": {
+ "color": "#97a0af"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Color - "
+ },
+ {
+ "type": "text",
+ "text": "Purple",
+ "marks": [
+ {
+ "type": "textColor",
+ "attrs": {
+ "color": "#6554c0"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Color - "
+ },
+ {
+ "type": "text",
+ "text": "Teal",
+ "marks": [
+ {
+ "type": "textColor",
+ "attrs": {
+ "color": "#00b8d9"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Color - "
+ },
+ {
+ "type": "text",
+ "text": "Green",
+ "marks": [
+ {
+ "type": "textColor",
+ "attrs": {
+ "color": "#36b37e"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Color - "
+ },
+ {
+ "type": "text",
+ "text": "Red",
+ "marks": [
+ {
+ "type": "textColor",
+ "attrs": {
+ "color": "#ff5630"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Color - "
+ },
+ {
+ "type": "text",
+ "text": "Orange",
+ "marks": [
+ {
+ "type": "textColor",
+ "attrs": {
+ "color": "#ff991f"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "rule"
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "inlineCard",
+ "attrs": {
+ "url": "https://gitlab-jira.atlassian.net/browse/DEMO-1"
+ }
+ },
+ {
+ "type": "text",
+ "text": " "
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "inlineCard",
+ "attrs": {
+ "data": {
+ "@context": "https://json-ld.org/contexts/person.jsonld",
+ "@id": "http://dbpedia.org/resource/John_Lennon",
+ "name": "John Lennon",
+ "born": "1940-10-09",
+ "spouse": "http://dbpedia.org/resource/Cynthia_Lennon"
+ }
+ }
+ },
+ {
+ "type": "text",
+ "text": " "
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "External Link",
+ "marks": [
+ {
+ "type": "link",
+ "attrs": {
+ "href": "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25718"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "rule"
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "blockquote",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is a block quote"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "panel",
+ "attrs": {
+ "panelType": "success"
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Success info panel"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "panel",
+ "attrs": {
+ "panelType": "info"
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Info info panel"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "panel",
+ "attrs": {
+ "panelType": "note"
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Note info panel"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "panel",
+ "attrs": {
+ "panelType": "warning"
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Warning info panel"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "panel",
+ "attrs": {
+ "panelType": "error"
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Error info panel"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "rule"
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "mention",
+ "attrs": {
+ "id": "5e32f803e127810e82875bc1",
+ "text": "jhope"
+ }
+ },
+ {
+ "type": "text",
+ "text": " what up"
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "emoji",
+ "attrs": {
+ "shortName": ":grinning:",
+ "id": "1f600",
+ "text": "\uD83D\uDE00"
+ }
+ },
+ {
+ "type": "text",
+ "text": " "
+ },
+ {
+ "type": "emoji",
+ "attrs": {
+ "shortName": ":rofl:",
+ "id": "1f923",
+ "text": "\uD83E\uDD23"
+ }
+ },
+ {
+ "type": "text",
+ "text": " "
+ },
+ {
+ "type": "emoji",
+ "attrs": {
+ "shortName": ":partying_face:",
+ "id": "1f973",
+ "text": "\uD83E\uDD73"
+ }
+ },
+ {
+ "type": "text",
+ "text": " "
+ },
+ {
+ "type": "emoji",
+ "attrs": {
+ "shortName": ":heart_eyes:",
+ "id": "1f60d",
+ "text": "\uD83D\uDE0D"
+ }
+ },
+ {
+ "type": "text",
+ "text": " "
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "table",
+ "attrs": {
+ "isNumberColumnEnabled": false,
+ "layout": "default"
+ },
+ "content": [
+ {
+ "type": "tableRow",
+ "content": [
+ {
+ "type": "tableHeader",
+ "attrs": {},
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Col 1 Row 1",
+ "marks": [
+ {
+ "type": "strong"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "tableHeader",
+ "attrs": {},
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Col 2 Row 1",
+ "marks": [
+ {
+ "type": "strong"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "tableHeader",
+ "attrs": {},
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Col 3 Row 1",
+ "marks": [
+ {
+ "type": "strong"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "tableRow",
+ "content": [
+ {
+ "type": "tableCell",
+ "attrs": {},
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Col 1 Row 2"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "tableCell",
+ "attrs": {},
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Col 2 Row 2"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "tableCell",
+ "attrs": {},
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Col 3 Row 2"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "tableRow",
+ "content": [
+ {
+ "type": "tableCell",
+ "attrs": {},
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Col 1 Row 3"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "tableCell",
+ "attrs": {},
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Col 2 Row 3"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "tableCell",
+ "attrs": {},
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Col 3 Row 3"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 1
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 1"
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 2
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 2"
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 3
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 3"
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 4
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 4"
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 5
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 5"
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 6
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 6"
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "bulletList",
+ "content": [
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Bullet point list item 1"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Bullet point list Item 2"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Bullet point list Item 3"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "orderedList",
+ "content": [
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list Item 1"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list item 2"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list item 3"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Underline",
+ "marks": [
+ {
+ "type": "underline"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Superscript",
+ "marks": [
+ {
+ "type": "subsup",
+ "attrs": {
+ "type": "sup"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Subscript",
+ "marks": [
+ {
+ "type": "subsup",
+ "attrs": {
+ "type": "sub"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Bold",
+ "marks": [
+ {
+ "type": "strong"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Italic",
+ "marks": [
+ {
+ "type": "em"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Strikethrough",
+ "marks": [
+ {
+ "type": "strike"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "codeBlock",
+ "attrs": {
+ "language": "javascript"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "export function makeIssue({ parentIssue, project, users }) {\n \n const issueType = pickRandom(project.issueTypes)\n\n let data = {\n fields: {\n summary: faker.lorem.sentence(),\n issuetype: {\n id: issueType.id\n },\n project: {\n id: project.id\n },\n reporter: {\n id: pickRandom(users)\n }\n }\n }\n\n if (issueType.subtask) {\n data = {\n parent: {\n key: parentIssue\n }\n }\n }\n\n console.log(data)\n\n return data\n}"
+ }
+ ]
+ },
+ {
+ "type": "mediaSingle",
+ "attrs": {
+ "layout": "center"
+ },
+ "content": [
+ {
+ "type": "media",
+ "attrs": {
+ "id": "79411c6b-50e0-477f-b4ed-ac3a5887750c",
+ "type": "file",
+ "collection": "jira-10050-field-description",
+ "width": 400,
+ "height": 400
+ }
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "mediaSingle",
+ "attrs": {
+ "layout": "center"
+ },
+ "content": [
+ {
+ "type": "media",
+ "attrs": {
+ "id": "6a5b48c6-70bd-4747-9ac8-a9abc9adb1f4",
+ "type": "file",
+ "collection": "jira-10050-field-description",
+ "width": 1280,
+ "height": 789
+ }
+ }
+ ]
+ },
+ {
+ "type": "mediaSingle",
+ "attrs": {
+ "layout": "center"
+ },
+ "content": [
+ {
+ "type": "media",
+ "attrs": {
+ "id": "e818a88d-9185-4a7f-8882-18339a0f0966",
+ "type": "file",
+ "collection": "jira-10050-field-description",
+ "width": 1280,
+ "height": 598
+ }
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "blob:"
+ },
+ {
+ "type": "text",
+ "text": "https://gitlab-jira.atlassian.net/5eb8e93b-7b15-446f-82d9-9d82ad7b8ea5#media-blob-url=true&id=572b2c1b-1b38-44ba-904a-649ee1861917&collection=upload-user-collection-426749591&contextId=10042&mimeType=image%2Fpng&name=import-jira-issues.png&size=294408",
+ "marks": [
+ {
+ "type": "link",
+ "attrs": {
+ "href": "https://gitlab-jira.atlassian.net/5eb8e93b-7b15-446f-82d9-9d82ad7b8ea5#media-blob-url=true&id=572b2c1b-1b38-44ba-904a-649ee1861917&collection=upload-user-collection-426749591&contextId=10042&mimeType=image%2Fpng&name=import-jira-issues.png&size=294408"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": []
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/complex_document.md b/spec/fixtures/lib/kramdown/atlassian_document_format/complex_document.md
new file mode 100644
index 00000000000..d4faf84e971
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/complex_document.md
@@ -0,0 +1,223 @@
+This is a complex issue…and this is normal text
+
+
+
+* * *
+
+Color - Dark Gray
+
+Color - <span color="#97a0af">Light Gray</span>
+
+Color - <span color="#6554c0">Purple</span>
+
+Color - <span color="#00b8d9">Teal</span>
+
+Color - <span color="#36b37e">Green</span>
+
+Color - <span color="#ff5630">Red</span>
+
+Color - <span color="#ff991f">Orange</span>
+
+
+
+* * *
+
+[https://gitlab-jira.atlassian.net/browse/DEMO-1][1]
+
+`adf-inlineCard:
+{"@context"=>"https://json-ld.org/contexts/person.jsonld",
+"@id"=>"http://dbpedia.org/resource/John_Lennon", "name"=>"John Lennon",
+"born"=>"1940-10-09",
+"spouse"=>"http://dbpedia.org/resource/Cynthia_Lennon"}`
+
+[External Link][2]
+
+
+
+* * *
+
+
+
+> This is a block quote
+
+
+
+> \:white\_check\_mark: Success info panel
+
+> \:information\_source: Info info panel
+
+> \:notepad\_spiral: Note info panel
+
+> \:warning: Warning info panel
+
+> \:octagonal\_sign: Error info panel
+
+
+
+
+
+* * *
+
+@adf-mention:jhope what up
+
+
+
+😀 🤣 🥳 😍
+
+
+
+<table>
+<tbody>
+<tr>
+<th>
+
+**Col 1 Row 1**
+
+</th>
+<th>
+
+**Col 2 Row 1**
+
+</th>
+<th>
+
+**Col 3 Row 1**
+
+</th>
+</tr>
+<tr>
+<td>
+
+Col 1 Row 2
+
+</td>
+<td>
+
+Col 2 Row 2
+
+</td>
+<td>
+
+Col 3 Row 2
+
+</td>
+</tr>
+<tr>
+<td>
+
+Col 1 Row 3
+
+</td>
+<td>
+
+Col 2 Row 3
+
+</td>
+<td>
+
+Col 3 Row 3
+
+</td>
+</tr>
+</tbody>
+</table>
+
+
+# Header 1
+
+## Header 2
+
+### Header 3
+
+#### Header 4
+
+##### Header 5
+
+###### Header 6
+
+
+
+* Bullet point list item 1
+
+* Bullet point list Item 2
+
+* Bullet point list Item 3
+
+
+
+1. Number list Item 1
+
+2. Number list item 2
+
+3. Number list item 3
+
+
+
+<u>Underline</u>
+
+<sup>Superscript</sup>
+
+<sub>Subscript</sub>
+
+**Bold**
+
+*Italic*
+
+<del>Strikethrough</del>
+
+```javascript
+export function makeIssue({ parentIssue, project, users }) {
+
+ const issueType = pickRandom(project.issueTypes)
+
+ let data = {
+ fields: {
+ summary: faker.lorem.sentence(),
+ issuetype: {
+ id: issueType.id
+ },
+ project: {
+ id: project.id
+ },
+ reporter: {
+ id: pickRandom(users)
+ }
+ }
+ }
+
+ if (issueType.subtask) {
+ data = {
+ parent: {
+ key: parentIssue
+ }
+ }
+ }
+
+ console.log(data)
+
+ return data
+}
+```
+
+
+![jira-10050-field-description](adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c)
+
+
+
+![jira-10050-field-description](adf-media://6a5b48c6-70bd-4747-9ac8-a9abc9adb1f4)
+
+![jira-10050-field-description](adf-media://e818a88d-9185-4a7f-8882-18339a0f0966)
+
+
+
+
+
+blob:[https://gitlab-jira.atlassian.net/5eb8e93b-7b15-446f-82d9-9d82ad7b8ea5#media-blob-url=true&id=572b2c1b-1b38-44ba-904a-649ee1861917&collection=upload-user-collection-426749591&contextId=10042&mimeType=image%2Fpng&name=import-jira-issues.png&size=294408][3]
+
+
+
+
+
+[1]: https://gitlab-jira.atlassian.net/browse/DEMO-1
+[2]: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25718
+[3]: https://gitlab-jira.atlassian.net/5eb8e93b-7b15-446f-82d9-9d82ad7b8ea5#media-blob-url=true&id=572b2c1b-1b38-44ba-904a-649ee1861917&collection=upload-user-collection-426749591&contextId=10042&mimeType=image%2Fpng&name=import-jira-issues.png&size=294408
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/emoji.json b/spec/fixtures/lib/kramdown/atlassian_document_format/emoji.json
new file mode 100644
index 00000000000..3a7944d2509
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/emoji.json
@@ -0,0 +1,66 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Grinning with unicode "
+ },
+ {
+ "type": "emoji",
+ "attrs": {
+ "shortName": ":grinning:",
+ "text": "😀"
+ }
+ },
+ {
+ "type": "text",
+ "text": " and heart eyes with raw unicode "
+ },
+ {
+ "type": "emoji",
+ "attrs": {
+ "shortName": ":heart_eyes:",
+ "id": "1f60d",
+ "text": "\uD83D\uDE0D"
+ }
+ },
+ {
+ "type": "text",
+ "text": ", non-standard Atlassian "
+ },
+ {
+ "type": "emoji",
+ "attrs": {
+ "shortName": ":awthanks:",
+ "id": "atlassian-awthanks",
+ "text": ":awthanks:"
+ }
+ },
+ {
+ "type": "text",
+ "text": ", non-standard customer emoji "
+ },
+ {
+ "type": "emoji",
+ "attrs": {
+ "shortName": ":thumbsup::skin-tone-2:"
+ }
+ },
+ {
+ "type": "text",
+ "text": ", and invalid is ignored "
+ },
+ {
+ "type": "emoji",
+ "attrs": {
+ "id": "1f44d"
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/emoji.md b/spec/fixtures/lib/kramdown/atlassian_document_format/emoji.md
new file mode 100644
index 00000000000..11adf377188
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/emoji.md
@@ -0,0 +1,4 @@
+Grinning with unicode 😀 and heart eyes with raw unicode 😍, non-standard
+Atlassian :awthanks:, non-standard customer emoji
+:thumbsup::skin-tone-2:, and invalid is ignored
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/hard_break.json b/spec/fixtures/lib/kramdown/atlassian_document_format/hard_break.json
new file mode 100644
index 00000000000..564570c016e
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/hard_break.json
@@ -0,0 +1,22 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is a sample paragraph"
+ },
+ {
+ "type": "hardBreak"
+ },
+ {
+ "type": "text",
+ "text": "with lorem ipsum dolor sit amet..."
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/hard_break.md b/spec/fixtures/lib/kramdown/atlassian_document_format/hard_break.md
new file mode 100644
index 00000000000..d8c4a8dca53
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/hard_break.md
@@ -0,0 +1,3 @@
+This is a sample paragraph
+with lorem ipsum dolor sit amet...
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/heading.json b/spec/fixtures/lib/kramdown/atlassian_document_format/heading.json
new file mode 100644
index 00000000000..dbc756f3603
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/heading.json
@@ -0,0 +1,91 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 1
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 1"
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 2
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 2",
+ "marks": [
+ {
+ "type": "strong"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 3
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 3"
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 4
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 4",
+ "marks": [
+ {
+ "type": "textColor",
+ "attrs": {
+ "color": "#ff6347"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 5
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 5"
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "level": 6
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Header 6"
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/heading.md b/spec/fixtures/lib/kramdown/atlassian_document_format/heading.md
new file mode 100644
index 00000000000..073ae1fdef4
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/heading.md
@@ -0,0 +1,12 @@
+# Header 1
+
+## **Header 2**
+
+### Header 3
+
+#### <span color="#ff6347">Header 4</span>
+
+##### Header 5
+
+###### Header 6
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/inline_card.json b/spec/fixtures/lib/kramdown/atlassian_document_format/inline_card.json
new file mode 100644
index 00000000000..8b7428debc2
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/inline_card.json
@@ -0,0 +1,46 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "An inline card (url): "
+ },
+ {
+ "type": "inlineCard",
+ "attrs": {
+ "url": "https://gitlab-jira.atlassian.net/browse/DEMO-1"
+ }
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Some prelude text "
+ },
+ {
+ "type": "inlineCard",
+ "attrs": {
+ "data": {
+ "@context": "https://json-ld.org/contexts/person.jsonld",
+ "@id": "http://dbpedia.org/resource/John_Lennon",
+ "name": "John Lennon",
+ "born": "1940-10-09",
+ "spouse": "http://dbpedia.org/resource/Cynthia_Lennon"
+ }
+ }
+ },
+ {
+ "type": "text",
+ "text": " some following text"
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/inline_card.md b/spec/fixtures/lib/kramdown/atlassian_document_format/inline_card.md
new file mode 100644
index 00000000000..655102941a7
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/inline_card.md
@@ -0,0 +1,13 @@
+An inline card (url):
+[https://gitlab-jira.atlassian.net/browse/DEMO-1][1]
+
+Some prelude text `adf-inlineCard:
+{"@context"=>"https://json-ld.org/contexts/person.jsonld",
+"@id"=>"http://dbpedia.org/resource/John_Lennon", "name"=>"John Lennon",
+"born"=>"1940-10-09",
+"spouse"=>"http://dbpedia.org/resource/Cynthia_Lennon"}` some following
+text
+
+
+
+[1]: https://gitlab-jira.atlassian.net/browse/DEMO-1
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/invalid_json.json b/spec/fixtures/lib/kramdown/atlassian_document_format/invalid_json.json
new file mode 100644
index 00000000000..6305592d175
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/invalid_json.json
@@ -0,0 +1,16 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is a second paragraph"
+ }
+ ]
+ }
+ ]
+}
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/invalid_no_doc.json b/spec/fixtures/lib/kramdown/atlassian_document_format/invalid_no_doc.json
new file mode 100644
index 00000000000..c9f10eeeb79
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/invalid_no_doc.json
@@ -0,0 +1,13 @@
+{
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is a sample paragraph"
+ },
+ {
+ "type": "text",
+ "text": " with lorem ipsum dolor sit amet..."
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/invalid_node_type.json b/spec/fixtures/lib/kramdown/atlassian_document_format/invalid_node_type.json
new file mode 100644
index 00000000000..ebb74624333
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/invalid_node_type.json
@@ -0,0 +1,28 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "invalid",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is a sample paragraph"
+ },
+ {
+ "type": "text",
+ "text": " with lorem ipsum dolor sit amet..."
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is a second paragraph"
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/link_mark.json b/spec/fixtures/lib/kramdown/atlassian_document_format/link_mark.json
new file mode 100644
index 00000000000..bf6facf1b05
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/link_mark.json
@@ -0,0 +1,44 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is a "
+ },
+ {
+ "type": "text",
+ "text": "link without title",
+ "marks": [
+ {
+ "type": "link",
+ "attrs": {
+ "href": "http://example.com"
+ }
+ }
+ ]
+ },
+ {
+ "type": "text",
+ "text": " and a "
+ },
+ {
+ "type": "text",
+ "text": "link with title",
+ "marks": [
+ {
+ "type": "link",
+ "attrs": {
+ "href": "http://example.net",
+ "title": "Link Title"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/link_mark.md b/spec/fixtures/lib/kramdown/atlassian_document_format/link_mark.md
new file mode 100644
index 00000000000..cd42b2297cf
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/link_mark.md
@@ -0,0 +1,6 @@
+This is a [link without title][1] and a [link with title][2]
+
+
+
+[1]: http://example.com
+[2]: http://example.net "Link Title"
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/media_group.json b/spec/fixtures/lib/kramdown/atlassian_document_format/media_group.json
new file mode 100644
index 00000000000..a51da964e0e
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/media_group.json
@@ -0,0 +1,59 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "mediaGroup",
+ "content": [
+ {
+ "type": "media",
+ "attrs": {
+ "id": "79411c6b-50e0-477f-b4ed-ac3a5887750c",
+ "type": "file",
+ "collection": "jira-10050-field-description",
+ "width": 400,
+ "height": 400
+ }
+ },
+ {
+ "type": "media",
+ "attrs": {
+ "id": "79411c6b-50e0-477f-b4ed-abcde",
+ "type": "file",
+ "collection": "jira-10050-field-description",
+ "width": 400,
+ "height": 400
+ }
+ }
+ ]
+ },
+ {
+ "type": "rule"
+ },
+ {
+ "type": "mediaGroup",
+ "content": [
+ {
+ "type": "media",
+ "attrs": {
+ "id": "79411c6b-50e0-477f-b4ed-ac3a5887750c-link1",
+ "type": "link",
+ "collection": "jira-10050-field-description-links",
+ "width": 400,
+ "height": 400
+ }
+ },
+ {
+ "type": "media",
+ "attrs": {
+ "id": "79411c6b-50e0-477f-b4ed-ac3a5887750c-link2",
+ "type": "link",
+ "collection": "jira-10050-field-description-links",
+ "width": 400,
+ "height": 400
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/media_group.md b/spec/fixtures/lib/kramdown/atlassian_document_format/media_group.md
new file mode 100644
index 00000000000..7b602d8fc2b
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/media_group.md
@@ -0,0 +1,10 @@
+* ![jira-10050-field-description](adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c)
+
+* ![jira-10050-field-description](adf-media://79411c6b-50e0-477f-b4ed-abcde)
+
+* * *
+
+* [adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c-link1](adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c-link1)
+
+* [adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c-link2](adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c-link2)
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/media_single.json b/spec/fixtures/lib/kramdown/atlassian_document_format/media_single.json
new file mode 100644
index 00000000000..0ecf2dcf03d
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/media_single.json
@@ -0,0 +1,42 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "mediaSingle",
+ "attrs": {
+ "layout": "center"
+ },
+ "content": [
+ {
+ "type": "media",
+ "attrs": {
+ "id": "79411c6b-50e0-477f-b4ed-ac3a5887750c",
+ "type": "file",
+ "collection": "jira-10050-field-description",
+ "width": 400,
+ "height": 400
+ }
+ }
+ ]
+ },
+ {
+ "type": "mediaSingle",
+ "attrs": {
+ "layout": "center"
+ },
+ "content": [
+ {
+ "type": "media",
+ "attrs": {
+ "id": "79411c6b-50e0-477f-b4ed-abcd",
+ "type": "file",
+ "collection": "another-jira-10050-field-description",
+ "width": 400,
+ "height": 400
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/media_single.md b/spec/fixtures/lib/kramdown/atlassian_document_format/media_single.md
new file mode 100644
index 00000000000..5ed1bbb55e1
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/media_single.md
@@ -0,0 +1,4 @@
+![jira-10050-field-description](adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c)
+
+![another-jira-10050-field-description](adf-media://79411c6b-50e0-477f-b4ed-abcd)
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/mention.json b/spec/fixtures/lib/kramdown/atlassian_document_format/mention.json
new file mode 100644
index 00000000000..81a087c57d9
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/mention.json
@@ -0,0 +1,44 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Mentioning "
+ },
+ {
+ "type": "mention",
+ "attrs": {
+ "id": "ABCDE-ABCDE-ABCDE-ABCDE",
+ "text": "@testuser",
+ "userType": "APP"
+ }
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Mentioning "
+ },
+ {
+ "type": "mention",
+ "attrs": {
+ "id": "ABCDE-ABCDE-ABCDE-ABCDE",
+ "text": "@test user",
+ "userType": "APP"
+ }
+ },
+ {
+ "type": "text",
+ "text": " with space in user name"
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/mention.md b/spec/fixtures/lib/kramdown/atlassian_document_format/mention.md
new file mode 100644
index 00000000000..f22c6421d8c
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/mention.md
@@ -0,0 +1,4 @@
+Mentioning @adf-mention:testuser
+
+Mentioning @adf-mention:\"test user\" with space in user name
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/ordered_list.json b/spec/fixtures/lib/kramdown/atlassian_document_format/ordered_list.json
new file mode 100644
index 00000000000..71d638c6a38
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/ordered_list.json
@@ -0,0 +1,170 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "orderedList",
+ "content": [
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list Item 1"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list item 2"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list item 3"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list item 4"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list item 5"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list item 6"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list item 7"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list item 8"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list item 9"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Number list item 10"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "orderedList",
+ "content": [
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Another list"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/ordered_list.md b/spec/fixtures/lib/kramdown/atlassian_document_format/ordered_list.md
new file mode 100644
index 00000000000..2c1f45cd810
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/ordered_list.md
@@ -0,0 +1,25 @@
+1. Number list Item 1
+
+2. Number list item 2
+
+3. Number list item 3
+
+4. Number list item 4
+
+5. Number list item 5
+
+6. Number list item 6
+
+7. Number list item 7
+
+8. Number list item 8
+
+9. Number list item 9
+
+10. Number list item 10
+<!-- -->
+
+1. Another list
+
+<!-- -->
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/panel.json b/spec/fixtures/lib/kramdown/atlassian_document_format/panel.json
new file mode 100644
index 00000000000..e3982d23a9e
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/panel.json
@@ -0,0 +1,117 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "panel",
+ "attrs": {
+ "panelType": "success"
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Success info panel"
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Second paragraph"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "panel",
+ "attrs": {
+ "panelType": "info"
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Info info panel"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "panel",
+ "attrs": {
+ "panelType": "note"
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Note info panel"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "panel",
+ "attrs": {
+ "panelType": "warning"
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Warning info panel"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "panel",
+ "attrs": {
+ "panelType": "error"
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Error info panel"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "panel",
+ "attrs": {
+ "panelType": "unknown"
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Unknown panel"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/panel.md b/spec/fixtures/lib/kramdown/atlassian_document_format/panel.md
new file mode 100644
index 00000000000..fe3a10e4878
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/panel.md
@@ -0,0 +1,12 @@
+> \:white\_check\_mark: Success info panel
+>
+> Second paragraph
+
+> \:information\_source: Info info panel
+
+> \:notepad\_spiral: Note info panel
+
+> \:warning: Warning info panel
+
+> \:octagonal\_sign: Error info panel
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/paragraph.json b/spec/fixtures/lib/kramdown/atlassian_document_format/paragraph.json
new file mode 100644
index 00000000000..e5b99dbd112
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/paragraph.json
@@ -0,0 +1,28 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is a sample paragraph"
+ },
+ {
+ "type": "text",
+ "text": " with lorem ipsum dolor sit amet..."
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is a second paragraph"
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/paragraph.md b/spec/fixtures/lib/kramdown/atlassian_document_format/paragraph.md
new file mode 100644
index 00000000000..661110e0267
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/paragraph.md
@@ -0,0 +1,4 @@
+This is a sample paragraph with lorem ipsum dolor sit amet...
+
+This is a second paragraph
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/rule.json b/spec/fixtures/lib/kramdown/atlassian_document_format/rule.json
new file mode 100644
index 00000000000..c2566a84713
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/rule.json
@@ -0,0 +1,9 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "rule"
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/rule.md b/spec/fixtures/lib/kramdown/atlassian_document_format/rule.md
new file mode 100644
index 00000000000..8ccef4e8dfb
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/rule.md
@@ -0,0 +1,2 @@
+* * *
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/strike_sup_sub_mark.json b/spec/fixtures/lib/kramdown/atlassian_document_format/strike_sup_sub_mark.json
new file mode 100644
index 00000000000..21d364d642f
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/strike_sup_sub_mark.json
@@ -0,0 +1,56 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is "
+ },
+ {
+ "type": "text",
+ "text": "stricken",
+ "marks": [
+ {
+ "type": "strike"
+ }
+ ]
+ },
+ {
+ "type": "text",
+ "text": " and "
+ },
+ {
+ "type": "text",
+ "text": "superscripted",
+ "marks": [
+ {
+ "type": "subsup",
+ "attrs": {
+ "type": "sup"
+ }
+ }
+ ]
+ },
+ {
+ "type": "text",
+ "text": " and "
+ },
+ {
+ "type": "text",
+ "text": "subscripted",
+ "marks": [
+ {
+ "type": "subsup",
+ "attrs": {
+ "type": "sub"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/strike_sup_sub_mark.md b/spec/fixtures/lib/kramdown/atlassian_document_format/strike_sup_sub_mark.md
new file mode 100644
index 00000000000..e4dda350907
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/strike_sup_sub_mark.md
@@ -0,0 +1,3 @@
+This is <del>stricken</del> and <sup>superscripted</sup> and
+<sub>subscripted</sub>
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/strong_em_mark.json b/spec/fixtures/lib/kramdown/atlassian_document_format/strong_em_mark.json
new file mode 100644
index 00000000000..7ab1a7c1461
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/strong_em_mark.json
@@ -0,0 +1,53 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Hello "
+ },
+ {
+ "type": "text",
+ "text": "strong",
+ "marks": [
+ {
+ "type": "strong"
+ }
+ ]
+ },
+ {
+ "type": "text",
+ "text": " and "
+ },
+ {
+ "type": "text",
+ "text": "emphasis",
+ "marks": [
+ {
+ "type": "em"
+ }
+ ]
+ },
+ {
+ "type": "text",
+ "text": " and "
+ },
+ {
+ "type": "text",
+ "text": "strongly emphasised",
+ "marks": [
+ {
+ "type": "em"
+ },
+ {
+ "type": "strong"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/strong_em_mark.md b/spec/fixtures/lib/kramdown/atlassian_document_format/strong_em_mark.md
new file mode 100644
index 00000000000..d0dcbb231ae
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/strong_em_mark.md
@@ -0,0 +1,2 @@
+Hello **strong** and *emphasis* and ***strongly emphasised***
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/table.json b/spec/fixtures/lib/kramdown/atlassian_document_format/table.json
new file mode 100644
index 00000000000..ad12c2875c0
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/table.json
@@ -0,0 +1,55 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "table",
+ "attrs": {
+ "isNumberColumnEnabled": false,
+ "layout": "default"
+ },
+ "content": [
+ {
+ "type": "tableRow",
+ "content": [
+ {
+ "type": "tableCell",
+ "attrs": {},
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Row one, cell one",
+ "marks": [
+ {
+ "type": "strong"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "tableCell",
+ "attrs": {},
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Row one, cell two"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/table.md b/spec/fixtures/lib/kramdown/atlassian_document_format/table.md
new file mode 100644
index 00000000000..4e0c53338df
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/table.md
@@ -0,0 +1,18 @@
+<table>
+<tbody>
+<tr>
+<td>
+
+**Row one, cell one**
+
+</td>
+<td>
+
+Row one, cell two
+
+</td>
+</tr>
+</tbody>
+</table>
+
+
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/underline_text_color_mark.json b/spec/fixtures/lib/kramdown/atlassian_document_format/underline_text_color_mark.json
new file mode 100644
index 00000000000..2690233d4f6
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/underline_text_color_mark.json
@@ -0,0 +1,40 @@
+{
+ "version": 1,
+ "type": "doc",
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "This is "
+ },
+ {
+ "type": "text",
+ "text": "underlined",
+ "marks": [
+ {
+ "type": "underline"
+ }
+ ]
+ },
+ {
+ "type": "text",
+ "text": " and "
+ },
+ {
+ "type": "text",
+ "text": "red",
+ "marks": [
+ {
+ "type": "textColor",
+ "attrs": {
+ "color": "#ff6347"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/lib/kramdown/atlassian_document_format/underline_text_color_mark.md b/spec/fixtures/lib/kramdown/atlassian_document_format/underline_text_color_mark.md
new file mode 100644
index 00000000000..75c9de3b451
--- /dev/null
+++ b/spec/fixtures/lib/kramdown/atlassian_document_format/underline_text_color_mark.md
@@ -0,0 +1,2 @@
+This is <u>underlined</u> and <span color="#ff6347">red</span>
+
diff --git a/spec/fixtures/pager_duty/webhook_incident_trigger.json b/spec/fixtures/pager_duty/webhook_incident_trigger.json
new file mode 100644
index 00000000000..872297adcf6
--- /dev/null
+++ b/spec/fixtures/pager_duty/webhook_incident_trigger.json
@@ -0,0 +1,239 @@
+{
+ "messages": [
+ {
+ "event": "incident.trigger",
+ "log_entries": [
+ {
+ "id": "R2XGXEI3W0FHMSDXHDIBQGBQ5E",
+ "type": "trigger_log_entry",
+ "summary": "Triggered through the website",
+ "self": "https://api.pagerduty.com/log_entries/R2XGXEI3W0FHMSDXHDIBQGBQ5E",
+ "html_url": "https://webdemo.pagerduty.com/incidents/PRORDTY/log_entries/R2XGXEI3W0FHMSDXHDIBQGBQ5E",
+ "created_at": "2017-09-26T15:14:36Z",
+ "agent": {
+ "id": "P553OPV",
+ "type": "user_reference",
+ "summary": "Laura Haley",
+ "self": "https://api.pagerduty.com/users/P553OPV",
+ "html_url": "https://webdemo.pagerduty.com/users/P553OPV"
+ },
+ "channel": {
+ "type": "web_trigger",
+ "summary": "My new incident",
+ "subject": "My new incident",
+ "details": "Oh my gosh",
+ "details_omitted": false
+ },
+ "service": {
+ "id": "PN49J75",
+ "type": "service_reference",
+ "summary": "Production XDB Cluster",
+ "self": "https://api.pagerduty.com/services/PN49J75",
+ "html_url": "https://webdemo.pagerduty.com/services/PN49J75"
+ },
+ "incident": {
+ "id": "PRORDTY",
+ "type": "incident_reference",
+ "summary": "[#33] My new incident",
+ "self": "https://api.pagerduty.com/incidents/PRORDTY",
+ "html_url": "https://webdemo.pagerduty.com/incidents/PRORDTY"
+ },
+ "teams": [
+ {
+ "id": "P4SI59S",
+ "type": "team_reference",
+ "summary": "Engineering",
+ "self": "https://api.pagerduty.com/teams/P4SI59S",
+ "html_url": "https://webdemo.pagerduty.com/teams/P4SI59S"
+ }
+ ],
+ "contexts": [],
+ "event_details": {
+ "description": "My new incident"
+ }
+ }
+ ],
+ "webhook": {
+ "endpoint_url": "https://requestb.in/18ao6fs1",
+ "name": "V2 wabhook",
+ "description": null,
+ "webhook_object": {
+ "id": "PN49J75",
+ "type": "service_reference",
+ "summary": "Production XDB Cluster",
+ "self": "https://api.pagerduty.com/services/PN49J75",
+ "html_url": "https://webdemo.pagerduty.com/services/PN49J75"
+ },
+ "config": {},
+ "outbound_integration": {
+ "id": "PJFWPEP",
+ "type": "outbound_integration_reference",
+ "summary": "Generic V2 Webhook",
+ "self": "https://api.pagerduty.com/outbound_integrations/PJFWPEP",
+ "html_url": null
+ },
+ "accounts_addon": null,
+ "id": "PKT9NNX",
+ "type": "webhook",
+ "summary": "V2 wabhook",
+ "self": "https://api.pagerduty.com/webhooks/PKT9NNX",
+ "html_url": null
+ },
+ "incident": {
+ "incident_number": 33,
+ "title": "My new incident",
+ "description": "My new incident",
+ "created_at": "2017-09-26T15:14:36Z",
+ "status": "triggered",
+ "pending_actions": [
+ {
+ "type": "escalate",
+ "at": "2017-09-26T15:44:36Z"
+ },
+ {
+ "type": "resolve",
+ "at": "2017-09-26T19:14:36Z"
+ }
+ ],
+ "incident_key": null,
+ "service": {
+ "id": "PN49J75",
+ "name": "Production XDB Cluster",
+ "description": "This service was created during onboarding on July 5, 2017.",
+ "auto_resolve_timeout": 14400,
+ "acknowledgement_timeout": 1800,
+ "created_at": "2017-07-05T17:33:09Z",
+ "status": "critical",
+ "last_incident_timestamp": "2017-09-26T15:14:36Z",
+ "teams": [
+ {
+ "id": "P4SI59S",
+ "type": "team_reference",
+ "summary": "Engineering",
+ "self": "https://api.pagerduty.com/teams/P4SI59S",
+ "html_url": "https://webdemo.pagerduty.com/teams/P4SI59S"
+ }
+ ],
+ "incident_urgency_rule": {
+ "type": "constant",
+ "urgency": "high"
+ },
+ "scheduled_actions": [],
+ "support_hours": null,
+ "escalation_policy": {
+ "id": "PINYWEF",
+ "type": "escalation_policy_reference",
+ "summary": "Default",
+ "self": "https://api.pagerduty.com/escalation_policies/PINYWEF",
+ "html_url": "https://webdemo.pagerduty.com/escalation_policies/PINYWEF"
+ },
+ "addons": [],
+ "privilege": null,
+ "alert_creation": "create_alerts_and_incidents",
+ "integrations": [
+ {
+ "id": "PUAYF96",
+ "type": "generic_events_api_inbound_integration_reference",
+ "summary": "API",
+ "self": "https://api.pagerduty.com/services/PN49J75/integrations/PUAYF96",
+ "html_url": "https://webdemo.pagerduty.com/services/PN49J75/integrations/PUAYF96"
+ },
+ {
+ "id": "P90GZUH",
+ "type": "generic_email_inbound_integration_reference",
+ "summary": "Email",
+ "self": "https://api.pagerduty.com/services/PN49J75/integrations/P90GZUH",
+ "html_url": "https://webdemo.pagerduty.com/services/PN49J75/integrations/P90GZUH"
+ }
+ ],
+ "metadata": {},
+ "type": "service",
+ "summary": "Production XDB Cluster",
+ "self": "https://api.pagerduty.com/services/PN49J75",
+ "html_url": "https://webdemo.pagerduty.com/services/PN49J75"
+ },
+ "assignments": [
+ {
+ "at": "2017-09-26T15:14:36Z",
+ "assignee": {
+ "id": "P553OPV",
+ "type": "user_reference",
+ "summary": "Laura Haley",
+ "self": "https://api.pagerduty.com/users/P553OPV",
+ "html_url": "https://webdemo.pagerduty.com/users/P553OPV"
+ }
+ }
+ ],
+ "acknowledgements": [],
+ "last_status_change_at": "2017-09-26T15:14:36Z",
+ "last_status_change_by": {
+ "id": "PN49J75",
+ "type": "service_reference",
+ "summary": "Production XDB Cluster",
+ "self": "https://api.pagerduty.com/services/PN49J75",
+ "html_url": "https://webdemo.pagerduty.com/services/PN49J75"
+ },
+ "first_trigger_log_entry": {
+ "id": "R2XGXEI3W0FHMSDXHDIBQGBQ5E",
+ "type": "trigger_log_entry_reference",
+ "summary": "Triggered through the website",
+ "self": "https://api.pagerduty.com/log_entries/R2XGXEI3W0FHMSDXHDIBQGBQ5E",
+ "html_url": "https://webdemo.pagerduty.com/incidents/PRORDTY/log_entries/R2XGXEI3W0FHMSDXHDIBQGBQ5E"
+ },
+ "escalation_policy": {
+ "id": "PINYWEF",
+ "type": "escalation_policy_reference",
+ "summary": "Default",
+ "self": "https://api.pagerduty.com/escalation_policies/PINYWEF",
+ "html_url": "https://webdemo.pagerduty.com/escalation_policies/PINYWEF"
+ },
+ "privilege": null,
+ "teams": [
+ {
+ "id": "P4SI59S",
+ "type": "team_reference",
+ "summary": "Engineering",
+ "self": "https://api.pagerduty.com/teams/P4SI59S",
+ "html_url": "https://webdemo.pagerduty.com/teams/P4SI59S"
+ }
+ ],
+ "alert_counts": {
+ "all": 0,
+ "triggered": 0,
+ "resolved": 0
+ },
+ "impacted_services": [
+ {
+ "id": "PN49J75",
+ "type": "service_reference",
+ "summary": "Production XDB Cluster",
+ "self": "https://api.pagerduty.com/services/PN49J75",
+ "html_url": "https://webdemo.pagerduty.com/services/PN49J75"
+ }
+ ],
+ "is_mergeable": true,
+ "basic_alert_grouping": null,
+ "alert_grouping": null,
+ "metadata": {},
+ "external_references": [],
+ "importance": null,
+ "incidents_responders": [],
+ "responder_requests": [],
+ "subscriber_requests": [],
+ "urgency": "high",
+ "id": "PRORDTY",
+ "type": "incident",
+ "summary": "[#33] My new incident",
+ "self": "https://api.pagerduty.com/incidents/PRORDTY",
+ "html_url": "https://webdemo.pagerduty.com/incidents/PRORDTY",
+ "alerts": [
+ {
+ "alert_key": "c24117fc42e44b44b4d6876190583378"
+ }
+ ]
+ },
+ "id": "69a7ced0-a2cd-11e7-a799-22000a15839c",
+ "created_on": "2017-09-26T15:14:36Z"
+ }
+ ]
+}
diff --git a/spec/fixtures/product_analytics/event.json b/spec/fixtures/product_analytics/event.json
new file mode 100644
index 00000000000..3100b068a0c
--- /dev/null
+++ b/spec/fixtures/product_analytics/event.json
@@ -0,0 +1,16 @@
+{
+ "aid":"1",
+ "p":"web",
+ "tna":"sp",
+ "tv":"js-2.14.0",
+ "eid":"fbf14096-74ee-47e4-883c-8a0d6cb72e37",
+ "duid":"79543c31-cfc3-4479-a737-fafb9333c8ba",
+ "sid":"54f6d3f3-f4f9-4fdc-87e0-a2c775234c1b",
+ "vid":4,
+ "url":"http://example.com/products/1",
+ "refr":"http://example.com/products/1",
+ "lang":"en-US",
+ "cookie":"1",
+ "tz":"America/Los_Angeles",
+ "cs":"UTF-8"
+}
diff --git a/spec/fixtures/sentry/issue_sample_response.json b/spec/fixtures/sentry/issue_sample_response.json
index 43d55f584b8..82588bd0749 100644
--- a/spec/fixtures/sentry/issue_sample_response.json
+++ b/spec/fixtures/sentry/issue_sample_response.json
@@ -42,7 +42,30 @@
"isBookmarked": false,
"isPublic": false,
"isSubscribed": true,
- "lastRelease": null,
+ "lastRelease": {
+ "dateReleased": null,
+ "commitCount": 1,
+ "url": null,
+ "data": {},
+ "lastDeploy": {},
+ "deployCount": 3,
+ "dateCreated": "2020-06-29T08:10:45.909Z",
+ "lastEvent": "2020-06-30T09:47:19.651Z",
+ "version": "17642328ead24b51867165985996d04b29321448",
+ "firstEvent": "2020-06-29T09:13:36.696Z",
+ "lastCommit": {},
+ "shortVersion": "27de6b42eb4",
+ "authors": [],
+ "owner": null,
+ "newGroups": 208,
+ "ref": null,
+ "projects": [
+ {
+ "name": "Pump Station",
+ "slug": "pump-station"
+ }
+ ]
+ },
"lastSeen": "2018-11-06T21:19:55Z",
"level": "error",
"logger": null,
diff --git a/spec/frontend/__mocks__/@gitlab/ui.js b/spec/frontend/__mocks__/@gitlab/ui.js
index d65fab80d3b..ea36f1dabaf 100644
--- a/spec/frontend/__mocks__/@gitlab/ui.js
+++ b/spec/frontend/__mocks__/@gitlab/ui.js
@@ -7,22 +7,23 @@ export * from '@gitlab/ui';
*
* This mock decouples those tests from the implementation, removing the need to set
* them up specially just for these tooltips.
+ *
+ * Mocking the modules using the full file path allows the mocks to take effect
+ * when the modules are imported in this project (`gitlab`) **and** when they
+ * are imported internally in `@gitlab/ui`.
*/
-export const GlTooltipDirective = {
+
+jest.mock('@gitlab/ui/dist/directives/tooltip.js', () => ({
bind() {},
-};
+}));
-export const GlTooltip = {
+jest.mock('@gitlab/ui/dist/components/base/tooltip/tooltip.js', () => ({
render(h) {
return h('div', this.$attrs, this.$slots.default);
},
-};
-
-export const GlPopoverDirective = {
- bind() {},
-};
+}));
-export const GlPopover = {
+jest.mock('@gitlab/ui/dist/components/base/popover/popover.js', () => ({
props: {
cssClasses: {
type: Array,
@@ -33,4 +34,4 @@ export const GlPopover = {
render(h) {
return h('div', this.$attrs, Object.keys(this.$slots).map(s => this.$slots[s]));
},
-};
+}));
diff --git a/spec/frontend/__mocks__/document-register-element/index.js b/spec/frontend/__mocks__/document-register-element/index.js
new file mode 100644
index 00000000000..2d1ec238274
--- /dev/null
+++ b/spec/frontend/__mocks__/document-register-element/index.js
@@ -0,0 +1 @@
+export default () => {};
diff --git a/spec/frontend/__mocks__/monaco-editor/index.js b/spec/frontend/__mocks__/monaco-editor/index.js
index 7c53cfb5174..b9602d69b74 100644
--- a/spec/frontend/__mocks__/monaco-editor/index.js
+++ b/spec/frontend/__mocks__/monaco-editor/index.js
@@ -8,9 +8,11 @@ import 'monaco-editor/esm/vs/language/css/monaco.contribution';
import 'monaco-editor/esm/vs/language/json/monaco.contribution';
import 'monaco-editor/esm/vs/language/html/monaco.contribution';
import 'monaco-editor/esm/vs/basic-languages/monaco.contribution';
+import 'monaco-yaml/esm/monaco.contribution';
// This language starts trying to spin up web workers which obviously breaks in Jest environment
jest.mock('monaco-editor/esm/vs/language/typescript/tsMode');
+jest.mock('monaco-yaml/esm/yamlMode');
export * from 'monaco-editor/esm/vs/editor/editor.api';
export default global.monaco;
diff --git a/spec/frontend/alert_management/components/alert_management_detail_spec.js b/spec/frontend/alert_management/components/alert_management_detail_spec.js
index 14e45a4f563..daa730d3b9f 100644
--- a/spec/frontend/alert_management/components/alert_management_detail_spec.js
+++ b/spec/frontend/alert_management/components/alert_management_detail_spec.js
@@ -3,7 +3,7 @@ import { GlAlert, GlLoadingIcon, GlTable } from '@gitlab/ui';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import AlertDetails from '~/alert_management/components/alert_details.vue';
-import createIssueQuery from '~/alert_management/graphql/mutations/create_issue_from_alert.graphql';
+import createIssueMutation from '~/alert_management/graphql/mutations/create_issue_from_alert.mutation.graphql';
import { joinPaths } from '~/lib/utils/url_utility';
import {
trackAlertsDetailsViewsOptions,
@@ -19,18 +19,20 @@ describe('AlertDetails', () => {
let mock;
const projectPath = 'root/alerts';
const projectIssuesPath = 'root/alerts/-/issues';
+ const projectId = '1';
const findDetailsTable = () => wrapper.find(GlTable);
function mountComponent({ data, loading = false, mountMethod = shallowMount, stubs = {} } = {}) {
wrapper = mountMethod(AlertDetails, {
- propsData: {
+ provide: {
alertId: 'alertId',
projectPath,
projectIssuesPath,
+ projectId,
},
data() {
- return { alert: { ...mockAlert }, ...data };
+ return { alert: { ...mockAlert }, sidebarStatus: false, ...data };
},
mocks: {
$apollo: {
@@ -39,6 +41,7 @@ describe('AlertDetails', () => {
alert: {
loading,
},
+ sidebarStatus: {},
},
},
},
@@ -52,9 +55,7 @@ describe('AlertDetails', () => {
afterEach(() => {
if (wrapper) {
- if (wrapper) {
- wrapper.destroy();
- }
+ wrapper.destroy();
}
mock.restore();
});
@@ -133,7 +134,7 @@ describe('AlertDetails', () => {
it('should display "View issue" button that links the issue page when issue exists', () => {
const issueIid = '3';
mountComponent({
- data: { alert: { ...mockAlert, issueIid } },
+ data: { alert: { ...mockAlert, issueIid }, sidebarStatus: false },
});
expect(findViewIssueBtn().exists()).toBe(true);
expect(findViewIssueBtn().attributes('href')).toBe(joinPaths(projectIssuesPath, issueIid));
@@ -146,8 +147,11 @@ describe('AlertDetails', () => {
mountMethod: mount,
data: { alert: { ...mockAlert, issueIid } },
});
- expect(findViewIssueBtn().exists()).toBe(false);
- expect(findCreateIssueBtn().exists()).toBe(true);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findViewIssueBtn().exists()).toBe(false);
+ expect(findCreateIssueBtn().exists()).toBe(true);
+ });
});
it('calls `$apollo.mutate` with `createIssueQuery`', () => {
@@ -158,7 +162,7 @@ describe('AlertDetails', () => {
findCreateIssueBtn().trigger('click');
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
- mutation: createIssueQuery,
+ mutation: createIssueMutation,
variables: {
iid: mockAlert.iid,
projectPath,
@@ -208,6 +212,13 @@ describe('AlertDetails', () => {
expect(wrapper.find(GlAlert).exists()).toBe(true);
});
+ it('renders html-errors correctly', () => {
+ mountComponent({
+ data: { errored: true, sidebarErrorMessage: '<span data-testid="htmlError" />' },
+ });
+ expect(wrapper.find('[data-testid="htmlError"]').exists()).toBe(true);
+ });
+
it('does not display an error when dismissed', () => {
mountComponent({ data: { errored: true, isErrorDismissed: true } });
expect(wrapper.find(GlAlert).exists()).toBe(false);
diff --git a/spec/frontend/alert_management/components/alert_management_empty_state_spec.js b/spec/frontend/alert_management/components/alert_management_empty_state_spec.js
new file mode 100644
index 00000000000..0d1214211d3
--- /dev/null
+++ b/spec/frontend/alert_management/components/alert_management_empty_state_spec.js
@@ -0,0 +1,54 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlEmptyState } from '@gitlab/ui';
+import AlertManagementEmptyState from '~/alert_management/components/alert_management_empty_state.vue';
+
+describe('AlertManagementEmptyState', () => {
+ let wrapper;
+
+ function mountComponent({
+ props = {
+ alertManagementEnabled: false,
+ userCanEnableAlertManagement: false,
+ },
+ stubs = {},
+ } = {}) {
+ wrapper = shallowMount(AlertManagementEmptyState, {
+ propsData: {
+ enableAlertManagementPath: '/link',
+ emptyAlertSvgPath: 'illustration/path',
+ ...props,
+ },
+ stubs,
+ });
+ }
+
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ const EmptyState = () => wrapper.find(GlEmptyState);
+
+ describe('Empty state', () => {
+ it('shows empty state', () => {
+ expect(EmptyState().exists()).toBe(true);
+ });
+
+ it('show OpsGenie integration state when OpsGenie mcv is true', () => {
+ mountComponent({
+ props: {
+ alertManagementEnabled: false,
+ userCanEnableAlertManagement: false,
+ opsgenieMvcEnabled: true,
+ opsgenieMvcTargetUrl: 'https://opsgenie-url.com',
+ },
+ });
+ expect(EmptyState().props('title')).toBe('Opsgenie is enabled');
+ });
+ });
+});
diff --git a/spec/frontend/alert_management/components/alert_management_list_wrapper_spec.js b/spec/frontend/alert_management/components/alert_management_list_wrapper_spec.js
new file mode 100644
index 00000000000..4644406c037
--- /dev/null
+++ b/spec/frontend/alert_management/components/alert_management_list_wrapper_spec.js
@@ -0,0 +1,57 @@
+import { shallowMount } from '@vue/test-utils';
+import AlertManagementList from '~/alert_management/components/alert_management_list_wrapper.vue';
+import { trackAlertListViewsOptions } from '~/alert_management/constants';
+import mockAlerts from '../mocks/alerts.json';
+import Tracking from '~/tracking';
+
+describe('AlertManagementList', () => {
+ let wrapper;
+
+ function mountComponent({
+ props = {
+ alertManagementEnabled: false,
+ userCanEnableAlertManagement: false,
+ },
+ data = {},
+ stubs = {},
+ } = {}) {
+ wrapper = shallowMount(AlertManagementList, {
+ propsData: {
+ projectPath: 'gitlab-org/gitlab',
+ enableAlertManagementPath: '/link',
+ populatingAlertsHelpUrl: '/help/help-page.md#populating-alert-data',
+ emptyAlertSvgPath: 'illustration/path',
+ ...props,
+ },
+ data() {
+ return data;
+ },
+ stubs,
+ });
+ }
+
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ describe('Snowplow tracking', () => {
+ beforeEach(() => {
+ jest.spyOn(Tracking, 'event');
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: mockAlerts } },
+ });
+ });
+
+ it('should track alert list page views', () => {
+ const { category, action } = trackAlertListViewsOptions;
+ expect(Tracking.event).toHaveBeenCalledWith(category, action);
+ });
+ });
+});
diff --git a/spec/frontend/alert_management/components/alert_management_sidebar_todo_spec.js b/spec/frontend/alert_management/components/alert_management_sidebar_todo_spec.js
new file mode 100644
index 00000000000..fe08cf2c10a
--- /dev/null
+++ b/spec/frontend/alert_management/components/alert_management_sidebar_todo_spec.js
@@ -0,0 +1,76 @@
+import { mount } from '@vue/test-utils';
+import SidebarTodo from '~/alert_management/components/sidebar/sidebar_todo.vue';
+import AlertMarkTodo from '~/alert_management/graphql/mutations/alert_todo_create.graphql';
+import mockAlerts from '../mocks/alerts.json';
+
+const mockAlert = mockAlerts[0];
+
+describe('Alert Details Sidebar To Do', () => {
+ let wrapper;
+
+ function mountComponent({ data, sidebarCollapsed = true, loading = false, stubs = {} } = {}) {
+ wrapper = mount(SidebarTodo, {
+ propsData: {
+ alert: { ...mockAlert },
+ ...data,
+ sidebarCollapsed,
+ projectPath: 'projectPath',
+ },
+ mocks: {
+ $apollo: {
+ mutate: jest.fn(),
+ queries: {
+ alert: {
+ loading,
+ },
+ },
+ },
+ },
+ stubs,
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('updating the alert to do', () => {
+ const mockUpdatedMutationResult = {
+ data: {
+ updateAlertTodo: {
+ errors: [],
+ alert: {},
+ },
+ },
+ };
+
+ beforeEach(() => {
+ mountComponent({
+ data: { alert: mockAlert },
+ sidebarCollapsed: false,
+ loading: false,
+ });
+ });
+
+ it('renders a button for adding a To Do', () => {
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find('[data-testid="alert-todo-button"]').text()).toBe('Add a To Do');
+ });
+ });
+
+ it('calls `$apollo.mutate` with `AlertMarkTodo` mutation and variables containing `iid`, `todoEvent`, & `projectPath`', () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult);
+
+ return wrapper.vm.$nextTick().then(() => {
+ wrapper.find('button').trigger('click');
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
+ mutation: AlertMarkTodo,
+ variables: {
+ iid: '1527542',
+ projectPath: 'projectPath',
+ },
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/alert_management/components/alert_management_list_spec.js b/spec/frontend/alert_management/components/alert_management_table_spec.js
index 0154e5fa112..f316126432e 100644
--- a/spec/frontend/alert_management/components/alert_management_list_spec.js
+++ b/spec/frontend/alert_management/components/alert_management_table_spec.js
@@ -1,6 +1,5 @@
import { mount } from '@vue/test-utils';
import {
- GlEmptyState,
GlTable,
GlAlert,
GlLoadingIcon,
@@ -11,28 +10,22 @@ import {
GlTab,
GlBadge,
GlPagination,
+ GlSearchBoxByType,
} from '@gitlab/ui';
import { visitUrl } from '~/lib/utils/url_utility';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
-import createFlash from '~/flash';
-import AlertManagementList from '~/alert_management/components/alert_management_list.vue';
-import {
- ALERTS_STATUS_TABS,
- trackAlertListViewsOptions,
- trackAlertStatusUpdateOptions,
-} from '~/alert_management/constants';
-import updateAlertStatus from '~/alert_management/graphql/mutations/update_alert_status.graphql';
+import AlertManagementTable from '~/alert_management/components/alert_management_table.vue';
+import { ALERTS_STATUS_TABS, trackAlertStatusUpdateOptions } from '~/alert_management/constants';
+import updateAlertStatus from '~/alert_management/graphql/mutations/update_alert_status.mutation.graphql';
import mockAlerts from '../mocks/alerts.json';
import Tracking from '~/tracking';
-jest.mock('~/flash');
-
jest.mock('~/lib/utils/url_utility', () => ({
visitUrl: jest.fn().mockName('visitUrlMock'),
joinPaths: jest.requireActual('~/lib/utils/url_utility').joinPaths,
}));
-describe('AlertManagementList', () => {
+describe('AlertManagementTable', () => {
let wrapper;
const findAlertsTable = () => wrapper.find(GlTable);
@@ -49,6 +42,8 @@ describe('AlertManagementList', () => {
const findSeverityFields = () => wrapper.findAll('[data-testid="severityField"]');
const findSeverityColumnHeader = () => wrapper.findAll('th').at(0);
const findPagination = () => wrapper.find(GlPagination);
+ const findSearch = () => wrapper.find(GlSearchBoxByType);
+ const findIssueFields = () => wrapper.findAll('[data-testid="issueField"]');
const alertsCount = {
open: 14,
triggered: 10,
@@ -66,11 +61,10 @@ describe('AlertManagementList', () => {
loading = false,
stubs = {},
} = {}) {
- wrapper = mount(AlertManagementList, {
+ wrapper = mount(AlertManagementTable, {
propsData: {
projectPath: 'gitlab-org/gitlab',
- enableAlertManagementPath: '/link',
- emptyAlertSvgPath: 'illustration/path',
+ populatingAlertsHelpUrl: '/help/help-page.md#populating-alert-data',
...props,
},
data() {
@@ -92,7 +86,7 @@ describe('AlertManagementList', () => {
}
beforeEach(() => {
- mountComponent();
+ mountComponent({ data: { alerts: mockAlerts, alertsCount } });
});
afterEach(() => {
@@ -101,12 +95,6 @@ describe('AlertManagementList', () => {
}
});
- describe('Empty state', () => {
- it('shows empty state', () => {
- expect(wrapper.find(GlEmptyState).exists()).toBe(true);
- });
- });
-
describe('Status Filter Tabs', () => {
beforeEach(() => {
mountComponent({
@@ -206,6 +194,15 @@ describe('AlertManagementList', () => {
expect(findStatusDropdown().exists()).toBe(true);
});
+ it('does not display a dropdown status header', () => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
+ loading: false,
+ });
+ expect(findStatusDropdown().contains('.dropdown-title')).toBe(false);
+ });
+
it('shows correct severity icons', () => {
mountComponent({
props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
@@ -278,6 +275,37 @@ describe('AlertManagementList', () => {
expect(visitUrl).toHaveBeenCalledWith('/1527542/details');
});
+ describe('alert issue links', () => {
+ beforeEach(() => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
+ loading: false,
+ });
+ });
+
+ it('shows "None" when no link exists', () => {
+ expect(
+ findIssueFields()
+ .at(0)
+ .text(),
+ ).toBe('None');
+ });
+
+ it('renders a link when one exists', () => {
+ expect(
+ findIssueFields()
+ .at(1)
+ .text(),
+ ).toBe('#1');
+ expect(
+ findIssueFields()
+ .at(1)
+ .attributes('href'),
+ ).toBe('/gitlab-org/gitlab/-/issues/1');
+ });
+ });
+
describe('handle date fields', () => {
it('should display time ago dates when values provided', () => {
mountComponent({
@@ -289,7 +317,6 @@ describe('AlertManagementList', () => {
iid: 1,
status: 'acknowledged',
startedAt: '2020-03-17T23:18:14.996Z',
- endedAt: '2020-04-17T23:18:14.996Z',
severity: 'high',
assignees: { nodes: [] },
},
@@ -300,7 +327,7 @@ describe('AlertManagementList', () => {
},
loading: false,
});
- expect(findDateFields().length).toBe(2);
+ expect(findDateFields().length).toBe(1);
});
it('should not display time ago dates when values not provided', () => {
@@ -312,7 +339,6 @@ describe('AlertManagementList', () => {
iid: 1,
status: 'acknowledged',
startedAt: null,
- endedAt: null,
severity: 'high',
},
],
@@ -323,6 +349,40 @@ describe('AlertManagementList', () => {
});
expect(findDateFields().exists()).toBe(false);
});
+
+ describe('New Alert indicator', () => {
+ const oldAlert = mockAlerts[0];
+
+ const newAlert = { ...oldAlert, isNew: true };
+
+ it('should highlight the row when alert is new', () => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: [newAlert] }, alertsCount, errored: false },
+ loading: false,
+ });
+
+ expect(
+ findAlerts()
+ .at(0)
+ .classes(),
+ ).toContain('new-alert');
+ });
+
+ it('should not highlight the row when alert is not new', () => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: [oldAlert] }, alertsCount, errored: false },
+ loading: false,
+ });
+
+ expect(
+ findAlerts()
+ .at(0)
+ .classes(),
+ ).not.toContain('new-alert');
+ });
+ });
});
});
@@ -388,14 +448,38 @@ describe('AlertManagementList', () => {
});
});
- it('calls `createFlash` when request fails', () => {
+ it('shows an error when request fails', () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error()));
findFirstStatusOption().vm.$emit('click');
+ wrapper.setData({
+ errored: true,
+ });
- setImmediate(() => {
- expect(createFlash).toHaveBeenCalledWith(
- 'There was an error while updating the status of the alert. Please try again.',
- );
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find('[data-testid="alert-error"]').exists()).toBe(true);
+ });
+ });
+
+ it('shows an error when response includes HTML errors', () => {
+ const mockUpdatedMutationErrorResult = {
+ data: {
+ updateAlertStatus: {
+ errors: ['<span data-testid="htmlError" />'],
+ alert: {
+ iid,
+ status: 'acknowledged',
+ },
+ },
+ },
+ };
+
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationErrorResult);
+ findFirstStatusOption().vm.$emit('click');
+ wrapper.setData({ errored: true });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.contains('[data-testid="alert-error"]')).toBe(true);
+ expect(wrapper.contains('[data-testid="htmlError"]')).toBe(true);
});
});
});
@@ -410,11 +494,6 @@ describe('AlertManagementList', () => {
});
});
- it('should track alert list page views', () => {
- const { category, action } = trackAlertListViewsOptions;
- expect(Tracking.event).toHaveBeenCalledWith(category, action);
- });
-
it('should track alert status updates', () => {
Tracking.event.mockClear();
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({});
@@ -438,14 +517,14 @@ describe('AlertManagementList', () => {
it('does NOT show pagination control when list is smaller than default page size', () => {
findStatusTabs().vm.$emit('input', 3);
- wrapper.vm.$nextTick(() => {
+ return wrapper.vm.$nextTick(() => {
expect(findPagination().exists()).toBe(false);
});
});
it('shows pagination control when list is larger than default page size', () => {
findStatusTabs().vm.$emit('input', 0);
- wrapper.vm.$nextTick(() => {
+ return wrapper.vm.$nextTick(() => {
expect(findPagination().exists()).toBe(true);
});
});
@@ -486,4 +565,26 @@ describe('AlertManagementList', () => {
});
});
});
+
+ describe('Search', () => {
+ beforeEach(() => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
+ loading: false,
+ });
+ });
+
+ it('renders the search component', () => {
+ expect(findSearch().exists()).toBe(true);
+ });
+
+ it('sets the `searchTerm` graphql variable', () => {
+ const SEARCH_TERM = 'Simple Alert';
+
+ findSearch().vm.$emit('input', SEARCH_TERM);
+
+ expect(wrapper.vm.$data.searchTerm).toBe(SEARCH_TERM);
+ });
+ });
});
diff --git a/spec/frontend/alert_management/components/alert_metrics_spec.js b/spec/frontend/alert_management/components/alert_metrics_spec.js
new file mode 100644
index 00000000000..c188363ddc2
--- /dev/null
+++ b/spec/frontend/alert_management/components/alert_metrics_spec.js
@@ -0,0 +1,67 @@
+import { shallowMount } from '@vue/test-utils';
+import waitForPromises from 'helpers/wait_for_promises';
+import AlertMetrics from '~/alert_management/components/alert_metrics.vue';
+import MockAdapter from 'axios-mock-adapter';
+import axios from 'axios';
+
+jest.mock('~/monitoring/stores', () => ({
+ monitoringDashboard: {},
+}));
+
+const mockEmbedName = 'MetricsEmbedStub';
+
+jest.mock('~/monitoring/components/embeds/metric_embed.vue', () => ({
+ name: mockEmbedName,
+ render(h) {
+ return h('div');
+ },
+}));
+
+describe('Alert Metrics', () => {
+ let wrapper;
+ const mock = new MockAdapter(axios);
+
+ function mountComponent({ props } = {}) {
+ wrapper = shallowMount(AlertMetrics, {
+ propsData: {
+ ...props,
+ },
+ stubs: {
+ MetricEmbed: true,
+ },
+ });
+ }
+
+ const findChart = () => wrapper.find({ name: mockEmbedName });
+ const findEmptyState = () => wrapper.find({ ref: 'emptyState' });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ afterAll(() => {
+ mock.restore();
+ });
+
+ describe('Empty state', () => {
+ it('should display a message when metrics dashboard url is not provided ', () => {
+ mountComponent();
+ expect(findChart().exists()).toBe(false);
+ expect(findEmptyState().text()).toBe("Metrics weren't available in the alerts payload.");
+ });
+ });
+
+ describe('Chart', () => {
+ it('should be rendered when dashboard url is provided', async () => {
+ mountComponent({ props: { dashboardUrl: 'metrics.url' } });
+
+ await waitForPromises();
+ await wrapper.vm.$nextTick();
+
+ expect(findEmptyState().exists()).toBe(false);
+ expect(findChart().exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/alert_management/components/alert_sidebar_status_spec.js b/spec/frontend/alert_management/components/alert_sidebar_status_spec.js
deleted file mode 100644
index 94643966a43..00000000000
--- a/spec/frontend/alert_management/components/alert_sidebar_status_spec.js
+++ /dev/null
@@ -1,107 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlDropdownItem, GlLoadingIcon } from '@gitlab/ui';
-import { trackAlertStatusUpdateOptions } from '~/alert_management/constants';
-import AlertSidebarStatus from '~/alert_management/components/sidebar/sidebar_status.vue';
-import updateAlertStatus from '~/alert_management/graphql/mutations/update_alert_status.graphql';
-import Tracking from '~/tracking';
-import mockAlerts from '../mocks/alerts.json';
-
-const mockAlert = mockAlerts[0];
-
-describe('Alert Details Sidebar Status', () => {
- let wrapper;
- const findStatusDropdownItem = () => wrapper.find(GlDropdownItem);
- const findStatusLoadingIcon = () => wrapper.find(GlLoadingIcon);
-
- function mountComponent({ data, sidebarCollapsed = true, loading = false, stubs = {} } = {}) {
- wrapper = shallowMount(AlertSidebarStatus, {
- propsData: {
- alert: { ...mockAlert },
- ...data,
- sidebarCollapsed,
- projectPath: 'projectPath',
- },
- mocks: {
- $apollo: {
- mutate: jest.fn(),
- queries: {
- alert: {
- loading,
- },
- },
- },
- },
- stubs,
- });
- }
-
- afterEach(() => {
- if (wrapper) {
- wrapper.destroy();
- }
- });
-
- describe('updating the alert status', () => {
- const mockUpdatedMutationResult = {
- data: {
- updateAlertStatus: {
- errors: [],
- alert: {
- status: 'acknowledged',
- },
- },
- },
- };
-
- beforeEach(() => {
- mountComponent({
- data: { alert: mockAlert },
- sidebarCollapsed: false,
- loading: false,
- });
- });
-
- it('calls `$apollo.mutate` with `updateAlertStatus` mutation and variables containing `iid`, `status`, & `projectPath`', () => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult);
- findStatusDropdownItem().vm.$emit('click');
-
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
- mutation: updateAlertStatus,
- variables: {
- iid: '1527542',
- status: 'TRIGGERED',
- projectPath: 'projectPath',
- },
- });
- });
-
- it('stops updating when the request fails', () => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error()));
- findStatusDropdownItem().vm.$emit('click');
- expect(findStatusLoadingIcon().exists()).toBe(false);
- expect(wrapper.find('[data-testid="status"]').text()).toBe('Triggered');
- });
- });
-
- describe('Snowplow tracking', () => {
- beforeEach(() => {
- jest.spyOn(Tracking, 'event');
- mountComponent({
- props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
- data: { alert: mockAlert },
- loading: false,
- });
- });
-
- it('should track alert status updates', () => {
- Tracking.event.mockClear();
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({});
- findStatusDropdownItem().vm.$emit('click');
- const status = findStatusDropdownItem().text();
- setImmediate(() => {
- const { category, action, label } = trackAlertStatusUpdateOptions;
- expect(Tracking.event).toHaveBeenCalledWith(category, action, { label, property: status });
- });
- });
- });
-});
diff --git a/spec/frontend/alert_management/components/alert_managment_sidebar_assignees_spec.js b/spec/frontend/alert_management/components/sidebar/alert_managment_sidebar_assignees_spec.js
index 5dbd83dbdac..db086782424 100644
--- a/spec/frontend/alert_management/components/alert_managment_sidebar_assignees_spec.js
+++ b/spec/frontend/alert_management/components/sidebar/alert_managment_sidebar_assignees_spec.js
@@ -4,8 +4,8 @@ import MockAdapter from 'axios-mock-adapter';
import { GlDropdownItem } from '@gitlab/ui';
import SidebarAssignee from '~/alert_management/components/sidebar/sidebar_assignee.vue';
import SidebarAssignees from '~/alert_management/components/sidebar/sidebar_assignees.vue';
-import AlertSetAssignees from '~/alert_management/graphql/mutations/alert_set_assignees.graphql';
-import mockAlerts from '../mocks/alerts.json';
+import AlertSetAssignees from '~/alert_management/graphql/mutations/alert_set_assignees.mutation.graphql';
+import mockAlerts from '../../mocks/alerts.json';
const mockAlert = mockAlerts[0];
@@ -33,6 +33,7 @@ describe('Alert Details Sidebar Assignees', () => {
...data,
sidebarCollapsed,
projectPath: 'projectPath',
+ projectId: '1',
},
mocks: {
$apollo: {
@@ -58,7 +59,7 @@ describe('Alert Details Sidebar Assignees', () => {
describe('updating the alert status', () => {
const mockUpdatedMutationResult = {
data: {
- updateAlertStatus: {
+ alertSetAssignees: {
errors: [],
alert: {
assigneeUsernames: ['root'],
@@ -69,7 +70,7 @@ describe('Alert Details Sidebar Assignees', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
- const path = '/autocomplete/users.json';
+ const path = '/-/autocomplete/users.json';
const users = [
{
avatar_url:
@@ -124,6 +125,26 @@ describe('Alert Details Sidebar Assignees', () => {
});
});
+ it('shows an error when request contains error messages', () => {
+ wrapper.setData({ isDropdownSearching: false });
+ const errorMutationResult = {
+ data: {
+ alertSetAssignees: {
+ errors: ['There was a problem for sure.'],
+ alert: {},
+ },
+ },
+ };
+
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(errorMutationResult);
+
+ return wrapper.vm.$nextTick().then(() => {
+ const SideBarAssigneeItem = wrapper.findAll(SidebarAssignee).at(0);
+ SideBarAssigneeItem.vm.$emit('click');
+ expect(wrapper.emitted('alert-refresh')).toBeUndefined();
+ });
+ });
+
it('stops updating and cancels loading when the request fails', () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error()));
wrapper.vm.updateAlertAssignees('root');
diff --git a/spec/frontend/alert_management/components/alert_sidebar_spec.js b/spec/frontend/alert_management/components/sidebar/alert_sidebar_spec.js
index 80c4d9e0650..5235ae63fee 100644
--- a/spec/frontend/alert_management/components/alert_sidebar_spec.js
+++ b/spec/frontend/alert_management/components/sidebar/alert_sidebar_spec.js
@@ -3,7 +3,7 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import AlertSidebar from '~/alert_management/components/alert_sidebar.vue';
import SidebarAssignees from '~/alert_management/components/sidebar/sidebar_assignees.vue';
-import mockAlerts from '../mocks/alerts.json';
+import mockAlerts from '../../mocks/alerts.json';
const mockAlert = mockAlerts[0];
@@ -11,19 +11,28 @@ describe('Alert Details Sidebar', () => {
let wrapper;
let mock;
- function mountComponent({
- sidebarCollapsed = true,
- mountMethod = shallowMount,
- stubs = {},
- alert = {},
- } = {}) {
+ function mountComponent({ mountMethod = shallowMount, stubs = {}, alert = {} } = {}) {
wrapper = mountMethod(AlertSidebar, {
+ data() {
+ return {
+ sidebarStatus: false,
+ };
+ },
propsData: {
alert,
- sidebarCollapsed,
+ },
+ provide: {
projectPath: 'projectPath',
+ projectId: '1',
},
stubs,
+ mocks: {
+ $apollo: {
+ queries: {
+ sidebarStatus: {},
+ },
+ },
+ },
});
}
@@ -41,7 +50,7 @@ describe('Alert Details Sidebar', () => {
});
it('open as default', () => {
- expect(wrapper.props('sidebarCollapsed')).toBe(true);
+ expect(wrapper.classes('right-sidebar-expanded')).toBe(true);
});
it('should render side bar assignee dropdown', () => {
diff --git a/spec/frontend/alert_management/components/sidebar/alert_sidebar_status_spec.js b/spec/frontend/alert_management/components/sidebar/alert_sidebar_status_spec.js
new file mode 100644
index 00000000000..c2eaf540e9c
--- /dev/null
+++ b/spec/frontend/alert_management/components/sidebar/alert_sidebar_status_spec.js
@@ -0,0 +1,129 @@
+import { mount } from '@vue/test-utils';
+import { GlDropdown, GlDropdownItem, GlLoadingIcon } from '@gitlab/ui';
+import { trackAlertStatusUpdateOptions } from '~/alert_management/constants';
+import AlertSidebarStatus from '~/alert_management/components/sidebar/sidebar_status.vue';
+import updateAlertStatus from '~/alert_management/graphql/mutations/update_alert_status.mutation.graphql';
+import Tracking from '~/tracking';
+import mockAlerts from '../../mocks/alerts.json';
+
+const mockAlert = mockAlerts[0];
+
+describe('Alert Details Sidebar Status', () => {
+ let wrapper;
+ const findStatusDropdown = () => wrapper.find(GlDropdown);
+ const findStatusDropdownItem = () => wrapper.find(GlDropdownItem);
+ const findStatusLoadingIcon = () => wrapper.find(GlLoadingIcon);
+
+ function mountComponent({ data, sidebarCollapsed = true, loading = false, stubs = {} } = {}) {
+ wrapper = mount(AlertSidebarStatus, {
+ propsData: {
+ alert: { ...mockAlert },
+ ...data,
+ sidebarCollapsed,
+ projectPath: 'projectPath',
+ },
+ mocks: {
+ $apollo: {
+ mutate: jest.fn(),
+ queries: {
+ alert: {
+ loading,
+ },
+ },
+ },
+ },
+ stubs,
+ });
+ }
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ describe('Alert Sidebar Dropdown Status', () => {
+ beforeEach(() => {
+ mountComponent({
+ data: { alert: mockAlert },
+ sidebarCollapsed: false,
+ loading: false,
+ });
+ });
+
+ it('displays status dropdown', () => {
+ expect(findStatusDropdown().exists()).toBe(true);
+ });
+
+ it('displays the dropdown status header', () => {
+ expect(findStatusDropdown().contains('.dropdown-title')).toBe(true);
+ });
+
+ describe('updating the alert status', () => {
+ const mockUpdatedMutationResult = {
+ data: {
+ updateAlertStatus: {
+ errors: [],
+ alert: {
+ status: 'acknowledged',
+ },
+ },
+ },
+ };
+
+ beforeEach(() => {
+ mountComponent({
+ data: { alert: mockAlert },
+ sidebarCollapsed: false,
+ loading: false,
+ });
+ });
+
+ it('calls `$apollo.mutate` with `updateAlertStatus` mutation and variables containing `iid`, `status`, & `projectPath`', () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult);
+ findStatusDropdownItem().vm.$emit('click');
+
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
+ mutation: updateAlertStatus,
+ variables: {
+ iid: '1527542',
+ status: 'TRIGGERED',
+ projectPath: 'projectPath',
+ },
+ });
+ });
+
+ it('stops updating when the request fails', () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error()));
+ findStatusDropdownItem().vm.$emit('click');
+ expect(findStatusLoadingIcon().exists()).toBe(false);
+ expect(wrapper.find('[data-testid="status"]').text()).toBe('Triggered');
+ });
+ });
+
+ describe('Snowplow tracking', () => {
+ beforeEach(() => {
+ jest.spyOn(Tracking, 'event');
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alert: mockAlert },
+ loading: false,
+ });
+ });
+
+ it('should track alert status updates', () => {
+ Tracking.event.mockClear();
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({});
+ findStatusDropdownItem().vm.$emit('click');
+ const status = findStatusDropdownItem().text();
+ setImmediate(() => {
+ const { category, action, label } = trackAlertStatusUpdateOptions;
+ expect(Tracking.event).toHaveBeenCalledWith(category, action, {
+ label,
+ property: status,
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/alert_management/components/alert_management_system_note_spec.js b/spec/frontend/alert_management/components/system_notes/alert_management_system_note_spec.js
index 87dc36cc7cb..8dd663e55d9 100644
--- a/spec/frontend/alert_management/components/alert_management_system_note_spec.js
+++ b/spec/frontend/alert_management/components/system_notes/alert_management_system_note_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import SystemNote from '~/alert_management/components/system_notes/system_note.vue';
-import mockAlerts from '../mocks/alerts.json';
+import mockAlerts from '../../mocks/alerts.json';
const mockAlert = mockAlerts[1];
@@ -28,7 +28,11 @@ describe('Alert Details System Note', () => {
});
it('renders the correct system note', () => {
- expect(wrapper.find('.note-wrapper').attributes('id')).toBe('note_1628');
+ const noteId = wrapper.find('.note-wrapper').attributes('id');
+ const iconRoute = wrapper.find('use').attributes('href');
+
+ expect(noteId).toBe('note_1628');
+ expect(iconRoute.includes('user')).toBe(true);
});
});
});
diff --git a/spec/frontend/alert_management/mocks/alerts.json b/spec/frontend/alert_management/mocks/alerts.json
index 312d1756790..f63019d1e5c 100644
--- a/spec/frontend/alert_management/mocks/alerts.json
+++ b/spec/frontend/alert_management/mocks/alerts.json
@@ -20,6 +20,7 @@
"endedAt": "2020-04-17T23:18:14.996Z",
"status": "ACKNOWLEDGED",
"assignees": { "nodes": [{ "username": "root" }] },
+ "issueIid": "1",
"notes": {
"nodes": [
{
@@ -32,7 +33,8 @@
"name": "Administrator",
"username": "root",
"webUrl": "http://192.168.1.4:3000/root"
- }
+ },
+ "systemNoteIconName": "user"
}
]
}
diff --git a/spec/frontend/alert_settings/__snapshots__/alert_settings_form_spec.js.snap b/spec/frontend/alert_settings/__snapshots__/alert_settings_form_spec.js.snap
new file mode 100644
index 00000000000..1f5c3a80fbb
--- /dev/null
+++ b/spec/frontend/alert_settings/__snapshots__/alert_settings_form_spec.js.snap
@@ -0,0 +1,48 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`AlertsSettingsForm with default values renders the initial template 1`] = `
+"<div>
+ <!---->
+ <div data-testid=\\"alert-settings-description\\" class=\\"gl-mt-5\\">
+ <p>
+ <gl-sprintf-stub message=\\"You must provide this URL and authorization key to authorize an external service to send alerts to GitLab. You can provide this URL and key to multiple services. After configuring an external service, alerts from your service will display on the GitLab %{linkStart}Alerts%{linkEnd} page.\\"></gl-sprintf-stub>
+ </p>
+ <p>
+ <gl-sprintf-stub message=\\"Review your external service's documentation to learn where to provide this information to your external service, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint.\\"></gl-sprintf-stub>
+ </p>
+ </div>
+ <gl-form-stub>
+ <gl-form-group-stub label=\\"Integrations\\" label-for=\\"integrations\\" label-class=\\"label-bold\\">
+ <gl-form-select-stub options=\\"[object Object],[object Object],[object Object]\\" data-testid=\\"alert-settings-select\\" value=\\"generic\\"></gl-form-select-stub> <span class=\\"gl-text-gray-400\\"><gl-sprintf-stub message=\\"Learn more about our %{linkStart}upcoming integrations%{linkEnd}\\"></gl-sprintf-stub></span>
+ </gl-form-group-stub>
+ <gl-form-group-stub label=\\"Active\\" label-for=\\"activated\\" label-class=\\"label-bold\\">
+ <toggle-button-stub id=\\"activated\\"></toggle-button-stub>
+ </gl-form-group-stub>
+ <!---->
+ <gl-form-group-stub label=\\"Webhook URL\\" label-for=\\"url\\" label-class=\\"label-bold\\">
+ <gl-form-input-group-stub value=\\"/alerts/notify.json\\" predefinedoptions=\\"[object Object]\\" id=\\"url\\" readonly=\\"\\"></gl-form-input-group-stub> <span class=\\"gl-text-gray-400\\">
+
+ </span>
+ </gl-form-group-stub>
+ <gl-form-group-stub label=\\"Authorization key\\" label-for=\\"authorization-key\\" label-class=\\"label-bold\\">
+ <gl-form-input-group-stub value=\\"abcedfg123\\" predefinedoptions=\\"[object Object]\\" id=\\"authorization-key\\" readonly=\\"\\" class=\\"gl-mb-2\\"></gl-form-input-group-stub>
+ <gl-button-stub category=\\"tertiary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" disabled=\\"true\\" class=\\"gl-mt-3\\" role=\\"button\\" tabindex=\\"0\\">Reset key</gl-button-stub>
+ <gl-modal-stub modalid=\\"authKeyModal\\" titletag=\\"h4\\" modalclass=\\"\\" size=\\"md\\" title=\\"Reset key\\" ok-title=\\"Reset key\\" ok-variant=\\"danger\\">
+ Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in.
+ </gl-modal-stub>
+ </gl-form-group-stub>
+ <gl-form-group-stub label=\\"Alert test payload\\" label-for=\\"alert-json\\" label-class=\\"label-bold\\">
+ <gl-form-textarea-stub noresize=\\"true\\" id=\\"alert-json\\" disabled=\\"true\\" state=\\"true\\" placeholder=\\"Enter test alert JSON....\\" rows=\\"6\\" max-rows=\\"10\\"></gl-form-textarea-stub>
+ </gl-form-group-stub>
+ <gl-button-stub category=\\"tertiary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" disabled=\\"true\\">Test alert payload</gl-button-stub>
+ <div class=\\"footer-block row-content-block gl-display-flex gl-justify-content-space-between\\">
+ <gl-button-stub category=\\"primary\\" variant=\\"success\\" size=\\"medium\\" icon=\\"\\" disabled=\\"true\\">
+ Save changes
+ </gl-button-stub>
+ <gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" disabled=\\"true\\">
+ Cancel
+ </gl-button-stub>
+ </div>
+ </gl-form-stub>
+</div>"
+`;
diff --git a/spec/frontend/alert_settings/alert_settings_form_spec.js b/spec/frontend/alert_settings/alert_settings_form_spec.js
new file mode 100644
index 00000000000..5a04d768645
--- /dev/null
+++ b/spec/frontend/alert_settings/alert_settings_form_spec.js
@@ -0,0 +1,233 @@
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import { shallowMount } from '@vue/test-utils';
+import { GlModal, GlAlert } from '@gitlab/ui';
+import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form.vue';
+import ToggleButton from '~/vue_shared/components/toggle_button.vue';
+
+const PROMETHEUS_URL = '/prometheus/alerts/notify.json';
+const GENERIC_URL = '/alerts/notify.json';
+const KEY = 'abcedfg123';
+const INVALID_URL = 'http://invalid';
+const ACTIVATED = false;
+
+const defaultProps = {
+ generic: {
+ initialAuthorizationKey: KEY,
+ formPath: INVALID_URL,
+ url: GENERIC_URL,
+ alertsSetupUrl: INVALID_URL,
+ alertsUsageUrl: INVALID_URL,
+ activated: ACTIVATED,
+ },
+ prometheus: {
+ prometheusAuthorizationKey: KEY,
+ prometheusFormPath: INVALID_URL,
+ prometheusUrl: PROMETHEUS_URL,
+ activated: ACTIVATED,
+ },
+ opsgenie: {
+ opsgenieMvcIsAvailable: true,
+ formPath: INVALID_URL,
+ activated: ACTIVATED,
+ opsgenieMvcTargetUrl: GENERIC_URL,
+ },
+};
+
+describe('AlertsSettingsForm', () => {
+ let wrapper;
+ let mockAxios;
+
+ const createComponent = (props = defaultProps, { methods } = {}, data) => {
+ wrapper = shallowMount(AlertsSettingsForm, {
+ data() {
+ return { ...data };
+ },
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ methods,
+ });
+ };
+
+ const findSelect = () => wrapper.find('[data-testid="alert-settings-select"]');
+ const findJsonInput = () => wrapper.find('#alert-json');
+ const findUrl = () => wrapper.find('#url');
+ const findAuthorizationKey = () => wrapper.find('#authorization-key');
+ const findApiUrl = () => wrapper.find('#api-url');
+
+ beforeEach(() => {
+ mockAxios = new MockAdapter(axios);
+ setFixtures(`
+ <div>
+ <span class="js-service-active-status fa fa-circle" data-value="true"></span>
+ <span class="js-service-active-status fa fa-power-off" data-value="false"></span>
+ </div>`);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ mockAxios.restore();
+ });
+
+ describe('with default values', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders the initial template', () => {
+ expect(wrapper.html()).toMatchSnapshot();
+ });
+ });
+
+ describe('reset key', () => {
+ it('triggers resetKey method', () => {
+ const resetGenericKey = jest.fn();
+ const methods = { resetGenericKey };
+ createComponent(defaultProps, { methods });
+
+ wrapper.find(GlModal).vm.$emit('ok');
+
+ expect(resetGenericKey).toHaveBeenCalled();
+ });
+
+ it('updates the authorization key on success', () => {
+ const formPath = 'some/path';
+ mockAxios.onPut(formPath, { service: { token: '' } }).replyOnce(200, { token: 'newToken' });
+ createComponent({ generic: { ...defaultProps.generic, formPath } });
+
+ return wrapper.vm.resetGenericKey().then(() => {
+ expect(findAuthorizationKey().attributes('value')).toBe('newToken');
+ });
+ });
+
+ it('shows a alert message on error', () => {
+ const formPath = 'some/path';
+ mockAxios.onPut(formPath).replyOnce(404);
+
+ createComponent({ generic: { ...defaultProps.generic, formPath } });
+
+ return wrapper.vm.resetGenericKey().then(() => {
+ expect(wrapper.find(GlAlert).exists()).toBe(true);
+ });
+ });
+ });
+
+ describe('activate toggle', () => {
+ it('triggers toggleActivated method', () => {
+ const toggleService = jest.fn();
+ const methods = { toggleService };
+ createComponent(defaultProps, { methods });
+
+ wrapper.find(ToggleButton).vm.$emit('change', true);
+
+ expect(toggleService).toHaveBeenCalled();
+ });
+
+ describe('error is encountered', () => {
+ beforeEach(() => {
+ const formPath = 'some/path';
+ mockAxios.onPut(formPath).replyOnce(500);
+ });
+
+ it('restores previous value', () => {
+ createComponent({ generic: { ...defaultProps.generic, initialActivated: false } });
+ return wrapper.vm.resetGenericKey().then(() => {
+ expect(wrapper.find(ToggleButton).props('value')).toBe(false);
+ });
+ });
+ });
+ });
+
+ describe('prometheus is active', () => {
+ beforeEach(() => {
+ createComponent(
+ { prometheus: { ...defaultProps.prometheus, prometheusIsActivated: true } },
+ {},
+ {
+ selectedEndpoint: 'prometheus',
+ },
+ );
+ });
+
+ it('renders a valid "select"', () => {
+ expect(findSelect().exists()).toBe(true);
+ });
+
+ it('shows the API URL input', () => {
+ expect(findApiUrl().exists()).toBe(true);
+ });
+
+ it('shows the correct default API URL', () => {
+ expect(findUrl().attributes('value')).toBe(PROMETHEUS_URL);
+ });
+ });
+
+ describe('opsgenie is active', () => {
+ beforeEach(() => {
+ createComponent(
+ { opsgenie: { ...defaultProps.opsgenie, opsgenieMvcActivated: true } },
+ {},
+ {
+ selectedEndpoint: 'opsgenie',
+ },
+ );
+ });
+
+ it('shows a input for the opsgenie target URL', () => {
+ expect(findApiUrl().exists()).toBe(true);
+ expect(findSelect().attributes('value')).toBe('opsgenie');
+ });
+ });
+
+ describe('trigger test alert', () => {
+ beforeEach(() => {
+ createComponent({ generic: { ...defaultProps.generic, initialActivated: true } }, {}, true);
+ });
+
+ it('should enable the JSON input', () => {
+ expect(findJsonInput().exists()).toBe(true);
+ expect(findJsonInput().props('value')).toBe(null);
+ });
+
+ it('should validate JSON input', () => {
+ createComponent({ generic: { ...defaultProps.generic } }, true, {
+ testAlertJson: '{ "value": "test" }',
+ });
+
+ findJsonInput().vm.$emit('change');
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findJsonInput().attributes('state')).toBe('true');
+ });
+ });
+
+ describe('alert service is toggled', () => {
+ it('should show a info alert if successful', () => {
+ const formPath = 'some/path';
+ const toggleService = true;
+ mockAxios.onPut(formPath).replyOnce(200);
+
+ createComponent({ generic: { ...defaultProps.generic, formPath } });
+
+ return wrapper.vm.toggleActivated(toggleService).then(() => {
+ expect(wrapper.find(GlAlert).attributes('variant')).toBe('info');
+ });
+ });
+
+ it('should show a error alert if failed', () => {
+ const formPath = 'some/path';
+ const toggleService = true;
+ mockAxios.onPut(formPath).replyOnce(422, {
+ errors: 'Error message to display',
+ });
+
+ createComponent({ generic: { ...defaultProps.generic, formPath } });
+
+ return wrapper.vm.toggleActivated(toggleService).then(() => {
+ expect(wrapper.find(GlAlert).attributes('variant')).toBe('danger');
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/alerts_service_settings/components/alerts_service_form_spec.js b/spec/frontend/alerts_service_settings/components/alerts_service_form_spec.js
index c7c15c8fd44..610f9d6b9bd 100644
--- a/spec/frontend/alerts_service_settings/components/alerts_service_form_spec.js
+++ b/spec/frontend/alerts_service_settings/components/alerts_service_form_spec.js
@@ -15,6 +15,7 @@ const defaultProps = {
alertsSetupUrl: 'http://invalid',
alertsUsageUrl: 'http://invalid',
initialActivated: false,
+ isDisabled: false,
};
describe('AlertsServiceForm', () => {
@@ -166,4 +167,17 @@ describe('AlertsServiceForm', () => {
});
});
});
+
+ describe('form is disabled', () => {
+ beforeEach(() => {
+ createComponent({ isDisabled: true });
+ });
+
+ it('cannot be toggled', () => {
+ wrapper.find(ToggleButton).vm.$emit('change');
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(ToggleButton).props('disabledInput')).toBe(true);
+ });
+ });
+ });
});
diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js
index c1a23d441b3..c94637e04af 100644
--- a/spec/frontend/api_spec.js
+++ b/spec/frontend/api_spec.js
@@ -46,6 +46,77 @@ describe('Api', () => {
});
});
+ describe('packages', () => {
+ const projectId = 'project_a';
+ const packageId = 'package_b';
+ const apiResponse = [{ id: 1, name: 'foo' }];
+
+ describe('groupPackages', () => {
+ const groupId = 'group_a';
+
+ it('fetch all group packages', () => {
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/packages`;
+ jest.spyOn(axios, 'get');
+ mock.onGet(expectedUrl).replyOnce(200, apiResponse);
+
+ return Api.groupPackages(groupId).then(({ data }) => {
+ expect(data).toEqual(apiResponse);
+ expect(axios.get).toHaveBeenCalledWith(expectedUrl, {});
+ });
+ });
+ });
+
+ describe('projectPackages', () => {
+ it('fetch all project packages', () => {
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectId}/packages`;
+ jest.spyOn(axios, 'get');
+ mock.onGet(expectedUrl).replyOnce(200, apiResponse);
+
+ return Api.projectPackages(projectId).then(({ data }) => {
+ expect(data).toEqual(apiResponse);
+ expect(axios.get).toHaveBeenCalledWith(expectedUrl, {});
+ });
+ });
+ });
+
+ describe('buildProjectPackageUrl', () => {
+ it('returns the right url', () => {
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectId}/packages/${packageId}`;
+ const url = Api.buildProjectPackageUrl(projectId, packageId);
+ expect(url).toEqual(expectedUrl);
+ });
+ });
+
+ describe('projectPackage', () => {
+ it('fetch package details', () => {
+ const expectedUrl = `foo`;
+ jest.spyOn(Api, 'buildProjectPackageUrl').mockReturnValue(expectedUrl);
+ jest.spyOn(axios, 'get');
+ mock.onGet(expectedUrl).replyOnce(200, apiResponse);
+
+ return Api.projectPackage(projectId, packageId).then(({ data }) => {
+ expect(data).toEqual(apiResponse);
+ expect(axios.get).toHaveBeenCalledWith(expectedUrl);
+ });
+ });
+ });
+
+ describe('deleteProjectPackage', () => {
+ it('delete a package', () => {
+ const expectedUrl = `foo`;
+
+ jest.spyOn(Api, 'buildProjectPackageUrl').mockReturnValue(expectedUrl);
+ jest.spyOn(axios, 'delete');
+ mock.onDelete(expectedUrl).replyOnce(200, true);
+
+ return Api.deleteProjectPackage(projectId, packageId).then(({ data }) => {
+ expect(data).toEqual(true);
+ expect(axios.delete).toHaveBeenCalledWith(expectedUrl);
+ });
+ });
+ });
+ });
+
describe('group', () => {
it('fetches a group', done => {
const groupId = '123456';
@@ -366,6 +437,30 @@ describe('Api', () => {
});
});
+ describe('commit', () => {
+ const projectId = 'user/project';
+ const sha = 'abcd0123';
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${encodeURIComponent(
+ projectId,
+ )}/repository/commits/${sha}`;
+
+ it('fetches a single commit', () => {
+ mock.onGet(expectedUrl).reply(200, { id: sha });
+
+ return Api.commit(projectId, sha).then(({ data: commit }) => {
+ expect(commit.id).toBe(sha);
+ });
+ });
+
+ it('fetches a single commit without stats', () => {
+ mock.onGet(expectedUrl, { params: { stats: false } }).reply(200, { id: sha });
+
+ return Api.commit(projectId, sha, { stats: false }).then(({ data: commit }) => {
+ expect(commit.id).toBe(sha);
+ });
+ });
+ });
+
describe('issueTemplate', () => {
it('fetches an issue template', done => {
const namespace = 'some namespace';
diff --git a/spec/frontend/awards_handler_spec.js b/spec/frontend/awards_handler_spec.js
index 754f0702b84..6cfbc6024af 100644
--- a/spec/frontend/awards_handler_spec.js
+++ b/spec/frontend/awards_handler_spec.js
@@ -1,63 +1,61 @@
import $ from 'jquery';
import Cookies from 'js-cookie';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import loadAwardsHandler from '~/awards_handler';
-import '~/lib/utils/common_utils';
-import waitForPromises from './helpers/wait_for_promises';
+import { setTestTimeout } from './helpers/timeout';
+import { EMOJI_VERSION } from '~/emoji';
+import { useFakeRequestAnimationFrame } from 'helpers/fake_request_animation_frame';
window.gl = window.gl || {};
window.gon = window.gon || {};
-let openAndWaitForEmojiMenu;
+let mock;
let awardsHandler = null;
const urlRoot = gon.relative_url_root;
-const lazyAssert = (done, assertFn) => {
- jest.runOnlyPendingTimers();
- waitForPromises()
- .then(() => {
- assertFn();
- done();
- })
- .catch(e => {
- throw e;
- });
-};
-
describe('AwardsHandler', () => {
+ useFakeRequestAnimationFrame();
+
+ const emojiData = getJSONFixture('emojis/emojis.json');
preloadFixtures('snippets/show.html');
- beforeEach(done => {
- loadFixtures('snippets/show.html');
- loadAwardsHandler(true)
- .then(obj => {
- awardsHandler = obj;
- jest.spyOn(awardsHandler, 'postEmoji').mockImplementation((button, url, emoji, cb) => cb());
- done();
- })
- .catch(done.fail);
-
- let isEmojiMenuBuilt = false;
- openAndWaitForEmojiMenu = () => {
- return new Promise(resolve => {
- if (isEmojiMenuBuilt) {
- resolve();
- } else {
- $('.js-add-award')
- .eq(0)
- .click();
- const $menu = $('.emoji-menu');
- $menu.one('build-emoji-menu-finish', () => {
- isEmojiMenuBuilt = true;
- resolve();
- });
- }
+
+ const openAndWaitForEmojiMenu = (sel = '.js-add-award') => {
+ $(sel)
+ .eq(0)
+ .click();
+
+ jest.advanceTimersByTime(200);
+
+ const $menu = $('.emoji-menu');
+
+ return new Promise(resolve => {
+ $menu.one('build-emoji-menu-finish', () => {
+ resolve();
});
- };
+ });
+ };
+
+ beforeEach(async () => {
+ // These tests have had some timeout issues
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/221086
+ setTestTimeout(6000);
+
+ mock = new MockAdapter(axios);
+ mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, emojiData);
+
+ loadFixtures('snippets/show.html');
+
+ awardsHandler = await loadAwardsHandler(true);
+ jest.spyOn(awardsHandler, 'postEmoji').mockImplementation((button, url, emoji, cb) => cb());
});
afterEach(() => {
// restore original url root value
gon.relative_url_root = urlRoot;
+ mock.restore();
+
// Undo what we did to the shared <body>
$('body').removeAttr('data-page');
@@ -65,55 +63,45 @@ describe('AwardsHandler', () => {
});
describe('::showEmojiMenu', () => {
- it('should show emoji menu when Add emoji button clicked', done => {
- $('.js-add-award')
- .eq(0)
- .click();
- lazyAssert(done, () => {
- const $emojiMenu = $('.emoji-menu');
+ it('should show emoji menu when Add emoji button clicked', async () => {
+ await openAndWaitForEmojiMenu();
- expect($emojiMenu.length).toBe(1);
- expect($emojiMenu.hasClass('is-visible')).toBe(true);
- expect($emojiMenu.find('.js-emoji-menu-search').length).toBe(1);
- expect($('.js-awards-block.current').length).toBe(1);
- });
+ const $emojiMenu = $('.emoji-menu');
+
+ expect($emojiMenu.length).toBe(1);
+ expect($emojiMenu.hasClass('is-visible')).toBe(true);
+ expect($emojiMenu.find('.js-emoji-menu-search').length).toBe(1);
+ expect($('.js-awards-block.current').length).toBe(1);
});
- it('should also show emoji menu for the smiley icon in notes', done => {
- $('.js-add-award.note-action-button').click();
- lazyAssert(done, () => {
- const $emojiMenu = $('.emoji-menu');
+ it('should also show emoji menu for the smiley icon in notes', async () => {
+ await openAndWaitForEmojiMenu('.js-add-award.note-action-button');
- expect($emojiMenu.length).toBe(1);
- });
+ const $emojiMenu = $('.emoji-menu');
+
+ expect($emojiMenu.length).toBe(1);
});
- it('should remove emoji menu when body is clicked', done => {
- $('.js-add-award')
- .eq(0)
- .click();
- lazyAssert(done, () => {
- const $emojiMenu = $('.emoji-menu');
- $('body').click();
+ it('should remove emoji menu when body is clicked', async () => {
+ await openAndWaitForEmojiMenu();
- expect($emojiMenu.length).toBe(1);
- expect($emojiMenu.hasClass('is-visible')).toBe(false);
- expect($('.js-awards-block.current').length).toBe(0);
- });
+ const $emojiMenu = $('.emoji-menu');
+ $('body').click();
+
+ expect($emojiMenu.length).toBe(1);
+ expect($emojiMenu.hasClass('is-visible')).toBe(false);
+ expect($('.js-awards-block.current').length).toBe(0);
});
- it('should not remove emoji menu when search is clicked', done => {
- $('.js-add-award')
- .eq(0)
- .click();
- lazyAssert(done, () => {
- const $emojiMenu = $('.emoji-menu');
- $('.emoji-search').click();
+ it('should not remove emoji menu when search is clicked', async () => {
+ await openAndWaitForEmojiMenu();
- expect($emojiMenu.length).toBe(1);
- expect($emojiMenu.hasClass('is-visible')).toBe(true);
- expect($('.js-awards-block.current').length).toBe(1);
- });
+ const $emojiMenu = $('.emoji-menu');
+ $('.emoji-search').click();
+
+ expect($emojiMenu.length).toBe(1);
+ expect($emojiMenu.hasClass('is-visible')).toBe(true);
+ expect($('.js-awards-block.current').length).toBe(1);
});
});
@@ -261,48 +249,39 @@ describe('AwardsHandler', () => {
});
describe('::searchEmojis', () => {
- it('should filter the emoji', done => {
- openAndWaitForEmojiMenu()
- .then(() => {
- expect($('[data-name=angel]').is(':visible')).toBe(true);
- expect($('[data-name=anger]').is(':visible')).toBe(true);
- awardsHandler.searchEmojis('ali');
-
- expect($('[data-name=angel]').is(':visible')).toBe(false);
- expect($('[data-name=anger]').is(':visible')).toBe(false);
- expect($('[data-name=alien]').is(':visible')).toBe(true);
- expect($('.js-emoji-menu-search').val()).toBe('ali');
- })
- .then(done)
- .catch(err => {
- done.fail(`Failed to open and build emoji menu: ${err.message}`);
- });
+ it('should filter the emoji', async () => {
+ await openAndWaitForEmojiMenu();
+
+ expect($('[data-name=angel]').is(':visible')).toBe(true);
+ expect($('[data-name=anger]').is(':visible')).toBe(true);
+ awardsHandler.searchEmojis('ali');
+
+ expect($('[data-name=angel]').is(':visible')).toBe(false);
+ expect($('[data-name=anger]').is(':visible')).toBe(false);
+ expect($('[data-name=alien]').is(':visible')).toBe(true);
+ expect($('.js-emoji-menu-search').val()).toBe('ali');
});
- it('should clear the search when searching for nothing', done => {
- openAndWaitForEmojiMenu()
- .then(() => {
- awardsHandler.searchEmojis('ali');
-
- expect($('[data-name=angel]').is(':visible')).toBe(false);
- expect($('[data-name=anger]').is(':visible')).toBe(false);
- expect($('[data-name=alien]').is(':visible')).toBe(true);
- awardsHandler.searchEmojis('');
-
- expect($('[data-name=angel]').is(':visible')).toBe(true);
- expect($('[data-name=anger]').is(':visible')).toBe(true);
- expect($('[data-name=alien]').is(':visible')).toBe(true);
- expect($('.js-emoji-menu-search').val()).toBe('');
- })
- .then(done)
- .catch(err => {
- done.fail(`Failed to open and build emoji menu: ${err.message}`);
- });
+ it('should clear the search when searching for nothing', async () => {
+ await openAndWaitForEmojiMenu();
+
+ awardsHandler.searchEmojis('ali');
+
+ expect($('[data-name=angel]').is(':visible')).toBe(false);
+ expect($('[data-name=anger]').is(':visible')).toBe(false);
+ expect($('[data-name=alien]').is(':visible')).toBe(true);
+ awardsHandler.searchEmojis('');
+
+ expect($('[data-name=angel]').is(':visible')).toBe(true);
+ expect($('[data-name=anger]').is(':visible')).toBe(true);
+ expect($('[data-name=alien]').is(':visible')).toBe(true);
+ expect($('.js-emoji-menu-search').val()).toBe('');
});
});
describe('emoji menu', () => {
const emojiSelector = '[data-name="sunglasses"]';
+
const openEmojiMenuAndAddEmoji = () => {
return openAndWaitForEmojiMenu().then(() => {
const $menu = $('.emoji-menu');
@@ -318,32 +297,23 @@ describe('AwardsHandler', () => {
});
};
- it('should add selected emoji to awards block', done => {
- openEmojiMenuAndAddEmoji()
- .then(done)
- .catch(err => {
- done.fail(`Failed to open and build emoji menu: ${err.message}`);
- });
+ it('should add selected emoji to awards block', async () => {
+ await openEmojiMenuAndAddEmoji();
});
- it('should remove already selected emoji', done => {
- openEmojiMenuAndAddEmoji()
- .then(() => {
- $('.js-add-award')
- .eq(0)
- .click();
- const $block = $('.js-awards-block');
- const $emoji = $('.emoji-menu').find(
- `.emoji-menu-list:not(.frequent-emojis) ${emojiSelector}`,
- );
- $emoji.click();
-
- expect($block.find(emojiSelector).length).toBe(0);
- })
- .then(done)
- .catch(err => {
- done.fail(`Failed to open and build emoji menu: ${err.message}`);
- });
+ it('should remove already selected emoji', async () => {
+ await openEmojiMenuAndAddEmoji();
+
+ $('.js-add-award')
+ .eq(0)
+ .click();
+ const $block = $('.js-awards-block');
+ const $emoji = $('.emoji-menu').find(
+ `.emoji-menu-list:not(.frequent-emojis) ${emojiSelector}`,
+ );
+ $emoji.click();
+
+ expect($block.find(emojiSelector).length).toBe(0);
});
});
@@ -353,37 +323,27 @@ describe('AwardsHandler', () => {
Cookies.set('frequently_used_emojis', '');
});
- it('shouldn\'t have any "Frequently used" heading if no frequently used emojis', done => {
- return openAndWaitForEmojiMenu()
- .then(() => {
- const emojiMenu = document.querySelector('.emoji-menu');
- Array.prototype.forEach.call(emojiMenu.querySelectorAll('.emoji-menu-title'), title => {
- expect(title.textContent.trim().toLowerCase()).not.toBe('frequently used');
- });
- })
- .then(done)
- .catch(err => {
- done.fail(`Failed to open and build emoji menu: ${err.message}`);
- });
+ it('shouldn\'t have any "Frequently used" heading if no frequently used emojis', async () => {
+ await openAndWaitForEmojiMenu();
+
+ const emojiMenu = document.querySelector('.emoji-menu');
+ Array.prototype.forEach.call(emojiMenu.querySelectorAll('.emoji-menu-title'), title => {
+ expect(title.textContent.trim().toLowerCase()).not.toBe('frequently used');
+ });
});
- it('should have any frequently used section when there are frequently used emojis', done => {
+ it('should have any frequently used section when there are frequently used emojis', async () => {
awardsHandler.addEmojiToFrequentlyUsedList('8ball');
- return openAndWaitForEmojiMenu()
- .then(() => {
- const emojiMenu = document.querySelector('.emoji-menu');
- const hasFrequentlyUsedHeading = Array.prototype.some.call(
- emojiMenu.querySelectorAll('.emoji-menu-title'),
- title => title.textContent.trim().toLowerCase() === 'frequently used',
- );
-
- expect(hasFrequentlyUsedHeading).toBe(true);
- })
- .then(done)
- .catch(err => {
- done.fail(`Failed to open and build emoji menu: ${err.message}`);
- });
+ await openAndWaitForEmojiMenu();
+
+ const emojiMenu = document.querySelector('.emoji-menu');
+ const hasFrequentlyUsedHeading = Array.prototype.some.call(
+ emojiMenu.querySelectorAll('.emoji-menu-title'),
+ title => title.textContent.trim().toLowerCase() === 'frequently used',
+ );
+
+ expect(hasFrequentlyUsedHeading).toBe(true);
});
it('should disregard invalid frequently used emoji that are being attempted to be added', () => {
diff --git a/spec/frontend/batch_comments/stores/modules/batch_comments/actions_spec.js b/spec/frontend/batch_comments/stores/modules/batch_comments/actions_spec.js
index 2ec114d026a..4bac6d4e3dc 100644
--- a/spec/frontend/batch_comments/stores/modules/batch_comments/actions_spec.js
+++ b/spec/frontend/batch_comments/stores/modules/batch_comments/actions_spec.js
@@ -2,6 +2,7 @@ import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import * as actions from '~/batch_comments/stores/modules/batch_comments/actions';
import axios from '~/lib/utils/axios_utils';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('Batch comments store actions', () => {
let res = {};
@@ -33,7 +34,7 @@ describe('Batch comments store actions', () => {
testAction(
actions.addDraftToDiscussion,
- { endpoint: gl.TEST_HOST, data: 'test' },
+ { endpoint: TEST_HOST, data: 'test' },
null,
[{ type: 'ADD_NEW_DRAFT', payload: res }],
[],
@@ -46,7 +47,7 @@ describe('Batch comments store actions', () => {
testAction(
actions.addDraftToDiscussion,
- { endpoint: gl.TEST_HOST, data: 'test' },
+ { endpoint: TEST_HOST, data: 'test' },
null,
[],
[],
@@ -62,7 +63,7 @@ describe('Batch comments store actions', () => {
testAction(
actions.createNewDraft,
- { endpoint: gl.TEST_HOST, data: 'test' },
+ { endpoint: TEST_HOST, data: 'test' },
null,
[{ type: 'ADD_NEW_DRAFT', payload: res }],
[],
@@ -73,14 +74,7 @@ describe('Batch comments store actions', () => {
it('does not commit ADD_NEW_DRAFT if errors returned', done => {
mock.onAny().reply(500);
- testAction(
- actions.createNewDraft,
- { endpoint: gl.TEST_HOST, data: 'test' },
- null,
- [],
- [],
- done,
- );
+ testAction(actions.createNewDraft, { endpoint: TEST_HOST, data: 'test' }, null, [], [], done);
});
});
@@ -90,7 +84,7 @@ describe('Batch comments store actions', () => {
beforeEach(() => {
getters = {
getNotesData: {
- draftsDiscardPath: gl.TEST_HOST,
+ draftsDiscardPath: TEST_HOST,
},
};
});
@@ -137,7 +131,7 @@ describe('Batch comments store actions', () => {
beforeEach(() => {
getters = {
getNotesData: {
- draftsPath: gl.TEST_HOST,
+ draftsPath: TEST_HOST,
},
};
});
@@ -171,7 +165,7 @@ describe('Batch comments store actions', () => {
dispatch = jest.fn();
commit = jest.fn();
getters = {
- getNotesData: { draftsPublishPath: gl.TEST_HOST, discussionsPath: gl.TEST_HOST },
+ getNotesData: { draftsPublishPath: TEST_HOST, discussionsPath: TEST_HOST },
};
rootGetters = { discussionsStructuredByLineCode: 'discussions' };
});
@@ -208,7 +202,7 @@ describe('Batch comments store actions', () => {
describe('discardReview', () => {
it('commits mutations', done => {
const getters = {
- getNotesData: { draftsDiscardPath: gl.TEST_HOST },
+ getNotesData: { draftsDiscardPath: TEST_HOST },
};
const commit = jest.fn();
mock.onAny().reply(200);
@@ -225,7 +219,7 @@ describe('Batch comments store actions', () => {
it('commits error mutations', done => {
const getters = {
- getNotesData: { draftsDiscardPath: gl.TEST_HOST },
+ getNotesData: { draftsDiscardPath: TEST_HOST },
};
const commit = jest.fn();
mock.onAny().reply(500);
@@ -247,7 +241,7 @@ describe('Batch comments store actions', () => {
beforeEach(() => {
getters = {
getNotesData: {
- draftsPath: gl.TEST_HOST,
+ draftsPath: TEST_HOST,
},
};
});
diff --git a/spec/frontend/behaviors/copy_as_gfm_spec.js b/spec/frontend/behaviors/copy_as_gfm_spec.js
index cf96ac488a8..33af9bc135e 100644
--- a/spec/frontend/behaviors/copy_as_gfm_spec.js
+++ b/spec/frontend/behaviors/copy_as_gfm_spec.js
@@ -1,3 +1,4 @@
+import * as commonUtils from '~/lib/utils/common_utils';
import initCopyAsGFM, { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm';
describe('CopyAsGFM', () => {
@@ -27,7 +28,7 @@ describe('CopyAsGFM', () => {
}
it('wraps pasted code when not already in code tags', () => {
- jest.spyOn(window.gl.utils, 'insertText').mockImplementation((el, textFunc) => {
+ jest.spyOn(commonUtils, 'insertText').mockImplementation((el, textFunc) => {
const insertedText = textFunc('This is code: ', '');
expect(insertedText).toEqual('`code`');
@@ -37,7 +38,7 @@ describe('CopyAsGFM', () => {
});
it('does not wrap pasted code when already in code tags', () => {
- jest.spyOn(window.gl.utils, 'insertText').mockImplementation((el, textFunc) => {
+ jest.spyOn(commonUtils, 'insertText').mockImplementation((el, textFunc) => {
const insertedText = textFunc('This is code: `', '`');
expect(insertedText).toEqual('code');
diff --git a/spec/frontend/behaviors/gl_emoji_spec.js b/spec/frontend/behaviors/gl_emoji_spec.js
new file mode 100644
index 00000000000..7ea0bafc328
--- /dev/null
+++ b/spec/frontend/behaviors/gl_emoji_spec.js
@@ -0,0 +1,110 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import { initEmojiMap, EMOJI_VERSION } from '~/emoji';
+import installGlEmojiElement from '~/behaviors/gl_emoji';
+
+import * as EmojiUnicodeSupport from '~/emoji/support';
+import waitForPromises from 'jest/helpers/wait_for_promises';
+
+jest.mock('~/emoji/support');
+
+describe('gl_emoji', () => {
+ let mock;
+ const emojiData = getJSONFixture('emojis/emojis.json');
+
+ beforeAll(() => {
+ jest.spyOn(EmojiUnicodeSupport, 'default').mockReturnValue(true);
+ installGlEmojiElement();
+ });
+
+ function markupToDomElement(markup) {
+ const div = document.createElement('div');
+ div.innerHTML = markup;
+ document.body.appendChild(div);
+
+ return div.firstElementChild;
+ }
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, emojiData);
+
+ return initEmojiMap().catch(() => {});
+ });
+
+ afterEach(() => {
+ mock.restore();
+
+ document.body.innerHTML = '';
+ });
+
+ describe.each([
+ [
+ 'bomb emoji just with name attribute',
+ '<gl-emoji data-name="bomb"></gl-emoji>',
+ '<gl-emoji data-name="bomb" data-unicode-version="6.0" title="bomb">💣</gl-emoji>',
+ '<gl-emoji data-name="bomb" data-unicode-version="6.0" title="bomb"><img class="emoji" title=":bomb:" alt=":bomb:" src="/-/emojis/1/bomb.png" width="20" height="20" align="absmiddle"></gl-emoji>',
+ ],
+ [
+ 'bomb emoji with name attribute and unicode version',
+ '<gl-emoji data-name="bomb" data-unicode-version="6.0">💣</gl-emoji>',
+ '<gl-emoji data-name="bomb" data-unicode-version="6.0">💣</gl-emoji>',
+ '<gl-emoji data-name="bomb" data-unicode-version="6.0"><img class="emoji" title=":bomb:" alt=":bomb:" src="/-/emojis/1/bomb.png" width="20" height="20" align="absmiddle"></gl-emoji>',
+ ],
+ [
+ 'bomb emoji with sprite fallback',
+ '<gl-emoji data-fallback-sprite-class="emoji-bomb" data-name="bomb"></gl-emoji>',
+ '<gl-emoji data-fallback-sprite-class="emoji-bomb" data-name="bomb" data-unicode-version="6.0" title="bomb">💣</gl-emoji>',
+ '<gl-emoji data-fallback-sprite-class="emoji-bomb" data-name="bomb" data-unicode-version="6.0" title="bomb" class="emoji-icon emoji-bomb">💣</gl-emoji>',
+ ],
+ [
+ 'bomb emoji with image fallback',
+ '<gl-emoji data-fallback-src="/bomb.png" data-name="bomb"></gl-emoji>',
+ '<gl-emoji data-fallback-src="/bomb.png" data-name="bomb" data-unicode-version="6.0" title="bomb">💣</gl-emoji>',
+ '<gl-emoji data-fallback-src="/bomb.png" data-name="bomb" data-unicode-version="6.0" title="bomb"><img class="emoji" title=":bomb:" alt=":bomb:" src="/bomb.png" width="20" height="20" align="absmiddle"></gl-emoji>',
+ ],
+ [
+ 'invalid emoji',
+ '<gl-emoji data-name="invalid_emoji"></gl-emoji>',
+ '<gl-emoji data-name="grey_question" data-unicode-version="6.0" title="white question mark ornament">❔</gl-emoji>',
+ '<gl-emoji data-name="grey_question" data-unicode-version="6.0" title="white question mark ornament"><img class="emoji" title=":grey_question:" alt=":grey_question:" src="/-/emojis/1/grey_question.png" width="20" height="20" align="absmiddle"></gl-emoji>',
+ ],
+ ])('%s', (name, markup, withEmojiSupport, withoutEmojiSupport) => {
+ it(`renders correctly with emoji support`, async () => {
+ jest.spyOn(EmojiUnicodeSupport, 'default').mockReturnValue(true);
+ const glEmojiElement = markupToDomElement(markup);
+
+ await waitForPromises();
+
+ expect(glEmojiElement.outerHTML).toBe(withEmojiSupport);
+ });
+
+ it(`renders correctly without emoji support`, async () => {
+ jest.spyOn(EmojiUnicodeSupport, 'default').mockReturnValue(false);
+ const glEmojiElement = markupToDomElement(markup);
+
+ await waitForPromises();
+
+ expect(glEmojiElement.outerHTML).toBe(withoutEmojiSupport);
+ });
+ });
+
+ it('Adds sprite CSS if emojis are not supported', async () => {
+ const testPath = '/test-path.css';
+ jest.spyOn(EmojiUnicodeSupport, 'default').mockReturnValue(false);
+ window.gon.emoji_sprites_css_path = testPath;
+
+ expect(document.head.querySelector(`link[href="${testPath}"]`)).toBe(null);
+ expect(window.gon.emoji_sprites_css_added).toBeFalsy();
+
+ markupToDomElement(
+ '<gl-emoji data-fallback-sprite-class="emoji-bomb" data-name="bomb"></gl-emoji>',
+ );
+ await waitForPromises();
+
+ expect(document.head.querySelector(`link[href="${testPath}"]`).outerHTML).toBe(
+ '<link rel="stylesheet" href="/test-path.css">',
+ );
+ expect(window.gon.emoji_sprites_css_added).toBe(true);
+ });
+});
diff --git a/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js b/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js
index 6391a544985..baedbf5771a 100644
--- a/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js
+++ b/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js
@@ -46,7 +46,7 @@ describe('ShortcutsIssuable', () => {
});
describe('replyWithSelectedText', () => {
- // Stub window.gl.utils.getSelectedFragment to return a node with the provided HTML.
+ // Stub getSelectedFragment to return a node with the provided HTML.
const stubSelection = (html, invalidNode) => {
getSelectedFragment.mockImplementation(() => {
const documentFragment = document.createDocumentFragment();
diff --git a/spec/frontend/blob/components/__snapshots__/blob_header_filepath_spec.js.snap b/spec/frontend/blob/components/__snapshots__/blob_header_filepath_spec.js.snap
index 005b2c5da1c..0f5b3cd3f5e 100644
--- a/spec/frontend/blob/components/__snapshots__/blob_header_filepath_spec.js.snap
+++ b/spec/frontend/blob/components/__snapshots__/blob_header_filepath_spec.js.snap
@@ -8,6 +8,7 @@ exports[`Blob Header Filepath rendering matches the snapshot 1`] = `
<file-icon-stub
aria-hidden="true"
cssclasses="mr-2"
+ filemode=""
filename="foo/bar/dummy.md"
size="18"
/>
diff --git a/spec/frontend/blob/components/blob_content_error_spec.js b/spec/frontend/blob/components/blob_content_error_spec.js
index 6eb5cfb71aa..508b1ed7e68 100644
--- a/spec/frontend/blob/components/blob_content_error_spec.js
+++ b/spec/frontend/blob/components/blob_content_error_spec.js
@@ -24,9 +24,9 @@ describe('Blob Content Error component', () => {
describe('collapsed and too large blobs', () => {
it.each`
- error | reason | options
- ${BLOB_RENDER_ERRORS.REASONS.COLLAPSED} | ${'it is larger than 1.00 MiB'} | ${[BLOB_RENDER_ERRORS.OPTIONS.LOAD.text, BLOB_RENDER_ERRORS.OPTIONS.DOWNLOAD.text]}
- ${BLOB_RENDER_ERRORS.REASONS.TOO_LARGE} | ${'it is larger than 100.00 MiB'} | ${[BLOB_RENDER_ERRORS.OPTIONS.DOWNLOAD.text]}
+ error | reason | options
+ ${BLOB_RENDER_ERRORS.REASONS.COLLAPSED} | ${'it is larger than 1.00 MiB'} | ${[BLOB_RENDER_ERRORS.OPTIONS.LOAD.text, BLOB_RENDER_ERRORS.OPTIONS.DOWNLOAD.text]}
+ ${BLOB_RENDER_ERRORS.REASONS.TOO_LARGE} | ${'it is larger than 10.00 MiB'} | ${[BLOB_RENDER_ERRORS.OPTIONS.DOWNLOAD.text]}
`('renders correct reason for $error.id', ({ error, reason, options }) => {
createComponent({
viewerError: error.id,
diff --git a/spec/frontend/blob/components/blob_header_default_actions_spec.js b/spec/frontend/blob/components/blob_header_default_actions_spec.js
index 0247a12d8d3..529e7cc85f5 100644
--- a/spec/frontend/blob/components/blob_header_default_actions_spec.js
+++ b/spec/frontend/blob/components/blob_header_default_actions_spec.js
@@ -13,7 +13,6 @@ describe('Blob Header Default Actions', () => {
let wrapper;
let btnGroup;
let buttons;
- const hrefPrefix = 'http://localhost';
function createComponent(propsData = {}) {
wrapper = mount(BlobHeaderActions, {
@@ -47,11 +46,11 @@ describe('Blob Header Default Actions', () => {
});
it('correct href attribute on RAW button', () => {
- expect(buttons.at(1).vm.$el.href).toBe(`${hrefPrefix}${Blob.rawPath}`);
+ expect(buttons.at(1).attributes('href')).toBe(Blob.rawPath);
});
it('correct href attribute on Download button', () => {
- expect(buttons.at(2).vm.$el.href).toBe(`${hrefPrefix}${Blob.rawPath}?inline=false`);
+ expect(buttons.at(2).attributes('href')).toBe(`${Blob.rawPath}?inline=false`);
});
it('does not render "Copy file contents" button as disables if the viewer is Simple', () => {
diff --git a/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js b/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js
index 0f34d6419d3..f1a7ac8b21a 100644
--- a/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js
+++ b/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js
@@ -6,7 +6,7 @@ import {
SIMPLE_BLOB_VIEWER,
SIMPLE_BLOB_VIEWER_TITLE,
} from '~/blob/components/constants';
-import { GlButtonGroup, GlDeprecatedButton } from '@gitlab/ui';
+import { GlButtonGroup, GlButton } from '@gitlab/ui';
describe('Blob Header Viewer Switcher', () => {
let wrapper;
@@ -35,7 +35,7 @@ describe('Blob Header Viewer Switcher', () => {
beforeEach(() => {
createComponent();
btnGroup = wrapper.find(GlButtonGroup);
- buttons = wrapper.findAll(GlDeprecatedButton);
+ buttons = wrapper.findAll(GlButton);
});
it('renders gl-button-group component', () => {
@@ -57,7 +57,7 @@ describe('Blob Header Viewer Switcher', () => {
function factory(propsData = {}) {
createComponent(propsData);
- buttons = wrapper.findAll(GlDeprecatedButton);
+ buttons = wrapper.findAll(GlButton);
simpleBtn = buttons.at(0);
richBtn = buttons.at(1);
diff --git a/spec/frontend/blob/components/mock_data.js b/spec/frontend/blob/components/mock_data.js
index 0f7193846ff..58aa1dc6dc9 100644
--- a/spec/frontend/blob/components/mock_data.js
+++ b/spec/frontend/blob/components/mock_data.js
@@ -32,6 +32,20 @@ export const Blob = {
},
};
+export const BinaryBlob = {
+ binary: true,
+ name: 'dummy.png',
+ path: 'foo/bar/dummy.png',
+ rawPath: '/flightjs/flight/snippets/51/raw',
+ size: 75,
+ simpleViewer: {
+ ...SimpleViewerMock,
+ },
+ richViewer: {
+ ...RichViewerMock,
+ },
+};
+
export const RichBlobContentMock = {
richData: '<h1>Rich</h1>',
};
diff --git a/spec/frontend/blob_edit/edit_blob_spec.js b/spec/frontend/blob_edit/edit_blob_spec.js
new file mode 100644
index 00000000000..9642b55b9b4
--- /dev/null
+++ b/spec/frontend/blob_edit/edit_blob_spec.js
@@ -0,0 +1,31 @@
+import EditBlob from '~/blob_edit/edit_blob';
+import EditorLite from '~/editor/editor_lite';
+import MarkdownExtension from '~/editor/editor_markdown_ext';
+
+jest.mock('~/editor/editor_lite');
+jest.mock('~/editor/editor_markdown_ext');
+
+describe('Blob Editing', () => {
+ beforeEach(() => {
+ setFixtures(
+ `<div class="js-edit-blob-form"><div id="file_path"></div><div id="iditor"></div><input id="file-content"></div>`,
+ );
+ });
+
+ const initEditor = (isMarkdown = false) => {
+ return new EditBlob({
+ isMarkdown,
+ monacoEnabled: true,
+ });
+ };
+
+ it('does not load MarkdownExtension by default', async () => {
+ await initEditor();
+ expect(EditorLite.prototype.use).not.toHaveBeenCalled();
+ });
+
+ it('loads MarkdownExtension only for the markdown files', async () => {
+ await initEditor(true);
+ expect(EditorLite.prototype.use).toHaveBeenCalledWith(MarkdownExtension);
+ });
+});
diff --git a/spec/frontend/boards/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js
index ee427bc2154..94f607698d7 100644
--- a/spec/frontend/boards/components/board_form_spec.js
+++ b/spec/frontend/boards/components/board_form_spec.js
@@ -3,14 +3,15 @@ import { mount } from '@vue/test-utils';
import boardsStore from '~/boards/stores/boards_store';
import boardForm from '~/boards/components/board_form.vue';
import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('board_form.vue', () => {
let wrapper;
const propsData = {
canAdminBoard: false,
- labelsPath: `${gl.TEST_HOST}/labels/path`,
- milestonePath: `${gl.TEST_HOST}/milestone/path`,
+ labelsPath: `${TEST_HOST}/labels/path`,
+ milestonePath: `${TEST_HOST}/milestone/path`,
};
const findModal = () => wrapper.find(DeprecatedModal);
diff --git a/spec/frontend/boards/issue_card_spec.js b/spec/frontend/boards/issue_card_spec.js
index 09b5c664bee..15750a161ae 100644
--- a/spec/frontend/boards/issue_card_spec.js
+++ b/spec/frontend/boards/issue_card_spec.js
@@ -6,7 +6,7 @@ import '~/boards/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
import IssueCardInner from '~/boards/components/issue_card_inner.vue';
-import { listObj } from '../../javascripts/boards/mock_data';
+import { listObj } from './mock_data';
import store from '~/boards/stores';
import { GlLabel } from '@gitlab/ui';
diff --git a/spec/frontend/ci_variable_list/components/ci_key_field_spec.js b/spec/frontend/ci_variable_list/components/ci_key_field_spec.js
deleted file mode 100644
index bcc29f22dd1..00000000000
--- a/spec/frontend/ci_variable_list/components/ci_key_field_spec.js
+++ /dev/null
@@ -1,244 +0,0 @@
-import { mount } from '@vue/test-utils';
-import { GlButton, GlFormInput } from '@gitlab/ui';
-import { AWS_ACCESS_KEY_ID, AWS_DEFAULT_REGION } from '~/ci_variable_list/constants';
-import CiKeyField from '~/ci_variable_list/components/ci_key_field.vue';
-
-import {
- awsTokens,
- awsTokenList,
-} from '~/ci_variable_list/components/ci_variable_autocomplete_tokens';
-
-const doTimes = (num, fn) => {
- for (let i = 0; i < num; i += 1) {
- fn();
- }
-};
-
-describe('Ci Key field', () => {
- let wrapper;
-
- const createComponent = () => {
- wrapper = mount({
- data() {
- return {
- inputVal: '',
- tokens: awsTokenList,
- };
- },
- components: { CiKeyField },
- template: `
- <div>
- <ci-key-field
- v-model="inputVal"
- :token-list="tokens"
- />
- </div>
- `,
- });
- };
-
- const findDropdown = () => wrapper.find('#ci-variable-dropdown');
- const findDropdownOptions = () => wrapper.findAll(GlButton).wrappers.map(item => item.text());
- const findInput = () => wrapper.find(GlFormInput);
- const findInputValue = () => findInput().element.value;
- const setInput = val => findInput().setValue(val);
- const clickDown = () => findInput().trigger('keydown.down');
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('match and filter functionality', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('is closed when the input is empty', () => {
- expect(findInput().isVisible()).toBe(true);
- expect(findInputValue()).toBe('');
- expect(findDropdown().isVisible()).toBe(false);
- });
-
- it('is open when the input text matches a token', () => {
- setInput('AWS');
- return wrapper.vm.$nextTick().then(() => {
- expect(findDropdown().isVisible()).toBe(true);
- });
- });
-
- it('shows partial matches at string start', () => {
- setInput('AWS');
- return wrapper.vm.$nextTick().then(() => {
- expect(findDropdown().isVisible()).toBe(true);
- expect(findDropdownOptions()).toEqual(awsTokenList);
- });
- });
-
- it('shows partial matches mid-string', () => {
- setInput('D');
- return wrapper.vm.$nextTick().then(() => {
- expect(findDropdown().isVisible()).toBe(true);
- expect(findDropdownOptions()).toEqual([
- awsTokens[AWS_ACCESS_KEY_ID].name,
- awsTokens[AWS_DEFAULT_REGION].name,
- ]);
- });
- });
-
- it('is closed when the text does not match', () => {
- setInput('elephant');
- return wrapper.vm.$nextTick().then(() => {
- expect(findDropdown().isVisible()).toBe(false);
- });
- });
- });
-
- describe('keyboard navigation in dropdown', () => {
- beforeEach(() => {
- createComponent();
- });
-
- describe('on down arrow + enter', () => {
- it('selects the next item in the list and closes the dropdown', () => {
- setInput('AWS');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- findInput().trigger('keydown.down');
- findInput().trigger('keydown.enter');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findInputValue()).toBe(awsTokenList[0]);
- });
- });
-
- it('loops to the top when it reaches the bottom', () => {
- setInput('AWS');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- doTimes(findDropdownOptions().length + 1, clickDown);
- findInput().trigger('keydown.enter');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findInputValue()).toBe(awsTokenList[0]);
- });
- });
- });
-
- describe('on up arrow + enter', () => {
- it('selects the previous item in the list and closes the dropdown', () => {
- setInput('AWS');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- doTimes(3, clickDown);
- findInput().trigger('keydown.up');
- findInput().trigger('keydown.enter');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findInputValue()).toBe(awsTokenList[1]);
- });
- });
-
- it('loops to the bottom when it reaches the top', () => {
- setInput('AWS');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- findInput().trigger('keydown.down');
- findInput().trigger('keydown.up');
- findInput().trigger('keydown.enter');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findInputValue()).toBe(awsTokenList[awsTokenList.length - 1]);
- });
- });
- });
-
- describe('on enter with no item highlighted', () => {
- it('does not select any item and closes the dropdown', () => {
- setInput('AWS');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- findInput().trigger('keydown.enter');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findInputValue()).toBe('AWS');
- });
- });
- });
-
- describe('on click', () => {
- it('selects the clicked item regardless of arrow highlight', () => {
- setInput('AWS');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- wrapper.find(GlButton).trigger('click');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findInputValue()).toBe(awsTokenList[0]);
- });
- });
- });
-
- describe('on tab', () => {
- it('selects entered text, closes dropdown', () => {
- setInput('AWS');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- findInput().trigger('keydown.tab');
- doTimes(2, clickDown);
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findInputValue()).toBe('AWS');
- expect(findDropdown().isVisible()).toBe(false);
- });
- });
- });
-
- describe('on esc', () => {
- describe('when dropdown is open', () => {
- it('closes dropdown and does not select anything', () => {
- setInput('AWS');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- findInput().trigger('keydown.esc');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findInputValue()).toBe('AWS');
- expect(findDropdown().isVisible()).toBe(false);
- });
- });
- });
-
- describe('when dropdown is closed', () => {
- it('clears the input field', () => {
- setInput('elephant');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- expect(findDropdown().isVisible()).toBe(false);
- findInput().trigger('keydown.esc');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findInputValue()).toBe('');
- });
- });
- });
- });
- });
-});
diff --git a/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js
index 094fdcdc185..ad398d6ccd6 100644
--- a/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js
+++ b/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js
@@ -1,10 +1,8 @@
import Vuex from 'vuex';
import { createLocalVue, shallowMount, mount } from '@vue/test-utils';
-import { GlDeprecatedButton } from '@gitlab/ui';
+import { GlDeprecatedButton, GlFormCombobox } from '@gitlab/ui';
import { AWS_ACCESS_KEY_ID } from '~/ci_variable_list/constants';
import CiVariableModal from '~/ci_variable_list/components/ci_variable_modal.vue';
-import CiKeyField from '~/ci_variable_list/components/ci_key_field.vue';
-import { awsTokens } from '~/ci_variable_list/components/ci_variable_autocomplete_tokens';
import createStore from '~/ci_variable_list/store';
import mockData from '../services/mock_data';
import ModalStub from '../stubs';
@@ -51,7 +49,7 @@ describe('Ci variable modal', () => {
});
it('does not render the autocomplete dropdown', () => {
- expect(wrapper.contains(CiKeyField)).toBe(false);
+ expect(wrapper.contains(GlFormCombobox)).toBe(false);
});
});
@@ -60,7 +58,7 @@ describe('Ci variable modal', () => {
createComponent(shallowMount);
});
it('renders the autocomplete dropdown', () => {
- expect(wrapper.find(CiKeyField).exists()).toBe(true);
+ expect(wrapper.find(GlFormCombobox).exists()).toBe(true);
});
});
});
@@ -159,10 +157,7 @@ describe('Ci variable modal', () => {
it('Update variable button dispatches updateVariable with correct variable', () => {
addOrUpdateButton(2).vm.$emit('click');
- expect(store.dispatch).toHaveBeenCalledWith(
- 'updateVariable',
- store.state.variableBeingEdited,
- );
+ expect(store.dispatch).toHaveBeenCalledWith('updateVariable');
});
it('Resets the editing state once modal is hidden', () => {
@@ -172,36 +167,13 @@ describe('Ci variable modal', () => {
it('dispatches deleteVariable with correct variable to delete', () => {
deleteVariableButton().vm.$emit('click');
- expect(store.dispatch).toHaveBeenCalledWith('deleteVariable', mockData.mockVariables[0]);
+ expect(store.dispatch).toHaveBeenCalledWith('deleteVariable');
});
});
describe('Validations', () => {
const maskError = 'This variable can not be masked.';
- describe('when the key state is invalid', () => {
- beforeEach(() => {
- const [variable] = mockData.mockVariables;
- const invalidKeyVariable = {
- ...variable,
- key: AWS_ACCESS_KEY_ID,
- value: 'AKIAIOSFODNN7EXAMPLEjdhy',
- secret_value: 'AKIAIOSFODNN7EXAMPLEjdhy',
- };
- createComponent(mount);
- store.state.variable = invalidKeyVariable;
- });
-
- it('disables the submit button', () => {
- expect(addOrUpdateButton(1).attributes('disabled')).toBeTruthy();
- });
-
- it('shows the correct error text', () => {
- const errorText = awsTokens[AWS_ACCESS_KEY_ID].invalidMessage;
- expect(findModal().text()).toContain(errorText);
- });
- });
-
describe('when the mask state is invalid', () => {
beforeEach(() => {
const [variable] = mockData.mockVariables;
@@ -225,39 +197,14 @@ describe('Ci variable modal', () => {
});
});
- describe('when the mask and key states are invalid', () => {
- beforeEach(() => {
- const [variable] = mockData.mockVariables;
- const invalidMaskandKeyVariable = {
- ...variable,
- key: AWS_ACCESS_KEY_ID,
- value: 'AKIAIOSFODNN7EXAMPLEjdhyd:;',
- secret_value: 'AKIAIOSFODNN7EXAMPLEjdhyd:;',
- masked: true,
- };
- createComponent(mount);
- store.state.variable = invalidMaskandKeyVariable;
- });
-
- it('disables the submit button', () => {
- expect(addOrUpdateButton(1).attributes('disabled')).toBeTruthy();
- });
-
- it('shows the correct error text', () => {
- const errorText = awsTokens[AWS_ACCESS_KEY_ID].invalidMessage;
- expect(findModal().text()).toContain(maskError);
- expect(findModal().text()).toContain(errorText);
- });
- });
-
describe('when both states are valid', () => {
beforeEach(() => {
const [variable] = mockData.mockVariables;
const validMaskandKeyVariable = {
...variable,
key: AWS_ACCESS_KEY_ID,
- value: 'AKIAIOSFODNN7EXAMPLE',
- secret_value: 'AKIAIOSFODNN7EXAMPLE',
+ value: '12345678',
+ secret_value: '87654321',
masked: true,
};
createComponent(mount);
@@ -268,12 +215,6 @@ describe('Ci variable modal', () => {
it('does not disable the submit button', () => {
expect(addOrUpdateButton(1).attributes('disabled')).toBeFalsy();
});
-
- it('shows no error text', () => {
- const errorText = awsTokens[AWS_ACCESS_KEY_ID].invalidMessage;
- expect(findModal().text()).not.toContain(maskError);
- expect(findModal().text()).not.toContain(errorText);
- });
});
});
});
diff --git a/spec/frontend/ci_variable_list/services/mock_data.js b/spec/frontend/ci_variable_list/services/mock_data.js
index 7dab33050d9..44f4db93c63 100644
--- a/spec/frontend/ci_variable_list/services/mock_data.js
+++ b/spec/frontend/ci_variable_list/services/mock_data.js
@@ -42,6 +42,7 @@ export default {
key: 'test_var',
masked: false,
protected: false,
+ protected_variable: false,
secret_value: 'test_val',
value: 'test_val',
variable_type: 'Variable',
@@ -52,6 +53,7 @@ export default {
key: 'test_var_2',
masked: false,
protected: false,
+ protected_variable: false,
secret_value: 'test_val_2',
value: 'test_val_2',
variable_type: 'File',
diff --git a/spec/frontend/ci_variable_list/store/actions_spec.js b/spec/frontend/ci_variable_list/store/actions_spec.js
index 12b4311d0f5..eb565d4c979 100644
--- a/spec/frontend/ci_variable_list/store/actions_spec.js
+++ b/spec/frontend/ci_variable_list/store/actions_spec.js
@@ -91,7 +91,7 @@ describe('CI variable list store actions', () => {
testAction(
actions.deleteVariable,
- mockVariable,
+ {},
state,
[],
[
@@ -110,7 +110,7 @@ describe('CI variable list store actions', () => {
testAction(
actions.deleteVariable,
- mockVariable,
+ {},
state,
[],
[
@@ -134,7 +134,7 @@ describe('CI variable list store actions', () => {
testAction(
actions.updateVariable,
- mockVariable,
+ {},
state,
[],
[
@@ -286,4 +286,66 @@ describe('CI variable list store actions', () => {
);
});
});
+
+ describe('Update variable values', () => {
+ it('updateVariableKey', () => {
+ testAction(
+ actions.updateVariableKey,
+ { key: mockVariable.key },
+ {},
+ [
+ {
+ type: types.UPDATE_VARIABLE_KEY,
+ payload: mockVariable.key,
+ },
+ ],
+ [],
+ );
+ });
+
+ it('updateVariableValue', () => {
+ testAction(
+ actions.updateVariableValue,
+ { secret_value: mockVariable.value },
+ {},
+ [
+ {
+ type: types.UPDATE_VARIABLE_VALUE,
+ payload: mockVariable.value,
+ },
+ ],
+ [],
+ );
+ });
+
+ it('updateVariableType', () => {
+ testAction(
+ actions.updateVariableType,
+ { variable_type: mockVariable.variable_type },
+ {},
+ [{ type: types.UPDATE_VARIABLE_TYPE, payload: mockVariable.variable_type }],
+ [],
+ );
+ });
+
+ it('updateVariableProtected', () => {
+ testAction(
+ actions.updateVariableProtected,
+ { protected_variable: mockVariable.protected },
+ {},
+ [{ type: types.UPDATE_VARIABLE_PROTECTED, payload: mockVariable.protected }],
+ [],
+ );
+ });
+
+ it('updateVariableMasked', () => {
+ testAction(
+ actions.updateVariableMasked,
+ { masked: mockVariable.masked },
+ {},
+ [{ type: types.UPDATE_VARIABLE_MASKED, payload: mockVariable.masked }],
+ [],
+ );
+ });
+ });
});
diff --git a/spec/frontend/ci_variable_list/store/mutations_spec.js b/spec/frontend/ci_variable_list/store/mutations_spec.js
index 1934d108957..663b3486a17 100644
--- a/spec/frontend/ci_variable_list/store/mutations_spec.js
+++ b/spec/frontend/ci_variable_list/store/mutations_spec.js
@@ -4,15 +4,6 @@ import * as types from '~/ci_variable_list/store/mutation_types';
describe('CI variable list mutations', () => {
let stateCopy;
- const variableBeingEdited = {
- environment_scope: '*',
- id: 63,
- key: 'test_var',
- masked: false,
- protected: false,
- value: 'test_val',
- variable_type: 'env_var',
- };
beforeEach(() => {
stateCopy = state();
@@ -29,18 +20,18 @@ describe('CI variable list mutations', () => {
});
describe('VARIABLE_BEING_EDITED', () => {
- it('should set variable that is being edited', () => {
- mutations[types.VARIABLE_BEING_EDITED](stateCopy, variableBeingEdited);
+ it('should set the variable that is being edited', () => {
+ mutations[types.VARIABLE_BEING_EDITED](stateCopy);
- expect(stateCopy.variableBeingEdited).toEqual(variableBeingEdited);
+ expect(stateCopy.variableBeingEdited).toBe(true);
});
});
describe('RESET_EDITING', () => {
- it('should reset variableBeingEdited to null', () => {
+ it('should reset variableBeingEdited to false', () => {
mutations[types.RESET_EDITING](stateCopy);
- expect(stateCopy.variableBeingEdited).toEqual(null);
+ expect(stateCopy.variableBeingEdited).toBe(false);
});
});
@@ -50,7 +41,7 @@ describe('CI variable list mutations', () => {
variable_type: 'Variable',
key: '',
secret_value: '',
- protected: false,
+ protected_variable: false,
masked: false,
environment_scope: 'All (default)',
};
@@ -74,15 +65,7 @@ describe('CI variable list mutations', () => {
describe('SET_ENVIRONMENT_SCOPE', () => {
const environment = 'production';
- it('should set scope to variable being updated if updating variable', () => {
- stateCopy.variableBeingEdited = variableBeingEdited;
-
- mutations[types.SET_ENVIRONMENT_SCOPE](stateCopy, environment);
-
- expect(stateCopy.variableBeingEdited.environment_scope).toBe('production');
- });
-
- it('should set scope to variable if adding new variable', () => {
+ it('should set environment scope on variable', () => {
mutations[types.SET_ENVIRONMENT_SCOPE](stateCopy, environment);
expect(stateCopy.variable.environment_scope).toBe('production');
@@ -102,7 +85,52 @@ describe('CI variable list mutations', () => {
it('should set protected value to true', () => {
mutations[types.SET_VARIABLE_PROTECTED](stateCopy);
- expect(stateCopy.variable.protected).toBe(true);
+ expect(stateCopy.variable.protected_variable).toBe(true);
+ });
+ });
+
+ describe('UPDATE_VARIABLE_KEY', () => {
+ it('should update variable key value', () => {
+ const key = 'new_var';
+ mutations[types.UPDATE_VARIABLE_KEY](stateCopy, key);
+
+ expect(stateCopy.variable.key).toBe(key);
+ });
+ });
+
+ describe('UPDATE_VARIABLE_VALUE', () => {
+ it('should update variable value', () => {
+ const value = 'variable_value';
+ mutations[types.UPDATE_VARIABLE_VALUE](stateCopy, value);
+
+ expect(stateCopy.variable.secret_value).toBe(value);
+ });
+ });
+
+ describe('UPDATE_VARIABLE_TYPE', () => {
+ it('should update variable type value', () => {
+ const type = 'File';
+ mutations[types.UPDATE_VARIABLE_TYPE](stateCopy, type);
+
+ expect(stateCopy.variable.variable_type).toBe(type);
+ });
+ });
+
+ describe('UPDATE_VARIABLE_PROTECTED', () => {
+ it('should update variable protected value', () => {
+ const protectedValue = true;
+ mutations[types.UPDATE_VARIABLE_PROTECTED](stateCopy, protectedValue);
+
+ expect(stateCopy.variable.protected_variable).toBe(protectedValue);
+ });
+ });
+
+ describe('UPDATE_VARIABLE_MASKED', () => {
+ it('should update variable masked value', () => {
+ const masked = true;
+ mutations[types.UPDATE_VARIABLE_MASKED](stateCopy, masked);
+
+ expect(stateCopy.variable.masked).toBe(masked);
});
});
});
diff --git a/spec/frontend/close_reopen_report_toggle_spec.js b/spec/frontend/close_reopen_report_toggle_spec.js
index f6b5e4bed87..d2ce6298c5c 100644
--- a/spec/frontend/close_reopen_report_toggle_spec.js
+++ b/spec/frontend/close_reopen_report_toggle_spec.js
@@ -274,12 +274,7 @@ describe('CloseReopenReportToggle', () => {
{
input: button,
valueAttribute: 'data-url',
- inputAttribute: 'href',
- },
- {
- input: button,
- valueAttribute: 'data-method',
- inputAttribute: 'data-method',
+ inputAttribute: 'data-endpoint',
},
],
});
diff --git a/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js b/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js
index 091d4e07987..f448948843a 100644
--- a/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js
+++ b/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js
@@ -28,7 +28,7 @@ describe('Remove cluster confirmation modal', () => {
describe('split button dropdown', () => {
const findModal = () => wrapper.find(GlModal).vm;
- const findSplitButton = () => wrapper.find(SplitButton).vm;
+ const findSplitButton = () => wrapper.find(SplitButton);
beforeEach(() => {
createComponent({ clusterName: 'my-test-cluster' });
@@ -36,7 +36,7 @@ describe('Remove cluster confirmation modal', () => {
});
it('opens modal with "cleanup" option', () => {
- findSplitButton().$emit('remove-cluster-and-cleanup');
+ findSplitButton().vm.$emit('remove-cluster-and-cleanup');
return wrapper.vm.$nextTick().then(() => {
expect(findModal().show).toHaveBeenCalled();
@@ -45,12 +45,23 @@ describe('Remove cluster confirmation modal', () => {
});
it('opens modal without "cleanup" option', () => {
- findSplitButton().$emit('remove-cluster');
+ findSplitButton().vm.$emit('remove-cluster');
return wrapper.vm.$nextTick().then(() => {
expect(findModal().show).toHaveBeenCalled();
expect(wrapper.vm.confirmCleanup).toEqual(false);
});
});
+
+ describe('with cluster management project', () => {
+ beforeEach(() => {
+ createComponent({ hasManagementProject: true });
+ });
+
+ it('renders regular button instead', () => {
+ expect(findSplitButton().exists()).toBe(false);
+ expect(wrapper.find('[data-testid="btnRemove"]').exists()).toBe(true);
+ });
+ });
});
});
diff --git a/spec/frontend/clusters_list/components/ancestor_notice_spec.js b/spec/frontend/clusters_list/components/ancestor_notice_spec.js
new file mode 100644
index 00000000000..c931912eaf9
--- /dev/null
+++ b/spec/frontend/clusters_list/components/ancestor_notice_spec.js
@@ -0,0 +1,51 @@
+import AncestorNotice from '~/clusters_list/components/ancestor_notice.vue';
+import ClusterStore from '~/clusters_list/store';
+import { shallowMount } from '@vue/test-utils';
+import { GlLink, GlSprintf } from '@gitlab/ui';
+
+describe('ClustersAncestorNotice', () => {
+ let store;
+ let wrapper;
+
+ const createWrapper = () => {
+ store = ClusterStore({ ancestorHelperPath: '/some/ancestor/path' });
+ wrapper = shallowMount(AncestorNotice, { store, stubs: { GlSprintf } });
+ return wrapper.vm.$nextTick();
+ };
+
+ beforeEach(() => {
+ return createWrapper();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when cluster does not have ancestors', () => {
+ beforeEach(() => {
+ store.state.hasAncestorClusters = false;
+ return wrapper.vm.$nextTick();
+ });
+
+ it('displays no notice', () => {
+ expect(wrapper.isEmpty()).toBe(true);
+ });
+ });
+
+ describe('when cluster has ancestors', () => {
+ beforeEach(() => {
+ store.state.hasAncestorClusters = true;
+ return wrapper.vm.$nextTick();
+ });
+
+ it('displays notice text', () => {
+ expect(wrapper.text()).toContain(
+ 'Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters.',
+ );
+ });
+
+ it('displays link', () => {
+ expect(wrapper.contains(GlLink)).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/clusters_list/components/clusters_spec.js b/spec/frontend/clusters_list/components/clusters_spec.js
index 07faee7e50b..deb275a9bb9 100644
--- a/spec/frontend/clusters_list/components/clusters_spec.js
+++ b/spec/frontend/clusters_list/components/clusters_spec.js
@@ -4,7 +4,7 @@ import ClusterStore from '~/clusters_list/store';
import MockAdapter from 'axios-mock-adapter';
import { apiData } from '../mock_data';
import { mount } from '@vue/test-utils';
-import { GlLoadingIcon, GlTable, GlPagination } from '@gitlab/ui';
+import { GlLoadingIcon, GlPagination, GlSkeletonLoading, GlTable } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
describe('Clusters', () => {
@@ -64,7 +64,7 @@ describe('Clusters', () => {
describe('clusters table', () => {
describe('when data is loading', () => {
beforeEach(() => {
- wrapper.vm.$store.state.loading = true;
+ wrapper.vm.$store.state.loadingClusters = true;
return wrapper.vm.$nextTick();
});
@@ -131,19 +131,48 @@ describe('Clusters', () => {
});
describe('nodes present', () => {
- it.each`
- nodeSize | lineNumber
- ${'Unknown'} | ${0}
- ${'1'} | ${1}
- ${'2'} | ${2}
- ${'1'} | ${3}
- ${'1'} | ${4}
- ${'Unknown'} | ${5}
- `('renders node size for each cluster', ({ nodeSize, lineNumber }) => {
- const sizes = findTable().findAll('td:nth-child(3)');
- const size = sizes.at(lineNumber);
-
- expect(size.text()).toBe(nodeSize);
+ describe('nodes while loading', () => {
+ it.each`
+ nodeSize | lineNumber
+ ${null} | ${0}
+ ${'1'} | ${1}
+ ${'2'} | ${2}
+ ${'1'} | ${3}
+ ${'1'} | ${4}
+ ${null} | ${5}
+ `('renders node size for each cluster', ({ nodeSize, lineNumber }) => {
+ const sizes = findTable().findAll('td:nth-child(3)');
+ const size = sizes.at(lineNumber);
+
+ if (nodeSize) {
+ expect(size.text()).toBe(nodeSize);
+ } else {
+ expect(size.find(GlSkeletonLoading).exists()).toBe(true);
+ }
+ });
+ });
+
+ describe('nodes finish loading', () => {
+ beforeEach(() => {
+ wrapper.vm.$store.state.loadingNodes = false;
+ return wrapper.vm.$nextTick();
+ });
+
+ it.each`
+ nodeSize | lineNumber
+ ${'Unknown'} | ${0}
+ ${'1'} | ${1}
+ ${'2'} | ${2}
+ ${'1'} | ${3}
+ ${'1'} | ${4}
+ ${'Unknown'} | ${5}
+ `('renders node size for each cluster', ({ nodeSize, lineNumber }) => {
+ const sizes = findTable().findAll('td:nth-child(3)');
+ const size = sizes.at(lineNumber);
+
+ expect(size.text()).toBe(nodeSize);
+ expect(size.find(GlSkeletonLoading).exists()).toBe(false);
+ });
});
describe('nodes with unknown quantity', () => {
diff --git a/spec/frontend/clusters_list/store/actions_spec.js b/spec/frontend/clusters_list/store/actions_spec.js
index 74e351a3704..c8556350747 100644
--- a/spec/frontend/clusters_list/store/actions_spec.js
+++ b/spec/frontend/clusters_list/store/actions_spec.js
@@ -13,6 +13,28 @@ import * as Sentry from '@sentry/browser';
jest.mock('~/flash.js');
describe('Clusters store actions', () => {
+ let captureException;
+
+ describe('reportSentryError', () => {
+ beforeEach(() => {
+ captureException = jest.spyOn(Sentry, 'captureException');
+ });
+
+ afterEach(() => {
+ captureException.mockRestore();
+ });
+
+ it('should report sentry error', done => {
+ const sentryError = new Error('New Sentry Error');
+ const tag = 'sentryErrorTag';
+
+ testAction(actions.reportSentryError, { error: sentryError, tag }, {}, [], [], () => {
+ expect(captureException).toHaveBeenCalledWith(sentryError);
+ done();
+ });
+ });
+ });
+
describe('fetchClusters', () => {
let mock;
@@ -48,8 +70,9 @@ describe('Clusters store actions', () => {
{ endpoint: apiData.endpoint },
{},
[
+ { type: types.SET_LOADING_NODES, payload: true },
{ type: types.SET_CLUSTERS_DATA, payload: { data: apiData, paginationInformation } },
- { type: types.SET_LOADING_STATE, payload: false },
+ { type: types.SET_LOADING_CLUSTERS, payload: false },
],
[],
() => done(),
@@ -63,8 +86,20 @@ describe('Clusters store actions', () => {
actions.fetchClusters,
{ endpoint: apiData.endpoint },
{},
- [{ type: types.SET_LOADING_STATE, payload: false }],
- [],
+ [
+ { type: types.SET_LOADING_NODES, payload: true },
+ { type: types.SET_LOADING_CLUSTERS, payload: false },
+ { type: types.SET_LOADING_NODES, payload: false },
+ ],
+ [
+ {
+ type: 'reportSentryError',
+ payload: {
+ error: new Error('Request failed with status code 400'),
+ tag: 'fetchClustersErrorCallback',
+ },
+ },
+ ],
() => {
expect(flashError).toHaveBeenCalledWith(expect.stringMatching('error'));
done();
@@ -73,7 +108,6 @@ describe('Clusters store actions', () => {
});
describe('multiple api requests', () => {
- let captureException;
let pollRequest;
let pollStop;
@@ -81,7 +115,6 @@ describe('Clusters store actions', () => {
const pollHeaders = { 'poll-interval': pollInterval, ...headers };
beforeEach(() => {
- captureException = jest.spyOn(Sentry, 'captureException');
pollRequest = jest.spyOn(Poll.prototype, 'makeRequest');
pollStop = jest.spyOn(Poll.prototype, 'stop');
@@ -89,7 +122,6 @@ describe('Clusters store actions', () => {
});
afterEach(() => {
- captureException.mockRestore();
pollRequest.mockRestore();
pollStop.mockRestore();
});
@@ -100,8 +132,9 @@ describe('Clusters store actions', () => {
{ endpoint: apiData.endpoint },
{},
[
+ { type: types.SET_LOADING_NODES, payload: true },
{ type: types.SET_CLUSTERS_DATA, payload: { data: apiData, paginationInformation } },
- { type: types.SET_LOADING_STATE, payload: false },
+ { type: types.SET_LOADING_CLUSTERS, payload: false },
],
[],
() => {
@@ -149,17 +182,27 @@ describe('Clusters store actions', () => {
{ endpoint: apiData.endpoint },
{},
[
+ { type: types.SET_LOADING_NODES, payload: true },
{
type: types.SET_CLUSTERS_DATA,
payload: { data: badApiResponse, paginationInformation },
},
- { type: types.SET_LOADING_STATE, payload: false },
+ { type: types.SET_LOADING_CLUSTERS, payload: false },
+ { type: types.SET_LOADING_CLUSTERS, payload: false },
+ { type: types.SET_LOADING_NODES, payload: false },
+ ],
+ [
+ {
+ type: 'reportSentryError',
+ payload: {
+ error: new Error('clusters.every is not a function'),
+ tag: 'fetchClustersSuccessCallback',
+ },
+ },
],
- [],
() => {
expect(pollRequest).toHaveBeenCalledTimes(1);
expect(pollStop).toHaveBeenCalledTimes(1);
- expect(captureException).toHaveBeenCalledTimes(1);
done();
},
);
diff --git a/spec/frontend/clusters_list/store/mutations_spec.js b/spec/frontend/clusters_list/store/mutations_spec.js
new file mode 100644
index 00000000000..df0dfe587b6
--- /dev/null
+++ b/spec/frontend/clusters_list/store/mutations_spec.js
@@ -0,0 +1,60 @@
+import * as types from '~/clusters_list/store/mutation_types';
+import { apiData } from '../mock_data';
+import getInitialState from '~/clusters_list/store/state';
+import mutations from '~/clusters_list/store/mutations';
+
+describe('Admin statistics panel mutations', () => {
+ let state;
+
+ const paginationInformation = {
+ nextPage: 1,
+ page: 1,
+ perPage: 20,
+ previousPage: 1,
+ total: apiData.clusters.length,
+ totalPages: 1,
+ };
+
+ beforeEach(() => {
+ state = getInitialState();
+ });
+
+ describe(`${types.SET_CLUSTERS_DATA}`, () => {
+ it('sets clusters and pagination values', () => {
+ mutations[types.SET_CLUSTERS_DATA](state, { data: apiData, paginationInformation });
+
+ expect(state.clusters).toBe(apiData.clusters);
+ expect(state.clustersPerPage).toBe(paginationInformation.perPage);
+ expect(state.hasAncestorClusters).toBe(apiData.has_ancestor_clusters);
+ expect(state.totalCulsters).toBe(paginationInformation.total);
+ });
+ });
+
+ describe(`${types.SET_LOADING_CLUSTERS}`, () => {
+ it('sets value to false', () => {
+ expect(state.loadingClusters).toBe(true);
+
+ mutations[types.SET_LOADING_CLUSTERS](state, false);
+
+ expect(state.loadingClusters).toBe(false);
+ });
+ });
+
+ describe(`${types.SET_LOADING_NODES}`, () => {
+ it('sets value to false', () => {
+ expect(state.loadingNodes).toBe(true);
+
+ mutations[types.SET_LOADING_NODES](state, false);
+
+ expect(state.loadingNodes).toBe(false);
+ });
+ });
+
+ describe(`${types.SET_PAGE}`, () => {
+ it('changes page value', () => {
+ mutations[types.SET_PAGE](state, 123);
+
+ expect(state.page).toBe(123);
+ });
+ });
+});
diff --git a/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap b/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap
index 7079ddfc2ab..161c2bade05 100644
--- a/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap
+++ b/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap
@@ -10,53 +10,81 @@ exports[`Code navigation popover component renders popover 1`] = `
style="left: 0px;"
/>
- <div
- class="border-bottom"
+ <gl-tabs-stub
+ contentclass="gl-py-0"
+ nav-class="gl-hidden"
+ theme="indigo"
>
- <pre
- class="border-0 bg-transparent m-0 code highlight"
+ <gl-tab-stub
+ title="Definition"
>
- <span
- class="line"
- lang="javascript"
+ <div
+ class="overflow-auto code-navigation-popover-container"
>
- <span
- class="k"
+ <div
+ class=""
>
- function
- </span>
- <span>
- main() {
- </span>
- </span>
- <span
- class="line"
- lang="javascript"
+ <pre
+ class="border-0 bg-transparent m-0 code highlight text-wrap"
+ >
+ <span
+ class="line"
+ lang="javascript"
+ >
+ <span
+ class="k"
+ >
+ function
+ </span>
+ <span>
+ main() {
+ </span>
+ </span>
+ <span
+ class="line"
+ lang="javascript"
+ >
+ <span>
+ }
+ </span>
+ </span>
+ </pre>
+ </div>
+ </div>
+
+ <div
+ class="popover-body border-top"
>
- <span>
- }
- </span>
- </span>
- </pre>
- </div>
-
- <div
- class="popover-body"
- >
- <gl-button-stub
- category="tertiary"
- class="w-100"
- data-testid="go-to-definition-btn"
- href="http://gitlab.com/test.js#L20"
- icon=""
- size="medium"
- target="_blank"
- variant="default"
+ <gl-button-stub
+ category="tertiary"
+ class="w-100"
+ data-testid="go-to-definition-btn"
+ href="http://gitlab.com/test.js"
+ icon=""
+ size="medium"
+ target="_blank"
+ variant="default"
+ >
+
+ Go to definition
+
+ </gl-button-stub>
+ </div>
+ </gl-tab-stub>
+
+ <gl-tab-stub
+ class="py-2"
+ data-testid="references-tab"
>
+
+ <p
+ class="gl-my-4 gl-px-4"
+ >
+
+ No references found
- Go to definition
-
- </gl-button-stub>
- </div>
+ </p>
+ </gl-tab-stub>
+ </gl-tabs-stub>
</div>
`;
diff --git a/spec/frontend/code_navigation/components/popover_spec.js b/spec/frontend/code_navigation/components/popover_spec.js
index b3f814f1be4..7b323cfab72 100644
--- a/spec/frontend/code_navigation/components/popover_spec.js
+++ b/spec/frontend/code_navigation/components/popover_spec.js
@@ -26,7 +26,8 @@ const MOCK_CODE_DATA = Object.freeze({
],
},
],
- definition_path: 'test.js#L20',
+ definition_path: 'test.js',
+ definitionLineNumber: 20,
});
const MOCK_DOCS_DATA = Object.freeze({
@@ -39,6 +40,17 @@ const MOCK_DOCS_DATA = Object.freeze({
definition_path: 'test.js#L20',
});
+const MOCK_DATA_WITH_REFERENCES = Object.freeze({
+ hover: [
+ {
+ language: null,
+ value: 'console.log',
+ },
+ ],
+ references: [{ path: 'index.js' }, { path: 'app.js' }],
+ definition_path: 'test.js#L20',
+});
+
let wrapper;
function factory({ position, data, definitionPathPrefix, blobPath = 'index.js' }) {
@@ -63,6 +75,16 @@ describe('Code navigation popover component', () => {
expect(wrapper.element).toMatchSnapshot();
});
+ it('srender references tab with empty text when no references exist', () => {
+ factory({
+ position: { x: 0, y: 0, height: 0 },
+ data: MOCK_CODE_DATA,
+ definitionPathPrefix: DEFINITION_PATH_PREFIX,
+ });
+
+ expect(wrapper.find('[data-testid="references-tab"]').text()).toContain('No references found');
+ });
+
it('renders link with hash to current file', () => {
factory({
position: { x: 0, y: 0, height: 0 },
@@ -74,6 +96,17 @@ describe('Code navigation popover component', () => {
expect(wrapper.find('[data-testid="go-to-definition-btn"]').attributes('href')).toBe('#L20');
});
+ it('renders list of references', () => {
+ factory({
+ position: { x: 0, y: 0, height: 0 },
+ data: MOCK_DATA_WITH_REFERENCES,
+ definitionPathPrefix: DEFINITION_PATH_PREFIX,
+ });
+
+ expect(wrapper.find('[data-testid="references-tab"]').exists()).toBe(true);
+ expect(wrapper.findAll('[data-testid="reference-link"]').length).toBe(2);
+ });
+
describe('code output', () => {
it('renders code output', () => {
factory({
diff --git a/spec/frontend/code_navigation/store/actions_spec.js b/spec/frontend/code_navigation/store/actions_spec.js
index 4cf77ed1be5..fbd93b10a14 100644
--- a/spec/frontend/code_navigation/store/actions_spec.js
+++ b/spec/frontend/code_navigation/store/actions_spec.js
@@ -69,7 +69,12 @@ describe('Code navigation actions', () => {
payload: {
path: 'index.js',
normalizedData: {
- '0:0': { start_line: 0, start_char: 0, hover: { value: '123' } },
+ '0:0': {
+ definitionLineNumber: 0,
+ start_line: 0,
+ start_char: 0,
+ hover: { value: '123' },
+ },
},
},
},
@@ -91,7 +96,12 @@ describe('Code navigation actions', () => {
payload: {
path: 'index.js',
normalizedData: {
- '0:0': { start_line: 0, start_char: 0, hover: { value: '123' } },
+ '0:0': {
+ definitionLineNumber: 0,
+ start_line: 0,
+ start_char: 0,
+ hover: { value: '123' },
+ },
},
},
},
@@ -159,7 +169,9 @@ describe('Code navigation actions', () => {
let target;
beforeEach(() => {
- setFixtures('<div data-path="index.js"><div class="js-test"></div></div>');
+ setFixtures(
+ '<div data-path="index.js"><div class="line"><div class="js-test"></div></div></div>',
+ );
target = document.querySelector('.js-test');
});
@@ -186,7 +198,7 @@ describe('Code navigation actions', () => {
payload: {
blobPath: 'index.js',
definition: { hover: 'test' },
- position: { height: 0, x: 0, y: 0 },
+ position: { height: 0, x: 0, y: 0, lineIndex: 0 },
},
},
],
@@ -210,7 +222,7 @@ describe('Code navigation actions', () => {
payload: {
blobPath: 'index.js',
definition: { hover: 'test' },
- position: { height: 0, x: 0, y: 0 },
+ position: { height: 0, x: 0, y: 0, lineIndex: 0 },
},
},
],
@@ -235,7 +247,7 @@ describe('Code navigation actions', () => {
payload: {
blobPath: 'index.js',
definition: { hover: 'test' },
- position: { height: 0, x: 0, y: 0 },
+ position: { height: 0, x: 0, y: 0, lineIndex: 0 },
},
},
],
diff --git a/spec/frontend/code_navigation/utils/index_spec.js b/spec/frontend/code_navigation/utils/index_spec.js
index 58c48a90075..6a01249d2a3 100644
--- a/spec/frontend/code_navigation/utils/index_spec.js
+++ b/spec/frontend/code_navigation/utils/index_spec.js
@@ -36,7 +36,7 @@ describe('setCurrentHoverElement', () => {
describe('addInteractionClass', () => {
beforeEach(() => {
setFixtures(
- '<div data-path="index.js"><div class="blob-content"><div id="LC1"><span>console</span><span>.</span><span>log</span></div><div id="LC2"><span>function</span></div></div></div>',
+ '<div data-path="index.js"><div class="blob-content"><div id="LC1" class="line"><span>console</span><span>.</span><span>log</span></div><div id="LC2" class="line"><span>function</span></div></div></div>',
);
});
diff --git a/spec/frontend/cycle_analytics/stage_nav_item_spec.js b/spec/frontend/cycle_analytics/stage_nav_item_spec.js
index 480bb756731..1fe80d3b1ce 100644
--- a/spec/frontend/cycle_analytics/stage_nav_item_spec.js
+++ b/spec/frontend/cycle_analytics/stage_nav_item_spec.js
@@ -10,7 +10,6 @@ describe('StageNavItem', () => {
const func = shallow ? shallowMount : mount;
return func(StageNavItem, {
propsData: {
- canEdit: false,
isActive: false,
isUserAllowed: false,
isDefaultStage: true,
@@ -125,7 +124,7 @@ describe('StageNavItem', () => {
describe('User can edit stages', () => {
beforeEach(() => {
- wrapper = createComponent({ canEdit: true, isUserAllowed: true }, false);
+ wrapper = createComponent({ isUserAllowed: true }, false);
});
afterEach(() => {
diff --git a/spec/frontend/design_management/components/design_notes/design_discussion_spec.js b/spec/frontend/design_management/components/design_notes/design_discussion_spec.js
index 557f53e864f..102e8e0664c 100644
--- a/spec/frontend/design_management/components/design_notes/design_discussion_spec.js
+++ b/spec/frontend/design_management/components/design_notes/design_discussion_spec.js
@@ -4,7 +4,7 @@ import notes from '../../mock_data/notes';
import DesignDiscussion from '~/design_management/components/design_notes/design_discussion.vue';
import DesignNote from '~/design_management/components/design_notes/design_note.vue';
import DesignReplyForm from '~/design_management/components/design_notes/design_reply_form.vue';
-import createNoteMutation from '~/design_management/graphql/mutations/createNote.mutation.graphql';
+import createNoteMutation from '~/design_management/graphql/mutations/create_note.mutation.graphql';
import toggleResolveDiscussionMutation from '~/design_management/graphql/mutations/toggle_resolve_discussion.mutation.graphql';
import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
import ToggleRepliesWidget from '~/design_management/components/design_notes/toggle_replies_widget.vue';
diff --git a/spec/frontend/design_management/pages/design/index_spec.js b/spec/frontend/design_management/pages/design/index_spec.js
index 430cf8722fe..82b607eb77d 100644
--- a/spec/frontend/design_management/pages/design/index_spec.js
+++ b/spec/frontend/design_management/pages/design/index_spec.js
@@ -5,8 +5,8 @@ import { ApolloMutation } from 'vue-apollo';
import createFlash from '~/flash';
import DesignIndex from '~/design_management/pages/design/index.vue';
import DesignSidebar from '~/design_management/components/design_sidebar.vue';
-import DesignReplyForm from '~/design_management/components/design_notes/design_reply_form.vue';
-import createImageDiffNoteMutation from '~/design_management/graphql/mutations/createImageDiffNote.mutation.graphql';
+import DesignPresentation from '~/design_management/components/design_presentation.vue';
+import createImageDiffNoteMutation from '~/design_management/graphql/mutations/create_image_diff_note.mutation.graphql';
import design from '../../mock_data/design';
import mockResponseWithDesigns from '../../mock_data/designs';
import mockResponseNoDesigns from '../../mock_data/no_designs';
@@ -26,6 +26,15 @@ jest.mock('mousetrap', () => ({
unbind: jest.fn(),
}));
+const focusInput = jest.fn();
+
+const DesignReplyForm = {
+ template: '<div><textarea ref="textarea"></textarea></div>',
+ methods: {
+ focusInput,
+ },
+};
+
const localVue = createLocalVue();
localVue.use(VueRouter);
@@ -64,6 +73,7 @@ describe('Design management design index page', () => {
const findDiscussionForm = () => wrapper.find(DesignReplyForm);
const findSidebar = () => wrapper.find(DesignSidebar);
+ const findDesignPresentation = () => wrapper.find(DesignPresentation);
function createComponent(loading = false, data = {}) {
const $apollo = {
@@ -83,6 +93,7 @@ describe('Design management design index page', () => {
stubs: {
ApolloMutation,
DesignSidebar,
+ DesignReplyForm,
},
data() {
return {
@@ -153,13 +164,29 @@ describe('Design management design index page', () => {
},
});
- wrapper.vm.openCommentForm({ x: 0, y: 0 });
+ findDesignPresentation().vm.$emit('openCommentForm', { x: 0, y: 0 });
return wrapper.vm.$nextTick().then(() => {
expect(findDiscussionForm().exists()).toBe(true);
});
});
+ it('keeps new discussion form focused', () => {
+ createComponent(false, {
+ design: {
+ ...design,
+ discussions: {
+ nodes: [],
+ },
+ },
+ annotationCoordinates,
+ });
+
+ findDesignPresentation().vm.$emit('openCommentForm', { x: 10, y: 10 });
+
+ expect(focusInput).toHaveBeenCalled();
+ });
+
it('sends a mutation on submitting form and closes form', () => {
createComponent(false, {
design: {
diff --git a/spec/frontend/design_management/pages/index_spec.js b/spec/frontend/design_management/pages/index_spec.js
index d4e9bae3e89..d3761bf09e9 100644
--- a/spec/frontend/design_management/pages/index_spec.js
+++ b/spec/frontend/design_management/pages/index_spec.js
@@ -3,7 +3,7 @@ import { ApolloMutation } from 'vue-apollo';
import VueRouter from 'vue-router';
import { GlEmptyState } from '@gitlab/ui';
import Index from '~/design_management/pages/index.vue';
-import uploadDesignQuery from '~/design_management/graphql/mutations/uploadDesign.mutation.graphql';
+import uploadDesignQuery from '~/design_management/graphql/mutations/upload_design.mutation.graphql';
import DesignDestroyer from '~/design_management/components/design_destroyer.vue';
import DesignDropzone from '~/design_management/components/upload/design_dropzone.vue';
import DeleteButton from '~/design_management/components/delete_button.vue';
diff --git a/spec/frontend/design_management/utils/tracking_spec.js b/spec/frontend/design_management/utils/tracking_spec.js
index 9fa5eae55b3..0549fb44956 100644
--- a/spec/frontend/design_management/utils/tracking_spec.js
+++ b/spec/frontend/design_management/utils/tracking_spec.js
@@ -8,7 +8,7 @@ function getTrackingSpy(key) {
describe('Tracking Events', () => {
describe('trackDesignDetailView', () => {
const eventKey = 'projects:issues:design';
- const eventName = 'design_viewed';
+ const eventName = 'view_design';
it('trackDesignDetailView fires a tracking event when called', () => {
const trackingSpy = getTrackingSpy(eventKey);
@@ -20,11 +20,14 @@ describe('Tracking Events', () => {
eventName,
expect.objectContaining({
label: eventName,
- value: {
- 'internal-object-refrerer': '',
- 'design-collection-owner': '',
- 'design-version-number': 1,
- 'design-is-current-version': false,
+ context: {
+ schema: expect.any(String),
+ data: {
+ 'design-version-number': 1,
+ 'design-is-current-version': false,
+ 'internal-object-referrer': '',
+ 'design-collection-owner': '',
+ },
},
}),
);
@@ -40,11 +43,14 @@ describe('Tracking Events', () => {
eventName,
expect.objectContaining({
label: eventName,
- value: {
- 'internal-object-refrerer': 'from-a-test',
- 'design-collection-owner': 'test',
- 'design-version-number': 100,
- 'design-is-current-version': true,
+ context: {
+ schema: expect.any(String),
+ data: {
+ 'design-version-number': 100,
+ 'design-is-current-version': true,
+ 'internal-object-referrer': 'from-a-test',
+ 'design-collection-owner': 'test',
+ },
},
}),
);
diff --git a/spec/frontend/design_management_new/components/__snapshots__/design_note_pin_spec.js.snap b/spec/frontend/design_management_new/components/__snapshots__/design_note_pin_spec.js.snap
new file mode 100644
index 00000000000..4c848256e5b
--- /dev/null
+++ b/spec/frontend/design_management_new/components/__snapshots__/design_note_pin_spec.js.snap
@@ -0,0 +1,42 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design note pin component should match the snapshot of note when repositioning 1`] = `
+<button
+ aria-label="Comment form position"
+ class="design-pin gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center btn-transparent comment-indicator"
+ style="left: 10px; top: 10px; cursor: move;"
+ type="button"
+>
+ <icon-stub
+ name="image-comment-dark"
+ size="16"
+ />
+</button>
+`;
+
+exports[`Design note pin component should match the snapshot of note with index 1`] = `
+<button
+ aria-label="Comment '1' position"
+ class="design-pin gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center js-image-badge badge badge-pill"
+ style="left: 10px; top: 10px;"
+ type="button"
+>
+
+ 1
+
+</button>
+`;
+
+exports[`Design note pin component should match the snapshot of note without index 1`] = `
+<button
+ aria-label="Comment form position"
+ class="design-pin gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center btn-transparent comment-indicator"
+ style="left: 10px; top: 10px;"
+ type="button"
+>
+ <icon-stub
+ name="image-comment-dark"
+ size="16"
+ />
+</button>
+`;
diff --git a/spec/frontend/design_management_new/components/__snapshots__/design_presentation_spec.js.snap b/spec/frontend/design_management_new/components/__snapshots__/design_presentation_spec.js.snap
new file mode 100644
index 00000000000..189962c5b2e
--- /dev/null
+++ b/spec/frontend/design_management_new/components/__snapshots__/design_presentation_spec.js.snap
@@ -0,0 +1,104 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management design presentation component currentCommentForm is equal to current annotation position when isAnnotating is true 1`] = `
+<div
+ class="h-100 w-100 p-3 overflow-auto position-relative"
+>
+ <div
+ class="h-100 w-100 d-flex align-items-center position-relative"
+ >
+ <design-image-stub
+ image="test.jpg"
+ name="test"
+ scale="1"
+ />
+
+ <design-overlay-stub
+ currentcommentform="[object Object]"
+ dimensions="[object Object]"
+ notes=""
+ position="[object Object]"
+ />
+ </div>
+</div>
+`;
+
+exports[`Design management design presentation component currentCommentForm is null when isAnnotating is false 1`] = `
+<div
+ class="h-100 w-100 p-3 overflow-auto position-relative"
+>
+ <div
+ class="h-100 w-100 d-flex align-items-center position-relative"
+ >
+ <design-image-stub
+ image="test.jpg"
+ name="test"
+ scale="1"
+ />
+
+ <design-overlay-stub
+ dimensions="[object Object]"
+ notes=""
+ position="[object Object]"
+ />
+ </div>
+</div>
+`;
+
+exports[`Design management design presentation component currentCommentForm is null when isAnnotating is true but annotation position is falsey 1`] = `
+<div
+ class="h-100 w-100 p-3 overflow-auto position-relative"
+>
+ <div
+ class="h-100 w-100 d-flex align-items-center position-relative"
+ >
+ <design-image-stub
+ image="test.jpg"
+ name="test"
+ scale="1"
+ />
+
+ <design-overlay-stub
+ dimensions="[object Object]"
+ notes=""
+ position="[object Object]"
+ />
+ </div>
+</div>
+`;
+
+exports[`Design management design presentation component renders empty state when no image provided 1`] = `
+<div
+ class="h-100 w-100 p-3 overflow-auto position-relative"
+>
+ <div
+ class="h-100 w-100 d-flex align-items-center position-relative"
+ >
+ <!---->
+
+ <!---->
+ </div>
+</div>
+`;
+
+exports[`Design management design presentation component renders image and overlay when image provided 1`] = `
+<div
+ class="h-100 w-100 p-3 overflow-auto position-relative"
+>
+ <div
+ class="h-100 w-100 d-flex align-items-center position-relative"
+ >
+ <design-image-stub
+ image="test.jpg"
+ name="test"
+ scale="1"
+ />
+
+ <design-overlay-stub
+ dimensions="[object Object]"
+ notes=""
+ position="[object Object]"
+ />
+ </div>
+</div>
+`;
diff --git a/spec/frontend/design_management_new/components/__snapshots__/design_scaler_spec.js.snap b/spec/frontend/design_management_new/components/__snapshots__/design_scaler_spec.js.snap
new file mode 100644
index 00000000000..cb4575cbd11
--- /dev/null
+++ b/spec/frontend/design_management_new/components/__snapshots__/design_scaler_spec.js.snap
@@ -0,0 +1,115 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management design scaler component minus and reset buttons are disabled when scale === 1 1`] = `
+<div
+ class="design-scaler btn-group"
+ role="group"
+>
+ <button
+ class="btn"
+ disabled="disabled"
+ >
+ <span
+ class="d-flex-center gl-icon s16"
+ >
+
+ –
+
+ </span>
+ </button>
+
+ <button
+ class="btn"
+ disabled="disabled"
+ >
+ <gl-icon-stub
+ name="redo"
+ size="16"
+ />
+ </button>
+
+ <button
+ class="btn"
+ >
+ <gl-icon-stub
+ name="plus"
+ size="16"
+ />
+ </button>
+</div>
+`;
+
+exports[`Design management design scaler component minus and reset buttons are enabled when scale > 1 1`] = `
+<div
+ class="design-scaler btn-group"
+ role="group"
+>
+ <button
+ class="btn"
+ >
+ <span
+ class="d-flex-center gl-icon s16"
+ >
+
+ –
+
+ </span>
+ </button>
+
+ <button
+ class="btn"
+ >
+ <gl-icon-stub
+ name="redo"
+ size="16"
+ />
+ </button>
+
+ <button
+ class="btn"
+ >
+ <gl-icon-stub
+ name="plus"
+ size="16"
+ />
+ </button>
+</div>
+`;
+
+exports[`Design management design scaler component plus button is disabled when scale === 2 1`] = `
+<div
+ class="design-scaler btn-group"
+ role="group"
+>
+ <button
+ class="btn"
+ >
+ <span
+ class="d-flex-center gl-icon s16"
+ >
+
+ –
+
+ </span>
+ </button>
+
+ <button
+ class="btn"
+ >
+ <gl-icon-stub
+ name="redo"
+ size="16"
+ />
+ </button>
+
+ <button
+ class="btn"
+ disabled="disabled"
+ >
+ <gl-icon-stub
+ name="plus"
+ size="16"
+ />
+ </button>
+</div>
+`;
diff --git a/spec/frontend/design_management_new/components/__snapshots__/image_spec.js.snap b/spec/frontend/design_management_new/components/__snapshots__/image_spec.js.snap
new file mode 100644
index 00000000000..acaa62b11eb
--- /dev/null
+++ b/spec/frontend/design_management_new/components/__snapshots__/image_spec.js.snap
@@ -0,0 +1,68 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management large image component renders image 1`] = `
+<div
+ class="m-auto js-design-image"
+>
+ <!---->
+
+ <img
+ alt="test"
+ class="mh-100 img-fluid"
+ src="test.jpg"
+ />
+</div>
+`;
+
+exports[`Design management large image component renders loading state 1`] = `
+<div
+ class="m-auto js-design-image"
+ isloading="true"
+>
+ <!---->
+
+ <img
+ alt=""
+ class="mh-100 img-fluid"
+ src=""
+ />
+</div>
+`;
+
+exports[`Design management large image component renders media broken icon on error 1`] = `
+<gl-icon-stub
+ class="text-secondary-100"
+ name="media-broken"
+ size="48"
+/>
+`;
+
+exports[`Design management large image component sets correct classes and styles if imageStyle is set 1`] = `
+<div
+ class="m-auto js-design-image"
+>
+ <!---->
+
+ <img
+ alt="test"
+ class="mh-100"
+ src="test.jpg"
+ style="width: 100px; height: 100px;"
+ />
+</div>
+`;
+
+exports[`Design management large image component zoom sets image style when zoomed 1`] = `
+<div
+ class="m-auto js-design-image"
+>
+ <!---->
+
+ <img
+ alt="test"
+ class="mh-100"
+ src="test.jpg"
+ style="width: 200px; height: 200px;"
+ />
+</div>
+`;
diff --git a/spec/frontend/design_management_new/components/delete_button_spec.js b/spec/frontend/design_management_new/components/delete_button_spec.js
new file mode 100644
index 00000000000..218c58847a6
--- /dev/null
+++ b/spec/frontend/design_management_new/components/delete_button_spec.js
@@ -0,0 +1,51 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlButton, GlModal, GlModalDirective } from '@gitlab/ui';
+import BatchDeleteButton from '~/design_management_new/components/delete_button.vue';
+
+describe('Batch delete button component', () => {
+ let wrapper;
+
+ const findButton = () => wrapper.find(GlButton);
+ const findModal = () => wrapper.find(GlModal);
+
+ function createComponent(isDeleting = false) {
+ wrapper = shallowMount(BatchDeleteButton, {
+ propsData: {
+ isDeleting,
+ },
+ directives: {
+ GlModalDirective,
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders non-disabled button by default', () => {
+ createComponent();
+
+ expect(findButton().exists()).toBe(true);
+ expect(findButton().attributes('disabled')).toBeFalsy();
+ });
+
+ it('renders disabled button when design is deleting', () => {
+ createComponent(true);
+ expect(findButton().attributes('disabled')).toBeTruthy();
+ });
+
+ it('emits `deleteSelectedDesigns` event on modal ok click', () => {
+ createComponent();
+ findButton().vm.$emit('click');
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ findModal().vm.$emit('ok');
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(wrapper.emitted().deleteSelectedDesigns).toBeTruthy();
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/design_note_pin_spec.js b/spec/frontend/design_management_new/components/design_note_pin_spec.js
new file mode 100644
index 00000000000..8e2caa604f4
--- /dev/null
+++ b/spec/frontend/design_management_new/components/design_note_pin_spec.js
@@ -0,0 +1,49 @@
+import { shallowMount } from '@vue/test-utils';
+import DesignNotePin from '~/design_management_new/components/design_note_pin.vue';
+
+describe('Design note pin component', () => {
+ let wrapper;
+
+ function createComponent(propsData = {}) {
+ wrapper = shallowMount(DesignNotePin, {
+ propsData: {
+ position: {
+ left: '10px',
+ top: '10px',
+ },
+ ...propsData,
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('should match the snapshot of note without index', () => {
+ createComponent();
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('should match the snapshot of note with index', () => {
+ createComponent({ label: 1 });
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('should match the snapshot of note when repositioning', () => {
+ createComponent({ repositioning: true });
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ describe('pinStyle', () => {
+ it('sets cursor to `move` when repositioning = true', () => {
+ createComponent({ repositioning: true });
+ expect(wrapper.vm.pinStyle.cursor).toBe('move');
+ });
+
+ it('does not set cursor when repositioning = false', () => {
+ createComponent();
+ expect(wrapper.vm.pinStyle.cursor).toBe(undefined);
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/design_notes/__snapshots__/design_note_spec.js.snap b/spec/frontend/design_management_new/components/design_notes/__snapshots__/design_note_spec.js.snap
new file mode 100644
index 00000000000..b55bacb6fc5
--- /dev/null
+++ b/spec/frontend/design_management_new/components/design_notes/__snapshots__/design_note_spec.js.snap
@@ -0,0 +1,67 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design note component should match the snapshot 1`] = `
+<timeline-entry-item-stub
+ class="design-note note-form"
+ id="note_123"
+>
+ <user-avatar-link-stub
+ imgalt=""
+ imgcssclasses=""
+ imgsize="40"
+ imgsrc=""
+ linkhref=""
+ tooltipplacement="top"
+ tooltiptext=""
+ username=""
+ />
+
+ <div
+ class="d-flex justify-content-between"
+ >
+ <div>
+ <a
+ class="js-user-link"
+ data-user-id="author-id"
+ >
+ <span
+ class="note-header-author-name bold"
+ >
+
+ </span>
+
+ <!---->
+
+ <span
+ class="note-headline-light"
+ >
+ @
+ </span>
+ </a>
+
+ <span
+ class="note-headline-light note-headline-meta"
+ >
+ <span
+ class="system-note-message"
+ />
+
+ <!---->
+ </span>
+ </div>
+
+ <div
+ class="gl-display-flex"
+ >
+
+ <!---->
+ </div>
+ </div>
+
+ <div
+ class="note-text js-note-text md"
+ data-qa-selector="note_content"
+ />
+
+</timeline-entry-item-stub>
+`;
diff --git a/spec/frontend/design_management_new/components/design_notes/__snapshots__/design_reply_form_spec.js.snap b/spec/frontend/design_management_new/components/design_notes/__snapshots__/design_reply_form_spec.js.snap
new file mode 100644
index 00000000000..e01c79e3520
--- /dev/null
+++ b/spec/frontend/design_management_new/components/design_notes/__snapshots__/design_reply_form_spec.js.snap
@@ -0,0 +1,15 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design reply form component renders button text as "Comment" when creating a comment 1`] = `
+"<button data-track-event=\\"click_button\\" data-qa-selector=\\"save_comment_button\\" type=\\"submit\\" disabled=\\"disabled\\" class=\\"btn btn-success btn-md disabled\\">
+ <!---->
+ Comment
+</button>"
+`;
+
+exports[`Design reply form component renders button text as "Save comment" when creating a comment 1`] = `
+"<button data-track-event=\\"click_button\\" data-qa-selector=\\"save_comment_button\\" type=\\"submit\\" disabled=\\"disabled\\" class=\\"btn btn-success btn-md disabled\\">
+ <!---->
+ Save comment
+</button>"
+`;
diff --git a/spec/frontend/design_management_new/components/design_notes/design_discussion_spec.js b/spec/frontend/design_management_new/components/design_notes/design_discussion_spec.js
new file mode 100644
index 00000000000..401ce64e859
--- /dev/null
+++ b/spec/frontend/design_management_new/components/design_notes/design_discussion_spec.js
@@ -0,0 +1,322 @@
+import { mount } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
+import notes from '../../mock_data/notes';
+import DesignDiscussion from '~/design_management_new/components/design_notes/design_discussion.vue';
+import DesignNote from '~/design_management_new/components/design_notes/design_note.vue';
+import DesignReplyForm from '~/design_management_new/components/design_notes/design_reply_form.vue';
+import createNoteMutation from '~/design_management_new/graphql/mutations/create_note.mutation.graphql';
+import toggleResolveDiscussionMutation from '~/design_management_new/graphql/mutations/toggle_resolve_discussion.mutation.graphql';
+import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
+import ToggleRepliesWidget from '~/design_management_new/components/design_notes/toggle_replies_widget.vue';
+
+const discussion = {
+ id: '0',
+ resolved: false,
+ resolvable: true,
+ notes,
+};
+
+describe('Design discussions component', () => {
+ let wrapper;
+
+ const findDesignNotes = () => wrapper.findAll(DesignNote);
+ const findReplyPlaceholder = () => wrapper.find(ReplyPlaceholder);
+ const findReplyForm = () => wrapper.find(DesignReplyForm);
+ const findRepliesWidget = () => wrapper.find(ToggleRepliesWidget);
+ const findResolveButton = () => wrapper.find('[data-testid="resolve-button"]');
+ const findResolveIcon = () => wrapper.find('[data-testid="resolve-icon"]');
+ const findResolvedMessage = () => wrapper.find('[data-testid="resolved-message"]');
+ const findResolveLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const findResolveCheckbox = () => wrapper.find('[data-testid="resolve-checkbox"]');
+
+ const mutationVariables = {
+ mutation: createNoteMutation,
+ update: expect.anything(),
+ variables: {
+ input: {
+ noteableId: 'noteable-id',
+ body: 'test',
+ discussionId: '0',
+ },
+ },
+ };
+ const mutate = jest.fn(() => Promise.resolve());
+ const $apollo = {
+ mutate,
+ };
+
+ function createComponent(props = {}, data = {}) {
+ wrapper = mount(DesignDiscussion, {
+ propsData: {
+ resolvedDiscussionsExpanded: true,
+ discussion,
+ noteableId: 'noteable-id',
+ designId: 'design-id',
+ discussionIndex: 1,
+ discussionWithOpenForm: '',
+ ...props,
+ },
+ data() {
+ return {
+ ...data,
+ };
+ },
+ provide: {
+ projectPath: 'project-path',
+ issueIid: '1',
+ },
+ mocks: {
+ $apollo,
+ $route: {
+ hash: '#note_1',
+ },
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when discussion is not resolvable', () => {
+ beforeEach(() => {
+ createComponent({
+ discussion: {
+ ...discussion,
+ resolvable: false,
+ },
+ });
+ });
+
+ it('does not render an icon to resolve a thread', () => {
+ expect(findResolveIcon().exists()).toBe(false);
+ });
+
+ it('does not render a checkbox in reply form', () => {
+ findReplyPlaceholder().vm.$emit('onMouseDown');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findResolveCheckbox().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('when discussion is unresolved', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders correct amount of discussion notes', () => {
+ expect(findDesignNotes()).toHaveLength(2);
+ expect(findDesignNotes().wrappers.every(w => w.isVisible())).toBe(true);
+ });
+
+ it('renders reply placeholder', () => {
+ expect(findReplyPlaceholder().isVisible()).toBe(true);
+ });
+
+ it('does not render toggle replies widget', () => {
+ expect(findRepliesWidget().exists()).toBe(false);
+ });
+
+ it('renders a correct icon to resolve a thread', () => {
+ expect(findResolveIcon().props('name')).toBe('check-circle');
+ });
+
+ it('renders a checkbox with Resolve thread text in reply form', () => {
+ findReplyPlaceholder().vm.$emit('onClick');
+ wrapper.setProps({ discussionWithOpenForm: discussion.id });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findResolveCheckbox().text()).toBe('Resolve thread');
+ });
+ });
+
+ it('does not render resolved message', () => {
+ expect(findResolvedMessage().exists()).toBe(false);
+ });
+ });
+
+ describe('when discussion is resolved', () => {
+ beforeEach(() => {
+ createComponent({
+ discussion: {
+ ...discussion,
+ resolved: true,
+ resolvedBy: notes[0].author,
+ resolvedAt: '2020-05-08T07:10:45Z',
+ },
+ });
+ });
+
+ it('shows only the first note', () => {
+ expect(
+ findDesignNotes()
+ .at(0)
+ .isVisible(),
+ ).toBe(true);
+ expect(
+ findDesignNotes()
+ .at(1)
+ .isVisible(),
+ ).toBe(false);
+ });
+
+ it('renders resolved message', () => {
+ expect(findResolvedMessage().exists()).toBe(true);
+ });
+
+ it('does not show renders reply placeholder', () => {
+ expect(findReplyPlaceholder().isVisible()).toBe(false);
+ });
+
+ it('renders toggle replies widget with correct props', () => {
+ expect(findRepliesWidget().exists()).toBe(true);
+ expect(findRepliesWidget().props()).toEqual({
+ collapsed: true,
+ replies: notes.slice(1),
+ });
+ });
+
+ it('renders a correct icon to resolve a thread', () => {
+ expect(findResolveIcon().props('name')).toBe('check-circle-filled');
+ });
+
+ describe('when replies are expanded', () => {
+ beforeEach(() => {
+ findRepliesWidget().vm.$emit('toggle');
+ return wrapper.vm.$nextTick();
+ });
+
+ it('renders replies widget with collapsed prop equal to false', () => {
+ expect(findRepliesWidget().props('collapsed')).toBe(false);
+ });
+
+ it('renders the second note', () => {
+ expect(
+ findDesignNotes()
+ .at(1)
+ .isVisible(),
+ ).toBe(true);
+ });
+
+ it('renders a reply placeholder', () => {
+ expect(findReplyPlaceholder().isVisible()).toBe(true);
+ });
+
+ it('renders a checkbox with Unresolve thread text in reply form', () => {
+ findReplyPlaceholder().vm.$emit('onClick');
+ wrapper.setProps({ discussionWithOpenForm: discussion.id });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findResolveCheckbox().text()).toBe('Unresolve thread');
+ });
+ });
+ });
+ });
+
+ it('hides reply placeholder and opens form on placeholder click', () => {
+ createComponent();
+ findReplyPlaceholder().vm.$emit('onClick');
+ wrapper.setProps({ discussionWithOpenForm: discussion.id });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findReplyPlaceholder().exists()).toBe(false);
+ expect(findReplyForm().exists()).toBe(true);
+ });
+ });
+
+ it('calls mutation on submitting form and closes the form', () => {
+ createComponent(
+ { discussionWithOpenForm: discussion.id },
+ { discussionComment: 'test', isFormRendered: true },
+ );
+
+ findReplyForm().vm.$emit('submitForm');
+ expect(mutate).toHaveBeenCalledWith(mutationVariables);
+
+ return mutate()
+ .then(() => {
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(findReplyForm().exists()).toBe(false);
+ });
+ });
+
+ it('clears the discussion comment on closing comment form', () => {
+ createComponent(
+ { discussionWithOpenForm: discussion.id },
+ { discussionComment: 'test', isFormRendered: true },
+ );
+
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ findReplyForm().vm.$emit('cancelForm');
+
+ expect(wrapper.vm.discussionComment).toBe('');
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(findReplyForm().exists()).toBe(false);
+ });
+ });
+
+ it('applies correct class to design notes when discussion is highlighted', () => {
+ createComponent(
+ {},
+ {
+ activeDiscussion: {
+ id: notes[0].id,
+ source: 'pin',
+ },
+ },
+ );
+
+ expect(wrapper.findAll(DesignNote).wrappers.every(note => note.classes('gl-bg-blue-50'))).toBe(
+ true,
+ );
+ });
+
+ it('calls toggleResolveDiscussion mutation on resolve thread button click', () => {
+ createComponent();
+ findResolveButton().trigger('click');
+ expect(mutate).toHaveBeenCalledWith({
+ mutation: toggleResolveDiscussionMutation,
+ variables: {
+ id: discussion.id,
+ resolve: true,
+ },
+ });
+ return wrapper.vm.$nextTick(() => {
+ expect(findResolveLoadingIcon().exists()).toBe(true);
+ });
+ });
+
+ it('calls toggleResolveDiscussion mutation after adding a note if checkbox was checked', () => {
+ createComponent(
+ { discussionWithOpenForm: discussion.id },
+ { discussionComment: 'test', isFormRendered: true },
+ );
+ findResolveButton().trigger('click');
+ findReplyForm().vm.$emit('submitForm');
+
+ return mutate().then(() => {
+ expect(mutate).toHaveBeenCalledWith({
+ mutation: toggleResolveDiscussionMutation,
+ variables: {
+ id: discussion.id,
+ resolve: true,
+ },
+ });
+ });
+ });
+
+ it('emits openForm event on opening the form', () => {
+ createComponent();
+ findReplyPlaceholder().vm.$emit('onClick');
+
+ expect(wrapper.emitted('openForm')).toBeTruthy();
+ });
+});
diff --git a/spec/frontend/design_management_new/components/design_notes/design_note_spec.js b/spec/frontend/design_management_new/components/design_notes/design_note_spec.js
new file mode 100644
index 00000000000..b0e3e85b9c6
--- /dev/null
+++ b/spec/frontend/design_management_new/components/design_notes/design_note_spec.js
@@ -0,0 +1,170 @@
+import { shallowMount } from '@vue/test-utils';
+import { ApolloMutation } from 'vue-apollo';
+import DesignNote from '~/design_management_new/components/design_notes/design_note.vue';
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import DesignReplyForm from '~/design_management_new/components/design_notes/design_reply_form.vue';
+
+const scrollIntoViewMock = jest.fn();
+const note = {
+ id: 'gid://gitlab/DiffNote/123',
+ author: {
+ id: 'author-id',
+ },
+ body: 'test',
+ userPermissions: {
+ adminNote: false,
+ },
+};
+HTMLElement.prototype.scrollIntoView = scrollIntoViewMock;
+
+const $route = {
+ hash: '#note_123',
+};
+
+const mutate = jest.fn().mockResolvedValue({ data: { updateNote: {} } });
+
+describe('Design note component', () => {
+ let wrapper;
+
+ const findUserAvatar = () => wrapper.find(UserAvatarLink);
+ const findUserLink = () => wrapper.find('.js-user-link');
+ const findReplyForm = () => wrapper.find(DesignReplyForm);
+ const findEditButton = () => wrapper.find('.js-note-edit');
+ const findNoteContent = () => wrapper.find('.js-note-text');
+
+ function createComponent(props = {}, data = { isEditing: false }) {
+ wrapper = shallowMount(DesignNote, {
+ propsData: {
+ note: {},
+ ...props,
+ },
+ data() {
+ return {
+ ...data,
+ };
+ },
+ mocks: {
+ $route,
+ $apollo: {
+ mutate,
+ },
+ },
+ stubs: {
+ ApolloMutation,
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('should match the snapshot', () => {
+ createComponent({
+ note,
+ });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('should render an author', () => {
+ createComponent({
+ note,
+ });
+
+ expect(findUserAvatar().exists()).toBe(true);
+ expect(findUserLink().exists()).toBe(true);
+ });
+
+ it('should render a time ago tooltip if note has createdAt property', () => {
+ createComponent({
+ note: {
+ ...note,
+ createdAt: '2019-07-26T15:02:20Z',
+ },
+ });
+
+ expect(wrapper.find(TimeAgoTooltip).exists()).toBe(true);
+ });
+
+ it('should trigger a scrollIntoView method', () => {
+ createComponent({
+ note,
+ });
+
+ expect(scrollIntoViewMock).toHaveBeenCalled();
+ });
+
+ it('should not render edit icon when user does not have a permission', () => {
+ createComponent({
+ note,
+ });
+
+ expect(findEditButton().exists()).toBe(false);
+ });
+
+ describe('when user has a permission to edit note', () => {
+ it('should open an edit form on edit button click', () => {
+ createComponent({
+ note: {
+ ...note,
+ userPermissions: {
+ adminNote: true,
+ },
+ },
+ });
+
+ findEditButton().trigger('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findReplyForm().exists()).toBe(true);
+ expect(findNoteContent().exists()).toBe(false);
+ });
+ });
+
+ describe('when edit form is rendered', () => {
+ beforeEach(() => {
+ createComponent(
+ {
+ note: {
+ ...note,
+ userPermissions: {
+ adminNote: true,
+ },
+ },
+ },
+ { isEditing: true },
+ );
+ });
+
+ it('should not render note content and should render reply form', () => {
+ expect(findNoteContent().exists()).toBe(false);
+ expect(findReplyForm().exists()).toBe(true);
+ });
+
+ it('hides the form on hideForm event', () => {
+ findReplyForm().vm.$emit('cancelForm');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findReplyForm().exists()).toBe(false);
+ expect(findNoteContent().exists()).toBe(true);
+ });
+ });
+
+ it('calls a mutation on submitForm event and hides a form', () => {
+ findReplyForm().vm.$emit('submitForm');
+ expect(mutate).toHaveBeenCalled();
+
+ return mutate()
+ .then(() => {
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(findReplyForm().exists()).toBe(false);
+ expect(findNoteContent().exists()).toBe(true);
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/design_notes/design_reply_form_spec.js b/spec/frontend/design_management_new/components/design_notes/design_reply_form_spec.js
new file mode 100644
index 00000000000..9c1d6154516
--- /dev/null
+++ b/spec/frontend/design_management_new/components/design_notes/design_reply_form_spec.js
@@ -0,0 +1,184 @@
+import { mount } from '@vue/test-utils';
+import DesignReplyForm from '~/design_management_new/components/design_notes/design_reply_form.vue';
+
+const showModal = jest.fn();
+
+const GlModal = {
+ template: '<div><slot name="modal-title"></slot><slot></slot><slot name="modal-ok"></slot></div>',
+ methods: {
+ show: showModal,
+ },
+};
+
+describe('Design reply form component', () => {
+ let wrapper;
+
+ const findTextarea = () => wrapper.find('textarea');
+ const findSubmitButton = () => wrapper.find({ ref: 'submitButton' });
+ const findCancelButton = () => wrapper.find({ ref: 'cancelButton' });
+ const findModal = () => wrapper.find({ ref: 'cancelCommentModal' });
+
+ function createComponent(props = {}, mountOptions = {}) {
+ wrapper = mount(DesignReplyForm, {
+ propsData: {
+ value: '',
+ isSaving: false,
+ ...props,
+ },
+ stubs: { GlModal },
+ ...mountOptions,
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('textarea has focus after component mount', () => {
+ // We need to attach to document, so that `document.activeElement` is properly set in jsdom
+ createComponent({}, { attachToDocument: true });
+
+ expect(findTextarea().element).toEqual(document.activeElement);
+ });
+
+ it('renders button text as "Comment" when creating a comment', () => {
+ createComponent();
+
+ expect(findSubmitButton().html()).toMatchSnapshot();
+ });
+
+ it('renders button text as "Save comment" when creating a comment', () => {
+ createComponent({ isNewComment: false });
+
+ expect(findSubmitButton().html()).toMatchSnapshot();
+ });
+
+ describe('when form has no text', () => {
+ beforeEach(() => {
+ createComponent({
+ value: '',
+ });
+ });
+
+ it('submit button is disabled', () => {
+ expect(findSubmitButton().attributes().disabled).toBeTruthy();
+ });
+
+ it('does not emit submitForm event on textarea ctrl+enter keydown', () => {
+ findTextarea().trigger('keydown.enter', {
+ ctrlKey: true,
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted('submitForm')).toBeFalsy();
+ });
+ });
+
+ it('does not emit submitForm event on textarea meta+enter keydown', () => {
+ findTextarea().trigger('keydown.enter', {
+ metaKey: true,
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted('submitForm')).toBeFalsy();
+ });
+ });
+
+ it('emits cancelForm event on pressing escape button on textarea', () => {
+ findTextarea().trigger('keyup.esc');
+
+ expect(wrapper.emitted('cancelForm')).toBeTruthy();
+ });
+
+ it('emits cancelForm event on clicking Cancel button', () => {
+ findCancelButton().vm.$emit('click');
+
+ expect(wrapper.emitted('cancelForm')).toHaveLength(1);
+ });
+ });
+
+ describe('when form has text', () => {
+ beforeEach(() => {
+ createComponent({
+ value: 'test',
+ });
+ });
+
+ it('submit button is enabled', () => {
+ expect(findSubmitButton().attributes().disabled).toBeFalsy();
+ });
+
+ it('emits submitForm event on Comment button click', () => {
+ findSubmitButton().vm.$emit('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted('submitForm')).toBeTruthy();
+ });
+ });
+
+ it('emits submitForm event on textarea ctrl+enter keydown', () => {
+ findTextarea().trigger('keydown.enter', {
+ ctrlKey: true,
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted('submitForm')).toBeTruthy();
+ });
+ });
+
+ it('emits submitForm event on textarea meta+enter keydown', () => {
+ findTextarea().trigger('keydown.enter', {
+ metaKey: true,
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted('submitForm')).toBeTruthy();
+ });
+ });
+
+ it('emits input event on changing textarea content', () => {
+ findTextarea().setValue('test2');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted('input')).toBeTruthy();
+ });
+ });
+
+ it('emits cancelForm event on Escape key if text was not changed', () => {
+ findTextarea().trigger('keyup.esc');
+
+ expect(wrapper.emitted('cancelForm')).toBeTruthy();
+ });
+
+ it('opens confirmation modal on Escape key when text has changed', () => {
+ wrapper.setProps({ value: 'test2' });
+
+ return wrapper.vm.$nextTick().then(() => {
+ findTextarea().trigger('keyup.esc');
+ expect(showModal).toHaveBeenCalled();
+ });
+ });
+
+ it('emits cancelForm event on Cancel button click if text was not changed', () => {
+ findCancelButton().trigger('click');
+
+ expect(wrapper.emitted('cancelForm')).toBeTruthy();
+ });
+
+ it('opens confirmation modal on Cancel button click when text has changed', () => {
+ wrapper.setProps({ value: 'test2' });
+
+ return wrapper.vm.$nextTick().then(() => {
+ findCancelButton().trigger('click');
+ expect(showModal).toHaveBeenCalled();
+ });
+ });
+
+ it('emits cancelForm event on modal Ok button click', () => {
+ findTextarea().trigger('keyup.esc');
+ findModal().vm.$emit('ok');
+
+ expect(wrapper.emitted('cancelForm')).toBeTruthy();
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/design_notes/toggle_replies_widget_spec.js b/spec/frontend/design_management_new/components/design_notes/toggle_replies_widget_spec.js
new file mode 100644
index 00000000000..d3c89075a24
--- /dev/null
+++ b/spec/frontend/design_management_new/components/design_notes/toggle_replies_widget_spec.js
@@ -0,0 +1,98 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlIcon, GlButton, GlLink } from '@gitlab/ui';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import ToggleRepliesWidget from '~/design_management_new/components/design_notes/toggle_replies_widget.vue';
+import notes from '../../mock_data/notes';
+
+describe('Toggle replies widget component', () => {
+ let wrapper;
+
+ const findToggleWrapper = () => wrapper.find('[data-testid="toggle-comments-wrapper"]');
+ const findIcon = () => wrapper.find(GlIcon);
+ const findButton = () => wrapper.find(GlButton);
+ const findAuthorLink = () => wrapper.find(GlLink);
+ const findTimeAgo = () => wrapper.find(TimeAgoTooltip);
+
+ function createComponent(props = {}) {
+ wrapper = shallowMount(ToggleRepliesWidget, {
+ propsData: {
+ collapsed: true,
+ replies: notes,
+ ...props,
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when replies are collapsed', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('should not have expanded class', () => {
+ expect(findToggleWrapper().classes()).not.toContain('expanded');
+ });
+
+ it('should render chevron-right icon', () => {
+ expect(findIcon().props('name')).toBe('chevron-right');
+ });
+
+ it('should have replies length on button', () => {
+ expect(findButton().text()).toBe('2 replies');
+ });
+
+ it('should render a link to the last reply author', () => {
+ expect(findAuthorLink().exists()).toBe(true);
+ expect(findAuthorLink().text()).toBe(notes[1].author.name);
+ expect(findAuthorLink().attributes('href')).toBe(notes[1].author.webUrl);
+ });
+
+ it('should render correct time ago tooltip', () => {
+ expect(findTimeAgo().exists()).toBe(true);
+ expect(findTimeAgo().props('time')).toBe(notes[1].createdAt);
+ });
+ });
+
+ describe('when replies are expanded', () => {
+ beforeEach(() => {
+ createComponent({ collapsed: false });
+ });
+
+ it('should have expanded class', () => {
+ expect(findToggleWrapper().classes()).toContain('expanded');
+ });
+
+ it('should render chevron-down icon', () => {
+ expect(findIcon().props('name')).toBe('chevron-down');
+ });
+
+ it('should have Collapse replies text on button', () => {
+ expect(findButton().text()).toBe('Collapse replies');
+ });
+
+ it('should not have a link to the last reply author', () => {
+ expect(findAuthorLink().exists()).toBe(false);
+ });
+
+ it('should not render time ago tooltip', () => {
+ expect(findTimeAgo().exists()).toBe(false);
+ });
+ });
+
+ it('should emit toggle event on icon click', () => {
+ createComponent();
+ findIcon().vm.$emit('click', new MouseEvent('click'));
+
+ expect(wrapper.emitted('toggle')).toHaveLength(1);
+ });
+
+ it('should emit toggle event on button click', () => {
+ createComponent();
+ findButton().vm.$emit('click', new MouseEvent('click'));
+
+ expect(wrapper.emitted('toggle')).toHaveLength(1);
+ });
+});
diff --git a/spec/frontend/design_management_new/components/design_overlay_spec.js b/spec/frontend/design_management_new/components/design_overlay_spec.js
new file mode 100644
index 00000000000..4ca69c143a8
--- /dev/null
+++ b/spec/frontend/design_management_new/components/design_overlay_spec.js
@@ -0,0 +1,410 @@
+import { mount } from '@vue/test-utils';
+import DesignOverlay from '~/design_management_new/components/design_overlay.vue';
+import updateActiveDiscussion from '~/design_management_new/graphql/mutations/update_active_discussion.mutation.graphql';
+import notes from '../mock_data/notes';
+import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '~/design_management_new/constants';
+
+const mutate = jest.fn(() => Promise.resolve());
+
+describe('Design overlay component', () => {
+ let wrapper;
+
+ const mockDimensions = { width: 100, height: 100 };
+
+ const findOverlay = () => wrapper.find('.image-diff-overlay');
+ const findAllNotes = () => wrapper.findAll('.js-image-badge');
+ const findCommentBadge = () => wrapper.find('.comment-indicator');
+ const findFirstBadge = () => findAllNotes().at(0);
+ const findSecondBadge = () => findAllNotes().at(1);
+
+ const clickAndDragBadge = (elem, fromPoint, toPoint) => {
+ elem.trigger('mousedown', { clientX: fromPoint.x, clientY: fromPoint.y });
+ return wrapper.vm.$nextTick().then(() => {
+ elem.trigger('mousemove', { clientX: toPoint.x, clientY: toPoint.y });
+ return wrapper.vm.$nextTick();
+ });
+ };
+
+ function createComponent(props = {}, data = {}) {
+ wrapper = mount(DesignOverlay, {
+ propsData: {
+ dimensions: mockDimensions,
+ position: {
+ top: '0',
+ left: '0',
+ },
+ resolvedDiscussionsExpanded: false,
+ ...props,
+ },
+ data() {
+ return {
+ activeDiscussion: {
+ id: null,
+ source: null,
+ },
+ ...data,
+ };
+ },
+ mocks: {
+ $apollo: {
+ mutate,
+ },
+ },
+ });
+ }
+
+ it('should have correct inline style', () => {
+ createComponent();
+
+ expect(wrapper.find('.image-diff-overlay').attributes().style).toBe(
+ 'width: 100px; height: 100px; top: 0px; left: 0px;',
+ );
+ });
+
+ it('should emit `openCommentForm` when clicking on overlay', () => {
+ createComponent();
+ const newCoordinates = {
+ x: 10,
+ y: 10,
+ };
+
+ wrapper
+ .find('.image-diff-overlay-add-comment')
+ .trigger('mouseup', { offsetX: newCoordinates.x, offsetY: newCoordinates.y });
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted('openCommentForm')).toEqual([
+ [{ x: newCoordinates.x, y: newCoordinates.y }],
+ ]);
+ });
+ });
+
+ describe('with notes', () => {
+ it('should render only the first note', () => {
+ createComponent({
+ notes,
+ });
+ expect(findAllNotes()).toHaveLength(1);
+ });
+
+ describe('with resolved discussions toggle expanded', () => {
+ beforeEach(() => {
+ createComponent({
+ notes,
+ resolvedDiscussionsExpanded: true,
+ });
+ });
+
+ it('should render all notes', () => {
+ expect(findAllNotes()).toHaveLength(notes.length);
+ });
+
+ it('should have set the correct position for each note badge', () => {
+ expect(findFirstBadge().attributes().style).toBe('left: 10px; top: 15px;');
+ expect(findSecondBadge().attributes().style).toBe('left: 50px; top: 50px;');
+ });
+
+ it('should apply resolved class to the resolved note pin', () => {
+ expect(findSecondBadge().classes()).toContain('resolved');
+ });
+
+ it('when there is an active discussion, should apply inactive class to all pins besides the active one', () => {
+ wrapper.setData({
+ activeDiscussion: {
+ id: notes[0].id,
+ source: 'discussion',
+ },
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findSecondBadge().classes()).toContain('inactive');
+ });
+ });
+ });
+
+ it('should recalculate badges positions on window resize', () => {
+ createComponent({
+ notes,
+ dimensions: {
+ width: 400,
+ height: 400,
+ },
+ });
+
+ expect(findFirstBadge().attributes().style).toBe('left: 40px; top: 60px;');
+
+ wrapper.setProps({
+ dimensions: {
+ width: 200,
+ height: 200,
+ },
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findFirstBadge().attributes().style).toBe('left: 20px; top: 30px;');
+ });
+ });
+
+ it('should call an update active discussion mutation when clicking a note without moving it', () => {
+ const note = notes[0];
+ const { position } = note;
+ const mutationVariables = {
+ mutation: updateActiveDiscussion,
+ variables: {
+ id: note.id,
+ source: ACTIVE_DISCUSSION_SOURCE_TYPES.pin,
+ },
+ };
+
+ findFirstBadge().trigger('mousedown', { clientX: position.x, clientY: position.y });
+
+ return wrapper.vm.$nextTick().then(() => {
+ findFirstBadge().trigger('mouseup', { clientX: position.x, clientY: position.y });
+ expect(mutate).toHaveBeenCalledWith(mutationVariables);
+ });
+ });
+ });
+
+ describe('when moving notes', () => {
+ it('should update badge style when note is being moved', () => {
+ createComponent({
+ notes,
+ });
+
+ const { position } = notes[0];
+
+ return clickAndDragBadge(
+ findFirstBadge(),
+ { x: position.x, y: position.y },
+ { x: 20, y: 20 },
+ ).then(() => {
+ expect(findFirstBadge().attributes().style).toBe('left: 20px; top: 20px; cursor: move;');
+ });
+ });
+
+ it('should emit `moveNote` event when note-moving action ends', () => {
+ createComponent({ notes });
+ const note = notes[0];
+ const { position } = note;
+ const newCoordinates = { x: 20, y: 20 };
+
+ wrapper.setData({
+ movingNoteNewPosition: {
+ ...position,
+ ...newCoordinates,
+ },
+ movingNoteStartPosition: {
+ noteId: notes[0].id,
+ discussionId: notes[0].discussion.id,
+ ...position,
+ },
+ });
+
+ const badge = findFirstBadge();
+ return clickAndDragBadge(badge, { x: position.x, y: position.y }, newCoordinates)
+ .then(() => {
+ badge.trigger('mouseup');
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(wrapper.emitted('moveNote')).toEqual([
+ [
+ {
+ noteId: notes[0].id,
+ discussionId: notes[0].discussion.id,
+ coordinates: newCoordinates,
+ },
+ ],
+ ]);
+ });
+ });
+
+ describe('without [adminNote] permission', () => {
+ const mockNoteNotAuthorised = {
+ ...notes[0],
+ userPermissions: {
+ adminNote: false,
+ },
+ };
+
+ const mockNoteCoordinates = {
+ x: mockNoteNotAuthorised.position.x,
+ y: mockNoteNotAuthorised.position.y,
+ };
+
+ it('should be unable to move a note', () => {
+ createComponent({
+ dimensions: mockDimensions,
+ notes: [mockNoteNotAuthorised],
+ });
+
+ const badge = findAllNotes().at(0);
+ return clickAndDragBadge(badge, { ...mockNoteCoordinates }, { x: 20, y: 20 }).then(() => {
+ // note position should not change after a click-and-drag attempt
+ expect(findFirstBadge().attributes().style).toContain(
+ `left: ${mockNoteCoordinates.x}px; top: ${mockNoteCoordinates.y}px;`,
+ );
+ });
+ });
+ });
+ });
+
+ describe('with a new form', () => {
+ it('should render a new comment badge', () => {
+ createComponent({
+ currentCommentForm: {
+ ...notes[0].position,
+ },
+ });
+
+ expect(findCommentBadge().exists()).toBe(true);
+ expect(findCommentBadge().attributes().style).toBe('left: 10px; top: 15px;');
+ });
+
+ describe('when moving the comment badge', () => {
+ it('should update badge style to reflect new position', () => {
+ const { position } = notes[0];
+
+ createComponent({
+ currentCommentForm: {
+ ...position,
+ },
+ });
+
+ return clickAndDragBadge(
+ findCommentBadge(),
+ { x: position.x, y: position.y },
+ { x: 20, y: 20 },
+ ).then(() => {
+ expect(findCommentBadge().attributes().style).toBe(
+ 'left: 20px; top: 20px; cursor: move;',
+ );
+ });
+ });
+
+ it('should update badge style when note-moving action ends', () => {
+ const { position } = notes[0];
+ createComponent({
+ currentCommentForm: {
+ ...position,
+ },
+ });
+
+ const commentBadge = findCommentBadge();
+ const toPoint = { x: 20, y: 20 };
+
+ return clickAndDragBadge(commentBadge, { x: position.x, y: position.y }, toPoint)
+ .then(() => {
+ commentBadge.trigger('mouseup');
+ // simulates the currentCommentForm being updated in index.vue component, and
+ // propagated back down to this prop
+ wrapper.setProps({
+ currentCommentForm: { height: position.height, width: position.width, ...toPoint },
+ });
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(commentBadge.attributes().style).toBe('left: 20px; top: 20px;');
+ });
+ });
+
+ it.each`
+ element | getElementFunc | event
+ ${'overlay'} | ${findOverlay} | ${'mouseleave'}
+ ${'comment badge'} | ${findCommentBadge} | ${'mouseup'}
+ `(
+ 'should emit `openCommentForm` event when $event fired on $element element',
+ ({ getElementFunc, event }) => {
+ createComponent({
+ notes,
+ currentCommentForm: {
+ ...notes[0].position,
+ },
+ });
+
+ const newCoordinates = { x: 20, y: 20 };
+ wrapper.setData({
+ movingNoteStartPosition: {
+ ...notes[0].position,
+ },
+ movingNoteNewPosition: {
+ ...notes[0].position,
+ ...newCoordinates,
+ },
+ });
+
+ getElementFunc().trigger(event);
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted('openCommentForm')).toEqual([[newCoordinates]]);
+ });
+ },
+ );
+ });
+ });
+
+ describe('getMovingNotePositionDelta', () => {
+ it('should calculate delta correctly from state', () => {
+ createComponent();
+
+ wrapper.setData({
+ movingNoteStartPosition: {
+ clientX: 10,
+ clientY: 20,
+ },
+ });
+
+ const mockMouseEvent = {
+ clientX: 30,
+ clientY: 10,
+ };
+
+ expect(wrapper.vm.getMovingNotePositionDelta(mockMouseEvent)).toEqual({
+ deltaX: 20,
+ deltaY: -10,
+ });
+ });
+ });
+
+ describe('isPositionInOverlay', () => {
+ createComponent({ dimensions: mockDimensions });
+
+ it.each`
+ test | coordinates | expectedResult
+ ${'within overlay bounds'} | ${{ x: 50, y: 50 }} | ${true}
+ ${'outside overlay bounds'} | ${{ x: 101, y: 101 }} | ${false}
+ `('returns [$expectedResult] when position is $test', ({ coordinates, expectedResult }) => {
+ const position = { ...mockDimensions, ...coordinates };
+
+ expect(wrapper.vm.isPositionInOverlay(position)).toBe(expectedResult);
+ });
+ });
+
+ describe('getNoteRelativePosition', () => {
+ it('calculates position correctly', () => {
+ createComponent({ dimensions: mockDimensions });
+ const position = { x: 50, y: 50, width: 200, height: 200 };
+
+ expect(wrapper.vm.getNoteRelativePosition(position)).toEqual({ left: 25, top: 25 });
+ });
+ });
+
+ describe('canMoveNote', () => {
+ it.each`
+ adminNotePermission | canMoveNoteResult
+ ${true} | ${true}
+ ${false} | ${false}
+ ${undefined} | ${false}
+ `(
+ 'returns [$canMoveNoteResult] when [adminNote permission] is [$adminNotePermission]',
+ ({ adminNotePermission, canMoveNoteResult }) => {
+ createComponent();
+
+ const note = {
+ userPermissions: {
+ adminNote: adminNotePermission,
+ },
+ };
+ expect(wrapper.vm.canMoveNote(note)).toBe(canMoveNoteResult);
+ },
+ );
+ });
+});
diff --git a/spec/frontend/design_management_new/components/design_presentation_spec.js b/spec/frontend/design_management_new/components/design_presentation_spec.js
new file mode 100644
index 00000000000..d043a762cd2
--- /dev/null
+++ b/spec/frontend/design_management_new/components/design_presentation_spec.js
@@ -0,0 +1,553 @@
+import { shallowMount } from '@vue/test-utils';
+import DesignPresentation from '~/design_management_new/components/design_presentation.vue';
+import DesignOverlay from '~/design_management_new/components/design_overlay.vue';
+
+const mockOverlayData = {
+ overlayDimensions: {
+ width: 100,
+ height: 100,
+ },
+ overlayPosition: {
+ top: '0',
+ left: '0',
+ },
+};
+
+describe('Design management design presentation component', () => {
+ let wrapper;
+
+ function createComponent(
+ {
+ image,
+ imageName,
+ discussions = [],
+ isAnnotating = false,
+ resolvedDiscussionsExpanded = false,
+ } = {},
+ data = {},
+ stubs = {},
+ ) {
+ wrapper = shallowMount(DesignPresentation, {
+ propsData: {
+ image,
+ imageName,
+ discussions,
+ isAnnotating,
+ resolvedDiscussionsExpanded,
+ },
+ stubs,
+ });
+
+ wrapper.setData(data);
+ wrapper.element.scrollTo = jest.fn();
+ }
+
+ const findOverlayCommentButton = () => wrapper.find('.image-diff-overlay-add-comment');
+
+ /**
+ * Spy on $refs and mock given values
+ * @param {Object} viewportDimensions {width, height}
+ * @param {Object} childDimensions {width, height}
+ * @param {Float} scrollTopPerc 0 < x < 1
+ * @param {Float} scrollLeftPerc 0 < x < 1
+ */
+ function mockRefDimensions(
+ ref,
+ viewportDimensions,
+ childDimensions,
+ scrollTopPerc,
+ scrollLeftPerc,
+ ) {
+ jest.spyOn(ref, 'scrollWidth', 'get').mockReturnValue(childDimensions.width);
+ jest.spyOn(ref, 'scrollHeight', 'get').mockReturnValue(childDimensions.height);
+ jest.spyOn(ref, 'offsetWidth', 'get').mockReturnValue(viewportDimensions.width);
+ jest.spyOn(ref, 'offsetHeight', 'get').mockReturnValue(viewportDimensions.height);
+ jest
+ .spyOn(ref, 'scrollLeft', 'get')
+ .mockReturnValue((childDimensions.width - viewportDimensions.width) * scrollLeftPerc);
+ jest
+ .spyOn(ref, 'scrollTop', 'get')
+ .mockReturnValue((childDimensions.height - viewportDimensions.height) * scrollTopPerc);
+ }
+
+ function clickDragExplore(startCoords, endCoords, { useTouchEvents, mouseup } = {}) {
+ const event = useTouchEvents
+ ? {
+ mousedown: 'touchstart',
+ mousemove: 'touchmove',
+ mouseup: 'touchend',
+ }
+ : {
+ mousedown: 'mousedown',
+ mousemove: 'mousemove',
+ mouseup: 'mouseup',
+ };
+
+ const addCommentOverlay = findOverlayCommentButton();
+
+ // triggering mouse events on this element best simulates
+ // reality, as it is the lowest-level node that needs to
+ // respond to mouse events
+ addCommentOverlay.trigger(event.mousedown, {
+ clientX: startCoords.clientX,
+ clientY: startCoords.clientY,
+ });
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ addCommentOverlay.trigger(event.mousemove, {
+ clientX: endCoords.clientX,
+ clientY: endCoords.clientY,
+ });
+
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ if (mouseup) {
+ addCommentOverlay.trigger(event.mouseup);
+ return wrapper.vm.$nextTick();
+ }
+
+ return undefined;
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders image and overlay when image provided', () => {
+ createComponent(
+ {
+ image: 'test.jpg',
+ imageName: 'test',
+ },
+ mockOverlayData,
+ );
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ it('renders empty state when no image provided', () => {
+ createComponent();
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ it('openCommentForm event emits correct data', () => {
+ createComponent(
+ {
+ image: 'test.jpg',
+ imageName: 'test',
+ },
+ mockOverlayData,
+ );
+
+ wrapper.vm.openCommentForm({ x: 1, y: 1 });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted('openCommentForm')).toEqual([
+ [{ ...mockOverlayData.overlayDimensions, x: 1, y: 1 }],
+ ]);
+ });
+ });
+
+ describe('currentCommentForm', () => {
+ it('is null when isAnnotating is false', () => {
+ createComponent(
+ {
+ image: 'test.jpg',
+ imageName: 'test',
+ },
+ mockOverlayData,
+ );
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.vm.currentCommentForm).toBeNull();
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ it('is null when isAnnotating is true but annotation position is falsey', () => {
+ createComponent(
+ {
+ image: 'test.jpg',
+ imageName: 'test',
+ isAnnotating: true,
+ },
+ mockOverlayData,
+ );
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.vm.currentCommentForm).toBeNull();
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ it('is equal to current annotation position when isAnnotating is true', () => {
+ createComponent(
+ {
+ image: 'test.jpg',
+ imageName: 'test',
+ isAnnotating: true,
+ },
+ {
+ ...mockOverlayData,
+ currentAnnotationPosition: {
+ x: 1,
+ y: 1,
+ width: 100,
+ height: 100,
+ },
+ },
+ );
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.vm.currentCommentForm).toEqual({
+ x: 1,
+ y: 1,
+ width: 100,
+ height: 100,
+ });
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+ });
+
+ describe('setOverlayPosition', () => {
+ beforeEach(() => {
+ createComponent(
+ {
+ image: 'test.jpg',
+ imageName: 'test',
+ },
+ mockOverlayData,
+ );
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('sets overlay position correctly when overlay is smaller than viewport', () => {
+ jest.spyOn(wrapper.vm.$refs.presentationViewport, 'offsetWidth', 'get').mockReturnValue(200);
+ jest.spyOn(wrapper.vm.$refs.presentationViewport, 'offsetHeight', 'get').mockReturnValue(200);
+
+ wrapper.vm.setOverlayPosition();
+ expect(wrapper.vm.overlayPosition).toEqual({
+ left: `calc(50% - ${mockOverlayData.overlayDimensions.width / 2}px)`,
+ top: `calc(50% - ${mockOverlayData.overlayDimensions.height / 2}px)`,
+ });
+ });
+
+ it('sets overlay position correctly when overlay width is larger than viewports', () => {
+ jest.spyOn(wrapper.vm.$refs.presentationViewport, 'offsetWidth', 'get').mockReturnValue(50);
+ jest.spyOn(wrapper.vm.$refs.presentationViewport, 'offsetHeight', 'get').mockReturnValue(200);
+
+ wrapper.vm.setOverlayPosition();
+ expect(wrapper.vm.overlayPosition).toEqual({
+ left: '0',
+ top: `calc(50% - ${mockOverlayData.overlayDimensions.height / 2}px)`,
+ });
+ });
+
+ it('sets overlay position correctly when overlay height is larger than viewports', () => {
+ jest.spyOn(wrapper.vm.$refs.presentationViewport, 'offsetWidth', 'get').mockReturnValue(200);
+ jest.spyOn(wrapper.vm.$refs.presentationViewport, 'offsetHeight', 'get').mockReturnValue(50);
+
+ wrapper.vm.setOverlayPosition();
+ expect(wrapper.vm.overlayPosition).toEqual({
+ left: `calc(50% - ${mockOverlayData.overlayDimensions.width / 2}px)`,
+ top: '0',
+ });
+ });
+ });
+
+ describe('getViewportCenter', () => {
+ beforeEach(() => {
+ createComponent(
+ {
+ image: 'test.jpg',
+ imageName: 'test',
+ },
+ mockOverlayData,
+ );
+ });
+
+ it('calculate center correctly with no scroll', () => {
+ mockRefDimensions(
+ wrapper.vm.$refs.presentationViewport,
+ { width: 10, height: 10 },
+ { width: 20, height: 20 },
+ 0,
+ 0,
+ );
+
+ expect(wrapper.vm.getViewportCenter()).toEqual({
+ x: 5,
+ y: 5,
+ });
+ });
+
+ it('calculate center correctly with some scroll', () => {
+ mockRefDimensions(
+ wrapper.vm.$refs.presentationViewport,
+ { width: 10, height: 10 },
+ { width: 20, height: 20 },
+ 0.5,
+ 0.5,
+ );
+
+ expect(wrapper.vm.getViewportCenter()).toEqual({
+ x: 10,
+ y: 10,
+ });
+ });
+
+ it('Returns default case if no overflow (scrollWidth==offsetWidth, etc.)', () => {
+ mockRefDimensions(
+ wrapper.vm.$refs.presentationViewport,
+ { width: 20, height: 20 },
+ { width: 20, height: 20 },
+ 0.5,
+ 0.5,
+ );
+
+ expect(wrapper.vm.getViewportCenter()).toEqual({
+ x: 10,
+ y: 10,
+ });
+ });
+ });
+
+ describe('scaleZoomFocalPoint', () => {
+ it('scales focal point correctly when zooming in', () => {
+ createComponent(
+ {
+ image: 'test.jpg',
+ imageName: 'test',
+ },
+ {
+ ...mockOverlayData,
+ zoomFocalPoint: {
+ x: 5,
+ y: 5,
+ width: 50,
+ height: 50,
+ },
+ },
+ );
+
+ wrapper.vm.scaleZoomFocalPoint();
+ expect(wrapper.vm.zoomFocalPoint).toEqual({
+ x: 10,
+ y: 10,
+ width: 100,
+ height: 100,
+ });
+ });
+
+ it('scales focal point correctly when zooming out', () => {
+ createComponent(
+ {
+ image: 'test.jpg',
+ imageName: 'test',
+ },
+ {
+ ...mockOverlayData,
+ zoomFocalPoint: {
+ x: 10,
+ y: 10,
+ width: 200,
+ height: 200,
+ },
+ },
+ );
+
+ wrapper.vm.scaleZoomFocalPoint();
+ expect(wrapper.vm.zoomFocalPoint).toEqual({
+ x: 5,
+ y: 5,
+ width: 100,
+ height: 100,
+ });
+ });
+ });
+
+ describe('onImageResize', () => {
+ it('sets zoom focal point on initial load', () => {
+ createComponent(
+ {
+ image: 'test.jpg',
+ imageName: 'test',
+ },
+ mockOverlayData,
+ );
+
+ wrapper.setMethods({
+ shiftZoomFocalPoint: jest.fn(),
+ scaleZoomFocalPoint: jest.fn(),
+ scrollToFocalPoint: jest.fn(),
+ });
+
+ wrapper.vm.onImageResize({ width: 10, height: 10 });
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.vm.shiftZoomFocalPoint).toHaveBeenCalled();
+ expect(wrapper.vm.initialLoad).toBe(false);
+ });
+ });
+
+ it('calls scaleZoomFocalPoint and scrollToFocalPoint after initial load', () => {
+ wrapper.vm.onImageResize({ width: 10, height: 10 });
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.vm.scaleZoomFocalPoint).toHaveBeenCalled();
+ expect(wrapper.vm.scrollToFocalPoint).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('onPresentationMousedown', () => {
+ it.each`
+ scenario | width | height
+ ${'width overflows'} | ${101} | ${100}
+ ${'height overflows'} | ${100} | ${101}
+ ${'width and height overflows'} | ${200} | ${200}
+ `('sets lastDragPosition when design $scenario', ({ width, height }) => {
+ createComponent();
+ mockRefDimensions(
+ wrapper.vm.$refs.presentationViewport,
+ { width: 100, height: 100 },
+ { width, height },
+ );
+
+ const newLastDragPosition = { x: 2, y: 2 };
+ wrapper.vm.onPresentationMousedown({
+ clientX: newLastDragPosition.x,
+ clientY: newLastDragPosition.y,
+ });
+
+ expect(wrapper.vm.lastDragPosition).toStrictEqual(newLastDragPosition);
+ });
+
+ it('does not set lastDragPosition if design does not overflow', () => {
+ const lastDragPosition = { x: 1, y: 1 };
+
+ createComponent({}, { lastDragPosition });
+ mockRefDimensions(
+ wrapper.vm.$refs.presentationViewport,
+ { width: 100, height: 100 },
+ { width: 50, height: 50 },
+ );
+
+ wrapper.vm.onPresentationMousedown({ clientX: 2, clientY: 2 });
+
+ // check lastDragPosition is unchanged
+ expect(wrapper.vm.lastDragPosition).toStrictEqual(lastDragPosition);
+ });
+ });
+
+ describe('getAnnotationPositon', () => {
+ it.each`
+ coordinates | overlayDimensions | position
+ ${{ x: 100, y: 100 }} | ${{ width: 50, height: 50 }} | ${{ x: 100, y: 100, width: 50, height: 50 }}
+ ${{ x: 100.2, y: 100.5 }} | ${{ width: 50.6, height: 50.0 }} | ${{ x: 100, y: 101, width: 51, height: 50 }}
+ `('returns correct annotation position', ({ coordinates, overlayDimensions, position }) => {
+ createComponent(undefined, {
+ overlayDimensions: {
+ width: overlayDimensions.width,
+ height: overlayDimensions.height,
+ },
+ });
+
+ expect(wrapper.vm.getAnnotationPositon(coordinates)).toStrictEqual(position);
+ });
+ });
+
+ describe('when design is overflowing', () => {
+ beforeEach(() => {
+ createComponent(
+ {
+ image: 'test.jpg',
+ imageName: 'test',
+ },
+ mockOverlayData,
+ {
+ 'design-overlay': DesignOverlay,
+ },
+ );
+
+ // mock a design that overflows
+ mockRefDimensions(
+ wrapper.vm.$refs.presentationViewport,
+ { width: 10, height: 10 },
+ { width: 20, height: 20 },
+ 0,
+ 0,
+ );
+ });
+
+ it('opens a comment form if design was not dragged', () => {
+ const addCommentOverlay = findOverlayCommentButton();
+ const startCoords = {
+ clientX: 1,
+ clientY: 1,
+ };
+
+ addCommentOverlay.trigger('mousedown', {
+ clientX: startCoords.clientX,
+ clientY: startCoords.clientY,
+ });
+
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ addCommentOverlay.trigger('mouseup');
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(wrapper.emitted('openCommentForm')).toBeDefined();
+ });
+ });
+
+ describe('when clicking and dragging', () => {
+ it.each`
+ description | useTouchEvents
+ ${'with touch events'} | ${true}
+ ${'without touch events'} | ${false}
+ `('calls scrollTo with correct arguments $description', ({ useTouchEvents }) => {
+ return clickDragExplore(
+ { clientX: 0, clientY: 0 },
+ { clientX: 10, clientY: 10 },
+ { useTouchEvents },
+ ).then(() => {
+ expect(wrapper.element.scrollTo).toHaveBeenCalledTimes(1);
+ expect(wrapper.element.scrollTo).toHaveBeenCalledWith(-10, -10);
+ });
+ });
+
+ it('does not open a comment form when drag position exceeds buffer', () => {
+ return clickDragExplore(
+ { clientX: 0, clientY: 0 },
+ { clientX: 10, clientY: 10 },
+ { mouseup: true },
+ ).then(() => {
+ expect(wrapper.emitted('openCommentForm')).toBeFalsy();
+ });
+ });
+
+ it('opens a comment form when drag position is within buffer', () => {
+ return clickDragExplore(
+ { clientX: 0, clientY: 0 },
+ { clientX: 1, clientY: 0 },
+ { mouseup: true },
+ ).then(() => {
+ expect(wrapper.emitted('openCommentForm')).toBeDefined();
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/design_scaler_spec.js b/spec/frontend/design_management_new/components/design_scaler_spec.js
new file mode 100644
index 00000000000..5ff2554cd60
--- /dev/null
+++ b/spec/frontend/design_management_new/components/design_scaler_spec.js
@@ -0,0 +1,67 @@
+import { shallowMount } from '@vue/test-utils';
+import DesignScaler from '~/design_management_new/components/design_scaler.vue';
+
+describe('Design management design scaler component', () => {
+ let wrapper;
+
+ function createComponent(propsData, data = {}) {
+ wrapper = shallowMount(DesignScaler, {
+ propsData,
+ });
+ wrapper.setData(data);
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const getButton = type => {
+ const buttonTypeOrder = ['minus', 'reset', 'plus'];
+ const buttons = wrapper.findAll('button');
+ return buttons.at(buttonTypeOrder.indexOf(type));
+ };
+
+ it('emits @scale event when "plus" button clicked', () => {
+ createComponent();
+
+ getButton('plus').trigger('click');
+ expect(wrapper.emitted('scale')).toEqual([[1.2]]);
+ });
+
+ it('emits @scale event when "reset" button clicked (scale > 1)', () => {
+ createComponent({}, { scale: 1.6 });
+ return wrapper.vm.$nextTick().then(() => {
+ getButton('reset').trigger('click');
+ expect(wrapper.emitted('scale')).toEqual([[1]]);
+ });
+ });
+
+ it('emits @scale event when "minus" button clicked (scale > 1)', () => {
+ createComponent({}, { scale: 1.6 });
+
+ return wrapper.vm.$nextTick().then(() => {
+ getButton('minus').trigger('click');
+ expect(wrapper.emitted('scale')).toEqual([[1.4]]);
+ });
+ });
+
+ it('minus and reset buttons are disabled when scale === 1', () => {
+ createComponent();
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('minus and reset buttons are enabled when scale > 1', () => {
+ createComponent({}, { scale: 1.2 });
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ it('plus button is disabled when scale === 2', () => {
+ createComponent({}, { scale: 2 });
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/design_sidebar_spec.js b/spec/frontend/design_management_new/components/design_sidebar_spec.js
new file mode 100644
index 00000000000..f1d442a7b21
--- /dev/null
+++ b/spec/frontend/design_management_new/components/design_sidebar_spec.js
@@ -0,0 +1,236 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlCollapse, GlPopover } from '@gitlab/ui';
+import Cookies from 'js-cookie';
+import DesignSidebar from '~/design_management_new/components/design_sidebar.vue';
+import Participants from '~/sidebar/components/participants/participants.vue';
+import DesignDiscussion from '~/design_management_new/components/design_notes/design_discussion.vue';
+import design from '../mock_data/design';
+import updateActiveDiscussionMutation from '~/design_management_new/graphql/mutations/update_active_discussion.mutation.graphql';
+
+const updateActiveDiscussionMutationVariables = {
+ mutation: updateActiveDiscussionMutation,
+ variables: {
+ id: design.discussions.nodes[0].notes.nodes[0].id,
+ source: 'discussion',
+ },
+};
+
+const $route = {
+ params: {
+ id: '1',
+ },
+};
+
+const cookieKey = 'hide_design_resolved_comments_popover';
+
+const mutate = jest.fn().mockResolvedValue();
+
+describe('Design management design sidebar component', () => {
+ let wrapper;
+
+ const findDiscussions = () => wrapper.findAll(DesignDiscussion);
+ const findFirstDiscussion = () => findDiscussions().at(0);
+ const findUnresolvedDiscussions = () => wrapper.findAll('[data-testid="unresolved-discussion"]');
+ const findResolvedDiscussions = () => wrapper.findAll('[data-testid="resolved-discussion"]');
+ const findParticipants = () => wrapper.find(Participants);
+ const findCollapsible = () => wrapper.find(GlCollapse);
+ const findToggleResolvedCommentsButton = () => wrapper.find('[data-testid="resolved-comments"]');
+ const findPopover = () => wrapper.find(GlPopover);
+ const findNewDiscussionDisclaimer = () =>
+ wrapper.find('[data-testid="new-discussion-disclaimer"]');
+
+ function createComponent(props = {}) {
+ wrapper = shallowMount(DesignSidebar, {
+ propsData: {
+ design,
+ resolvedDiscussionsExpanded: false,
+ markdownPreviewPath: '',
+ ...props,
+ },
+ mocks: {
+ $route,
+ $apollo: {
+ mutate,
+ },
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders participants', () => {
+ createComponent();
+
+ expect(findParticipants().exists()).toBe(true);
+ });
+
+ it('passes the correct amount of participants to the Participants component', () => {
+ createComponent();
+
+ expect(findParticipants().props('participants')).toHaveLength(1);
+ });
+
+ describe('when has no discussions', () => {
+ beforeEach(() => {
+ createComponent({
+ design: {
+ ...design,
+ discussions: {
+ nodes: [],
+ },
+ },
+ });
+ });
+
+ it('does not render discussions', () => {
+ expect(findDiscussions().exists()).toBe(false);
+ });
+
+ it('renders a message about possibility to create a new discussion', () => {
+ expect(findNewDiscussionDisclaimer().exists()).toBe(true);
+ });
+ });
+
+ describe('when has discussions', () => {
+ beforeEach(() => {
+ Cookies.set(cookieKey, true);
+ createComponent();
+ });
+
+ it('renders correct amount of unresolved discussions', () => {
+ expect(findUnresolvedDiscussions()).toHaveLength(1);
+ });
+
+ it('renders correct amount of resolved discussions', () => {
+ expect(findResolvedDiscussions()).toHaveLength(1);
+ });
+
+ it('has resolved comments collapsible collapsed', () => {
+ expect(findCollapsible().attributes('visible')).toBeUndefined();
+ });
+
+ it('emits toggleResolveComments event on resolve comments button click', () => {
+ findToggleResolvedCommentsButton().vm.$emit('click');
+ expect(wrapper.emitted('toggleResolvedComments')).toHaveLength(1);
+ });
+
+ it('opens a collapsible when resolvedDiscussionsExpanded prop changes to true', () => {
+ expect(findCollapsible().attributes('visible')).toBeUndefined();
+ wrapper.setProps({
+ resolvedDiscussionsExpanded: true,
+ });
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findCollapsible().attributes('visible')).toBe('true');
+ });
+ });
+
+ it('does not popover about resolved comments', () => {
+ expect(findPopover().exists()).toBe(false);
+ });
+
+ it('sends a mutation to set an active discussion when clicking on a discussion', () => {
+ findFirstDiscussion().trigger('click');
+
+ expect(mutate).toHaveBeenCalledWith(updateActiveDiscussionMutationVariables);
+ });
+
+ it('sends a mutation to reset an active discussion when clicking outside of discussion', () => {
+ wrapper.trigger('click');
+
+ expect(mutate).toHaveBeenCalledWith({
+ ...updateActiveDiscussionMutationVariables,
+ variables: { id: undefined, source: 'discussion' },
+ });
+ });
+
+ it('emits correct event on discussion create note error', () => {
+ findFirstDiscussion().vm.$emit('createNoteError', 'payload');
+ expect(wrapper.emitted('onDesignDiscussionError')).toEqual([['payload']]);
+ });
+
+ it('emits correct event on discussion update note error', () => {
+ findFirstDiscussion().vm.$emit('updateNoteError', 'payload');
+ expect(wrapper.emitted('updateNoteError')).toEqual([['payload']]);
+ });
+
+ it('emits correct event on discussion resolve error', () => {
+ findFirstDiscussion().vm.$emit('resolveDiscussionError', 'payload');
+ expect(wrapper.emitted('resolveDiscussionError')).toEqual([['payload']]);
+ });
+
+ it('changes prop correctly on opening discussion form', () => {
+ findFirstDiscussion().vm.$emit('openForm', 'some-id');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findFirstDiscussion().props('discussionWithOpenForm')).toBe('some-id');
+ });
+ });
+ });
+
+ describe('when all discussions are resolved', () => {
+ beforeEach(() => {
+ createComponent({
+ design: {
+ ...design,
+ discussions: {
+ nodes: [
+ {
+ id: 'discussion-id',
+ replyId: 'discussion-reply-id',
+ resolved: true,
+ notes: {
+ nodes: [
+ {
+ id: 'note-id',
+ body: '123',
+ author: {
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'link-to-author',
+ avatarUrl: 'link-to-avatar',
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ });
+ });
+
+ it('renders a message about possibility to create a new discussion', () => {
+ expect(findNewDiscussionDisclaimer().exists()).toBe(true);
+ });
+
+ it('does not render unresolved discussions', () => {
+ expect(findUnresolvedDiscussions()).toHaveLength(0);
+ });
+ });
+
+ describe('when showing resolved discussions for the first time', () => {
+ beforeEach(() => {
+ Cookies.set(cookieKey, false);
+ createComponent();
+ });
+
+ it('renders a popover if we show resolved comments collapsible for the first time', () => {
+ expect(findPopover().exists()).toBe(true);
+ });
+
+ it('dismisses a popover on the outside click', () => {
+ wrapper.trigger('click');
+ return wrapper.vm.$nextTick(() => {
+ expect(findPopover().exists()).toBe(false);
+ });
+ });
+
+ it(`sets a ${cookieKey} cookie on clicking outside the popover`, () => {
+ jest.spyOn(Cookies, 'set');
+ wrapper.trigger('click');
+ expect(Cookies.set).toHaveBeenCalledWith(cookieKey, 'true', { expires: 365 * 10 });
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/image_spec.js b/spec/frontend/design_management_new/components/image_spec.js
new file mode 100644
index 00000000000..c1a8a8767df
--- /dev/null
+++ b/spec/frontend/design_management_new/components/image_spec.js
@@ -0,0 +1,133 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlIcon } from '@gitlab/ui';
+import DesignImage from '~/design_management_new/components/image.vue';
+
+describe('Design management large image component', () => {
+ let wrapper;
+
+ function createComponent(propsData, data = {}) {
+ wrapper = shallowMount(DesignImage, {
+ propsData,
+ });
+ wrapper.setData(data);
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders loading state', () => {
+ createComponent({
+ isLoading: true,
+ });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders image', () => {
+ createComponent({
+ isLoading: false,
+ image: 'test.jpg',
+ name: 'test',
+ });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('sets correct classes and styles if imageStyle is set', () => {
+ createComponent(
+ {
+ isLoading: false,
+ image: 'test.jpg',
+ name: 'test',
+ },
+ {
+ imageStyle: {
+ width: '100px',
+ height: '100px',
+ },
+ },
+ );
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ it('renders media broken icon on error', () => {
+ createComponent({
+ isLoading: false,
+ image: 'test.jpg',
+ name: 'test',
+ });
+
+ const image = wrapper.find('img');
+ image.trigger('error');
+ return wrapper.vm.$nextTick().then(() => {
+ expect(image.isVisible()).toBe(false);
+ expect(wrapper.find(GlIcon).element).toMatchSnapshot();
+ });
+ });
+
+ describe('zoom', () => {
+ const baseImageWidth = 100;
+ const baseImageHeight = 100;
+
+ beforeEach(() => {
+ createComponent(
+ {
+ isLoading: false,
+ image: 'test.jpg',
+ name: 'test',
+ },
+ {
+ imageStyle: {
+ width: `${baseImageWidth}px`,
+ height: `${baseImageHeight}px`,
+ },
+ baseImageSize: {
+ width: baseImageWidth,
+ height: baseImageHeight,
+ },
+ },
+ );
+
+ jest.spyOn(wrapper.vm.$refs.contentImg, 'offsetWidth', 'get').mockReturnValue(baseImageWidth);
+ jest
+ .spyOn(wrapper.vm.$refs.contentImg, 'offsetHeight', 'get')
+ .mockReturnValue(baseImageHeight);
+ });
+
+ it('emits @resize event on zoom', () => {
+ const zoomAmount = 2;
+ wrapper.vm.zoom(zoomAmount);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted('resize')).toEqual([
+ [{ width: baseImageWidth * zoomAmount, height: baseImageHeight * zoomAmount }],
+ ]);
+ });
+ });
+
+ it('emits @resize event with base image size when scale=1', () => {
+ wrapper.vm.zoom(1);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted('resize')).toEqual([
+ [{ width: baseImageWidth, height: baseImageHeight }],
+ ]);
+ });
+ });
+
+ it('sets image style when zoomed', () => {
+ const zoomAmount = 2;
+ wrapper.vm.zoom(zoomAmount);
+ expect(wrapper.vm.imageStyle).toEqual({
+ width: `${baseImageWidth * zoomAmount}px`,
+ height: `${baseImageHeight * zoomAmount}px`,
+ });
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/list/__snapshots__/item_spec.js.snap b/spec/frontend/design_management_new/components/list/__snapshots__/item_spec.js.snap
new file mode 100644
index 00000000000..8c6e20cb54c
--- /dev/null
+++ b/spec/frontend/design_management_new/components/list/__snapshots__/item_spec.js.snap
@@ -0,0 +1,472 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management list item component when item appears in view after image is loaded renders media broken icon when image onerror triggered 1`] = `
+<gl-icon-stub
+ class="text-secondary"
+ name="media-broken"
+ size="32"
+/>
+`;
+
+exports[`Design management list item component with no notes renders item with correct status icon for creation event 1`] = `
+<router-link-stub
+ class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
+ to="[object Object]"
+>
+ <div
+ class="card-body p-0 d-flex-center overflow-hidden position-relative"
+ >
+ <div
+ class="design-event position-absolute"
+ >
+ <span
+ aria-label="Added in this version"
+ title="Added in this version"
+ >
+ <icon-stub
+ class="text-success-500"
+ name="file-addition-solid"
+ size="18"
+ />
+ </span>
+ </div>
+
+ <gl-intersection-observer-stub
+ options="[object Object]"
+ >
+ <!---->
+
+ <img
+ alt="test"
+ class="block mx-auto mw-100 mh-100 design-img"
+ data-qa-selector="design_image"
+ src=""
+ />
+ </gl-intersection-observer-stub>
+ </div>
+
+ <div
+ class="card-footer d-flex w-100"
+ >
+ <div
+ class="d-flex flex-column str-truncated-100"
+ >
+ <span
+ class="bold str-truncated-100"
+ data-qa-selector="design_file_name"
+ >
+ test
+ </span>
+
+ <span
+ class="str-truncated-100"
+ >
+
+ Updated
+ <timeago-stub
+ cssclass=""
+ time="01-01-2019"
+ tooltipplacement="bottom"
+ />
+ </span>
+ </div>
+
+ <!---->
+ </div>
+</router-link-stub>
+`;
+
+exports[`Design management list item component with no notes renders item with correct status icon for deletion event 1`] = `
+<router-link-stub
+ class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
+ to="[object Object]"
+>
+ <div
+ class="card-body p-0 d-flex-center overflow-hidden position-relative"
+ >
+ <div
+ class="design-event position-absolute"
+ >
+ <span
+ aria-label="Deleted in this version"
+ title="Deleted in this version"
+ >
+ <icon-stub
+ class="text-danger-500"
+ name="file-deletion-solid"
+ size="18"
+ />
+ </span>
+ </div>
+
+ <gl-intersection-observer-stub
+ options="[object Object]"
+ >
+ <!---->
+
+ <img
+ alt="test"
+ class="block mx-auto mw-100 mh-100 design-img"
+ data-qa-selector="design_image"
+ src=""
+ />
+ </gl-intersection-observer-stub>
+ </div>
+
+ <div
+ class="card-footer d-flex w-100"
+ >
+ <div
+ class="d-flex flex-column str-truncated-100"
+ >
+ <span
+ class="bold str-truncated-100"
+ data-qa-selector="design_file_name"
+ >
+ test
+ </span>
+
+ <span
+ class="str-truncated-100"
+ >
+
+ Updated
+ <timeago-stub
+ cssclass=""
+ time="01-01-2019"
+ tooltipplacement="bottom"
+ />
+ </span>
+ </div>
+
+ <!---->
+ </div>
+</router-link-stub>
+`;
+
+exports[`Design management list item component with no notes renders item with correct status icon for modification event 1`] = `
+<router-link-stub
+ class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
+ to="[object Object]"
+>
+ <div
+ class="card-body p-0 d-flex-center overflow-hidden position-relative"
+ >
+ <div
+ class="design-event position-absolute"
+ >
+ <span
+ aria-label="Modified in this version"
+ title="Modified in this version"
+ >
+ <icon-stub
+ class="text-primary-500"
+ name="file-modified-solid"
+ size="18"
+ />
+ </span>
+ </div>
+
+ <gl-intersection-observer-stub
+ options="[object Object]"
+ >
+ <!---->
+
+ <img
+ alt="test"
+ class="block mx-auto mw-100 mh-100 design-img"
+ data-qa-selector="design_image"
+ src=""
+ />
+ </gl-intersection-observer-stub>
+ </div>
+
+ <div
+ class="card-footer d-flex w-100"
+ >
+ <div
+ class="d-flex flex-column str-truncated-100"
+ >
+ <span
+ class="bold str-truncated-100"
+ data-qa-selector="design_file_name"
+ >
+ test
+ </span>
+
+ <span
+ class="str-truncated-100"
+ >
+
+ Updated
+ <timeago-stub
+ cssclass=""
+ time="01-01-2019"
+ tooltipplacement="bottom"
+ />
+ </span>
+ </div>
+
+ <!---->
+ </div>
+</router-link-stub>
+`;
+
+exports[`Design management list item component with no notes renders item with no status icon for none event 1`] = `
+<router-link-stub
+ class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
+ to="[object Object]"
+>
+ <div
+ class="card-body p-0 d-flex-center overflow-hidden position-relative"
+ >
+ <!---->
+
+ <gl-intersection-observer-stub
+ options="[object Object]"
+ >
+ <!---->
+
+ <img
+ alt="test"
+ class="block mx-auto mw-100 mh-100 design-img"
+ data-qa-selector="design_image"
+ src=""
+ />
+ </gl-intersection-observer-stub>
+ </div>
+
+ <div
+ class="card-footer d-flex w-100"
+ >
+ <div
+ class="d-flex flex-column str-truncated-100"
+ >
+ <span
+ class="bold str-truncated-100"
+ data-qa-selector="design_file_name"
+ >
+ test
+ </span>
+
+ <span
+ class="str-truncated-100"
+ >
+
+ Updated
+ <timeago-stub
+ cssclass=""
+ time="01-01-2019"
+ tooltipplacement="bottom"
+ />
+ </span>
+ </div>
+
+ <!---->
+ </div>
+</router-link-stub>
+`;
+
+exports[`Design management list item component with no notes renders loading spinner when isUploading is true 1`] = `
+<router-link-stub
+ class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
+ to="[object Object]"
+>
+ <div
+ class="card-body p-0 d-flex-center overflow-hidden position-relative"
+ >
+ <!---->
+
+ <gl-intersection-observer-stub
+ options="[object Object]"
+ >
+ <gl-loading-icon-stub
+ color="orange"
+ label="Loading"
+ size="md"
+ />
+
+ <img
+ alt="test"
+ class="block mx-auto mw-100 mh-100 design-img"
+ data-qa-selector="design_image"
+ src=""
+ style="display: none;"
+ />
+ </gl-intersection-observer-stub>
+ </div>
+
+ <div
+ class="card-footer d-flex w-100"
+ >
+ <div
+ class="d-flex flex-column str-truncated-100"
+ >
+ <span
+ class="bold str-truncated-100"
+ data-qa-selector="design_file_name"
+ >
+ test
+ </span>
+
+ <span
+ class="str-truncated-100"
+ >
+
+ Updated
+ <timeago-stub
+ cssclass=""
+ time="01-01-2019"
+ tooltipplacement="bottom"
+ />
+ </span>
+ </div>
+
+ <!---->
+ </div>
+</router-link-stub>
+`;
+
+exports[`Design management list item component with notes renders item with multiple comments 1`] = `
+<router-link-stub
+ class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
+ to="[object Object]"
+>
+ <div
+ class="card-body p-0 d-flex-center overflow-hidden position-relative"
+ >
+ <!---->
+
+ <gl-intersection-observer-stub
+ options="[object Object]"
+ >
+ <!---->
+
+ <img
+ alt="test"
+ class="block mx-auto mw-100 mh-100 design-img"
+ data-qa-selector="design_image"
+ src=""
+ />
+ </gl-intersection-observer-stub>
+ </div>
+
+ <div
+ class="card-footer d-flex w-100"
+ >
+ <div
+ class="d-flex flex-column str-truncated-100"
+ >
+ <span
+ class="bold str-truncated-100"
+ data-qa-selector="design_file_name"
+ >
+ test
+ </span>
+
+ <span
+ class="str-truncated-100"
+ >
+
+ Updated
+ <timeago-stub
+ cssclass=""
+ time="01-01-2019"
+ tooltipplacement="bottom"
+ />
+ </span>
+ </div>
+
+ <div
+ class="ml-auto d-flex align-items-center text-secondary"
+ >
+ <icon-stub
+ class="ml-1"
+ name="comments"
+ size="16"
+ />
+
+ <span
+ aria-label="2 comments"
+ class="ml-1"
+ >
+
+ 2
+
+ </span>
+ </div>
+ </div>
+</router-link-stub>
+`;
+
+exports[`Design management list item component with notes renders item with single comment 1`] = `
+<router-link-stub
+ class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
+ to="[object Object]"
+>
+ <div
+ class="card-body p-0 d-flex-center overflow-hidden position-relative"
+ >
+ <!---->
+
+ <gl-intersection-observer-stub
+ options="[object Object]"
+ >
+ <!---->
+
+ <img
+ alt="test"
+ class="block mx-auto mw-100 mh-100 design-img"
+ data-qa-selector="design_image"
+ src=""
+ />
+ </gl-intersection-observer-stub>
+ </div>
+
+ <div
+ class="card-footer d-flex w-100"
+ >
+ <div
+ class="d-flex flex-column str-truncated-100"
+ >
+ <span
+ class="bold str-truncated-100"
+ data-qa-selector="design_file_name"
+ >
+ test
+ </span>
+
+ <span
+ class="str-truncated-100"
+ >
+
+ Updated
+ <timeago-stub
+ cssclass=""
+ time="01-01-2019"
+ tooltipplacement="bottom"
+ />
+ </span>
+ </div>
+
+ <div
+ class="ml-auto d-flex align-items-center text-secondary"
+ >
+ <icon-stub
+ class="ml-1"
+ name="comments"
+ size="16"
+ />
+
+ <span
+ aria-label="1 comment"
+ class="ml-1"
+ >
+
+ 1
+
+ </span>
+ </div>
+ </div>
+</router-link-stub>
+`;
diff --git a/spec/frontend/design_management_new/components/list/item_spec.js b/spec/frontend/design_management_new/components/list/item_spec.js
new file mode 100644
index 00000000000..5e3e6832acb
--- /dev/null
+++ b/spec/frontend/design_management_new/components/list/item_spec.js
@@ -0,0 +1,168 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { GlIcon, GlLoadingIcon, GlIntersectionObserver } from '@gitlab/ui';
+import VueRouter from 'vue-router';
+import Item from '~/design_management_new/components/list/item.vue';
+
+const localVue = createLocalVue();
+localVue.use(VueRouter);
+const router = new VueRouter();
+
+// Referenced from: doc/api/graphql/reference/gitlab_schema.graphql:DesignVersionEvent
+const DESIGN_VERSION_EVENT = {
+ CREATION: 'CREATION',
+ DELETION: 'DELETION',
+ MODIFICATION: 'MODIFICATION',
+ NO_CHANGE: 'NONE',
+};
+
+describe('Design management list item component', () => {
+ let wrapper;
+
+ function createComponent({
+ notesCount = 0,
+ event = DESIGN_VERSION_EVENT.NO_CHANGE,
+ isUploading = false,
+ imageLoading = false,
+ } = {}) {
+ wrapper = shallowMount(Item, {
+ localVue,
+ router,
+ propsData: {
+ id: 1,
+ filename: 'test',
+ image: 'http://via.placeholder.com/300',
+ isUploading,
+ event,
+ notesCount,
+ updatedAt: '01-01-2019',
+ },
+ data() {
+ return {
+ imageLoading,
+ };
+ },
+ stubs: ['router-link'],
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when item is not in view', () => {
+ it('image is not rendered', () => {
+ createComponent();
+
+ const image = wrapper.find('img');
+ expect(image.attributes('src')).toBe('');
+ });
+ });
+
+ describe('when item appears in view', () => {
+ let image;
+ let glIntersectionObserver;
+
+ beforeEach(() => {
+ createComponent();
+ image = wrapper.find('img');
+ glIntersectionObserver = wrapper.find(GlIntersectionObserver);
+
+ glIntersectionObserver.vm.$emit('appear');
+ return wrapper.vm.$nextTick();
+ });
+
+ describe('before image is loaded', () => {
+ it('renders loading spinner', () => {
+ expect(wrapper.find(GlLoadingIcon)).toExist();
+ });
+ });
+
+ describe('after image is loaded', () => {
+ beforeEach(() => {
+ image.trigger('load');
+ return wrapper.vm.$nextTick();
+ });
+
+ it('renders an image', () => {
+ expect(image.attributes('src')).toBe('http://via.placeholder.com/300');
+ expect(image.isVisible()).toBe(true);
+ });
+
+ it('renders media broken icon when image onerror triggered', () => {
+ image.trigger('error');
+ return wrapper.vm.$nextTick().then(() => {
+ expect(image.isVisible()).toBe(false);
+ expect(wrapper.find(GlIcon).element).toMatchSnapshot();
+ });
+ });
+
+ describe('when imageV432x230 and image provided', () => {
+ it('renders imageV432x230 image', () => {
+ const mockSrc = 'mock-imageV432x230-url';
+ wrapper.setProps({ imageV432x230: mockSrc });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(image.attributes('src')).toBe(mockSrc);
+ });
+ });
+ });
+
+ describe('when image disappears from view and then reappears', () => {
+ beforeEach(() => {
+ glIntersectionObserver.vm.$emit('appear');
+ return wrapper.vm.$nextTick();
+ });
+
+ it('renders an image', () => {
+ expect(image.isVisible()).toBe(true);
+ });
+ });
+ });
+ });
+
+ describe('with notes', () => {
+ it('renders item with single comment', () => {
+ createComponent({ notesCount: 1 });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders item with multiple comments', () => {
+ createComponent({ notesCount: 2 });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ describe('with no notes', () => {
+ it('renders item with no status icon for none event', () => {
+ createComponent();
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders item with correct status icon for modification event', () => {
+ createComponent({ event: DESIGN_VERSION_EVENT.MODIFICATION });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders item with correct status icon for deletion event', () => {
+ createComponent({ event: DESIGN_VERSION_EVENT.DELETION });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders item with correct status icon for creation event', () => {
+ createComponent({ event: DESIGN_VERSION_EVENT.CREATION });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders loading spinner when isUploading is true', () => {
+ createComponent({ isUploading: true });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/toolbar/__snapshots__/index_spec.js.snap b/spec/frontend/design_management_new/components/toolbar/__snapshots__/index_spec.js.snap
new file mode 100644
index 00000000000..f251171ecda
--- /dev/null
+++ b/spec/frontend/design_management_new/components/toolbar/__snapshots__/index_spec.js.snap
@@ -0,0 +1,63 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management toolbar component renders design and updated data 1`] = `
+<header
+ class="d-flex p-2 bg-white align-items-center js-design-header"
+>
+ <a
+ aria-label="Go back to designs"
+ class="mr-3 text-plain d-flex justify-content-center align-items-center"
+ data-testid="close-design"
+ >
+ <icon-stub
+ name="close"
+ size="18"
+ />
+ </a>
+
+ <div
+ class="overflow-hidden d-flex align-items-center"
+ >
+ <h2
+ class="m-0 str-truncated-100 gl-font-base"
+ >
+ test.jpg
+ </h2>
+
+ <small
+ class="text-secondary"
+ >
+ Updated 1 hour ago by Test Name
+ </small>
+ </div>
+
+ <pagination-stub
+ class="ml-auto flex-shrink-0"
+ id="1"
+ />
+
+ <gl-deprecated-button-stub
+ class="mr-2"
+ href="/-/designs/306/7f747adcd4693afadbe968d7ba7d983349b9012d"
+ size="md"
+ variant="secondary"
+ >
+ <icon-stub
+ name="download"
+ size="18"
+ />
+ </gl-deprecated-button-stub>
+
+ <delete-button-stub
+ buttonclass=""
+ buttonsize="medium"
+ buttonvariant="danger"
+ hasselecteddesigns="true"
+ >
+ <icon-stub
+ name="remove"
+ size="18"
+ />
+ </delete-button-stub>
+</header>
+`;
diff --git a/spec/frontend/design_management_new/components/toolbar/__snapshots__/pagination_button_spec.js.snap b/spec/frontend/design_management_new/components/toolbar/__snapshots__/pagination_button_spec.js.snap
new file mode 100644
index 00000000000..08662a04f15
--- /dev/null
+++ b/spec/frontend/design_management_new/components/toolbar/__snapshots__/pagination_button_spec.js.snap
@@ -0,0 +1,28 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management pagination button component disables button when no design is passed 1`] = `
+<router-link-stub
+ aria-label="Test title"
+ class="btn btn-default disabled"
+ disabled="true"
+ to="[object Object]"
+>
+ <icon-stub
+ name="angle-right"
+ size="16"
+ />
+</router-link-stub>
+`;
+
+exports[`Design management pagination button component renders router-link 1`] = `
+<router-link-stub
+ aria-label="Test title"
+ class="btn btn-default"
+ to="[object Object]"
+>
+ <icon-stub
+ name="angle-right"
+ size="16"
+ />
+</router-link-stub>
+`;
diff --git a/spec/frontend/design_management_new/components/toolbar/__snapshots__/pagination_spec.js.snap b/spec/frontend/design_management_new/components/toolbar/__snapshots__/pagination_spec.js.snap
new file mode 100644
index 00000000000..0197b4bff79
--- /dev/null
+++ b/spec/frontend/design_management_new/components/toolbar/__snapshots__/pagination_spec.js.snap
@@ -0,0 +1,29 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management pagination component hides components when designs are empty 1`] = `<!---->`;
+
+exports[`Design management pagination component renders pagination buttons 1`] = `
+<div
+ class="d-flex align-items-center"
+>
+
+ 0 of 2
+
+ <div
+ class="btn-group ml-3 mr-3"
+ >
+ <pagination-button-stub
+ class="js-previous-design"
+ iconname="angle-left"
+ title="Go to previous design"
+ />
+
+ <pagination-button-stub
+ class="js-next-design"
+ design="[object Object]"
+ iconname="angle-right"
+ title="Go to next design"
+ />
+ </div>
+</div>
+`;
diff --git a/spec/frontend/design_management_new/components/toolbar/index_spec.js b/spec/frontend/design_management_new/components/toolbar/index_spec.js
new file mode 100644
index 00000000000..eb5ae15ed58
--- /dev/null
+++ b/spec/frontend/design_management_new/components/toolbar/index_spec.js
@@ -0,0 +1,123 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import VueRouter from 'vue-router';
+import Toolbar from '~/design_management_new/components/toolbar/index.vue';
+import DeleteButton from '~/design_management_new/components/delete_button.vue';
+import { DESIGNS_ROUTE_NAME } from '~/design_management_new/router/constants';
+import { GlDeprecatedButton } from '@gitlab/ui';
+
+const localVue = createLocalVue();
+localVue.use(VueRouter);
+const router = new VueRouter();
+
+const RouterLinkStub = {
+ props: {
+ to: {
+ type: Object,
+ },
+ },
+ render(createElement) {
+ return createElement('a', {}, this.$slots.default);
+ },
+};
+
+describe('Design management toolbar component', () => {
+ let wrapper;
+
+ function createComponent(isLoading = false, createDesign = true, props) {
+ const updatedAt = new Date();
+ updatedAt.setHours(updatedAt.getHours() - 1);
+
+ wrapper = shallowMount(Toolbar, {
+ localVue,
+ router,
+ propsData: {
+ id: '1',
+ isLatestVersion: true,
+ isLoading,
+ isDeleting: false,
+ filename: 'test.jpg',
+ updatedAt: updatedAt.toString(),
+ updatedBy: {
+ name: 'Test Name',
+ },
+ image: '/-/designs/306/7f747adcd4693afadbe968d7ba7d983349b9012d',
+ ...props,
+ },
+ stubs: {
+ 'router-link': RouterLinkStub,
+ },
+ });
+
+ wrapper.setData({
+ permissions: {
+ createDesign,
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders design and updated data', () => {
+ createComponent();
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ it('links back to designs list', () => {
+ createComponent();
+
+ return wrapper.vm.$nextTick().then(() => {
+ const link = wrapper.find('a');
+
+ expect(link.props('to')).toEqual({
+ name: DESIGNS_ROUTE_NAME,
+ query: {
+ version: undefined,
+ },
+ });
+ });
+ });
+
+ it('renders delete button on latest designs version with logged in user', () => {
+ createComponent();
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(DeleteButton).exists()).toBe(true);
+ });
+ });
+
+ it('does not render delete button on non-latest version', () => {
+ createComponent(false, true, { isLatestVersion: false });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(DeleteButton).exists()).toBe(false);
+ });
+ });
+
+ it('does not render delete button when user is not logged in', () => {
+ createComponent(false, false);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(DeleteButton).exists()).toBe(false);
+ });
+ });
+
+ it('emits `delete` event on deleteButton `deleteSelectedDesigns` event', () => {
+ createComponent();
+
+ return wrapper.vm.$nextTick().then(() => {
+ wrapper.find(DeleteButton).vm.$emit('deleteSelectedDesigns');
+ expect(wrapper.emitted().delete).toBeTruthy();
+ });
+ });
+
+ it('renders download button with correct link', () => {
+ expect(wrapper.find(GlDeprecatedButton).attributes('href')).toBe(
+ '/-/designs/306/7f747adcd4693afadbe968d7ba7d983349b9012d',
+ );
+ });
+});
diff --git a/spec/frontend/design_management_new/components/toolbar/pagination_button_spec.js b/spec/frontend/design_management_new/components/toolbar/pagination_button_spec.js
new file mode 100644
index 00000000000..5f33d65fc1f
--- /dev/null
+++ b/spec/frontend/design_management_new/components/toolbar/pagination_button_spec.js
@@ -0,0 +1,61 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import VueRouter from 'vue-router';
+import PaginationButton from '~/design_management_new/components/toolbar/pagination_button.vue';
+import { DESIGN_ROUTE_NAME } from '~/design_management_new/router/constants';
+
+const localVue = createLocalVue();
+localVue.use(VueRouter);
+const router = new VueRouter();
+
+describe('Design management pagination button component', () => {
+ let wrapper;
+
+ function createComponent(design = null) {
+ wrapper = shallowMount(PaginationButton, {
+ localVue,
+ router,
+ propsData: {
+ design,
+ title: 'Test title',
+ iconName: 'angle-right',
+ },
+ stubs: ['router-link'],
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('disables button when no design is passed', () => {
+ createComponent();
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders router-link', () => {
+ createComponent({ id: '2' });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ describe('designLink', () => {
+ it('returns empty link when design is null', () => {
+ createComponent();
+
+ expect(wrapper.vm.designLink).toEqual({});
+ });
+
+ it('returns design link', () => {
+ createComponent({ id: '2', filename: 'test' });
+
+ wrapper.vm.$router.replace('/root/test-project/issues/1/designs/test?version=1');
+
+ expect(wrapper.vm.designLink).toEqual({
+ name: DESIGN_ROUTE_NAME,
+ params: { id: 'test' },
+ query: { version: '1' },
+ });
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/toolbar/pagination_spec.js b/spec/frontend/design_management_new/components/toolbar/pagination_spec.js
new file mode 100644
index 00000000000..45dce15e292
--- /dev/null
+++ b/spec/frontend/design_management_new/components/toolbar/pagination_spec.js
@@ -0,0 +1,79 @@
+/* global Mousetrap */
+import 'mousetrap';
+import { shallowMount } from '@vue/test-utils';
+import Pagination from '~/design_management_new/components/toolbar/pagination.vue';
+import { DESIGN_ROUTE_NAME } from '~/design_management_new/router/constants';
+
+const push = jest.fn();
+const $router = {
+ push,
+};
+
+const $route = {
+ path: '/designs/design-2',
+ query: {},
+};
+
+describe('Design management pagination component', () => {
+ let wrapper;
+
+ function createComponent() {
+ wrapper = shallowMount(Pagination, {
+ propsData: {
+ id: '2',
+ },
+ mocks: {
+ $router,
+ $route,
+ },
+ });
+ }
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('hides components when designs are empty', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders pagination buttons', () => {
+ wrapper.setData({
+ designs: [{ id: '1' }, { id: '2' }],
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ describe('keyboard buttons navigation', () => {
+ beforeEach(() => {
+ wrapper.setData({
+ designs: [{ filename: '1' }, { filename: '2' }, { filename: '3' }],
+ });
+ });
+
+ it('routes to previous design on Left button', () => {
+ Mousetrap.trigger('left');
+ expect(push).toHaveBeenCalledWith({
+ name: DESIGN_ROUTE_NAME,
+ params: { id: '1' },
+ query: {},
+ });
+ });
+
+ it('routes to next design on Right button', () => {
+ Mousetrap.trigger('right');
+ expect(push).toHaveBeenCalledWith({
+ name: DESIGN_ROUTE_NAME,
+ params: { id: '3' },
+ query: {},
+ });
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/upload/__snapshots__/button_spec.js.snap b/spec/frontend/design_management_new/components/upload/__snapshots__/button_spec.js.snap
new file mode 100644
index 00000000000..b498becc606
--- /dev/null
+++ b/spec/frontend/design_management_new/components/upload/__snapshots__/button_spec.js.snap
@@ -0,0 +1,85 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management upload button component renders inverted upload design button 1`] = `
+<div
+ isinverted="true"
+>
+ <gl-button-stub
+ category="tertiary"
+ icon=""
+ size="small"
+ title="Adding a design with the same filename replaces the file in a new version."
+ variant="success"
+ >
+
+ Upload designs
+
+ <!---->
+ </gl-button-stub>
+
+ <input
+ accept="image/*"
+ class="hide"
+ multiple="multiple"
+ name="design_file"
+ type="file"
+ />
+</div>
+`;
+
+exports[`Design management upload button component renders loading icon 1`] = `
+<div>
+ <gl-button-stub
+ category="tertiary"
+ disabled="true"
+ icon=""
+ size="small"
+ title="Adding a design with the same filename replaces the file in a new version."
+ variant="success"
+ >
+
+ Upload designs
+
+ <gl-loading-icon-stub
+ class="ml-1"
+ color="orange"
+ inline="true"
+ label="Loading"
+ size="sm"
+ />
+ </gl-button-stub>
+
+ <input
+ accept="image/*"
+ class="hide"
+ multiple="multiple"
+ name="design_file"
+ type="file"
+ />
+</div>
+`;
+
+exports[`Design management upload button component renders upload design button 1`] = `
+<div>
+ <gl-button-stub
+ category="tertiary"
+ icon=""
+ size="small"
+ title="Adding a design with the same filename replaces the file in a new version."
+ variant="success"
+ >
+
+ Upload designs
+
+ <!---->
+ </gl-button-stub>
+
+ <input
+ accept="image/*"
+ class="hide"
+ multiple="multiple"
+ name="design_file"
+ type="file"
+ />
+</div>
+`;
diff --git a/spec/frontend/design_management_new/components/upload/__snapshots__/design_dropzone_spec.js.snap b/spec/frontend/design_management_new/components/upload/__snapshots__/design_dropzone_spec.js.snap
new file mode 100644
index 00000000000..c53c6c889b0
--- /dev/null
+++ b/spec/frontend/design_management_new/components/upload/__snapshots__/design_dropzone_spec.js.snap
@@ -0,0 +1,501 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management dropzone component when dragging renders correct template when drag event contains files 1`] = `
+<div
+ class="w-100 position-relative"
+>
+ <button
+ class="card design-dropzone-card design-dropzone-border w-100 h-100 gl-align-items-center gl-justify-content-center gl-p-3"
+ >
+ <div
+ class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
+ data-testid="dropzone-area"
+ >
+ <gl-icon-stub
+ class="gl-mb-2"
+ name="upload"
+ size="24"
+ />
+
+ <p
+ class="gl-font-weight-bold gl-mb-0"
+ >
+ <gl-sprintf-stub
+ message="Drop or %{linkStart}upload%{linkEnd} Designs to attach"
+ />
+ </p>
+ </div>
+ </button>
+
+ <input
+ accept="image/*"
+ class="hide"
+ multiple="multiple"
+ name="design_file"
+ type="file"
+ />
+
+ <transition-stub
+ name="design-dropzone-fade"
+ >
+ <div
+ class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
+ style=""
+ >
+ <div
+ class="mw-50 text-center"
+ style="display: none;"
+ >
+ <h3
+ class=""
+ >
+ Oh no!
+ </h3>
+
+ <span>
+ You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.
+ </span>
+ </div>
+
+ <div
+ class="mw-50 text-center"
+ style=""
+ >
+ <h3
+ class=""
+ >
+ Incoming!
+ </h3>
+
+ <span>
+ Drop your designs to start your upload.
+ </span>
+ </div>
+ </div>
+ </transition-stub>
+</div>
+`;
+
+exports[`Design management dropzone component when dragging renders correct template when drag event contains files and text 1`] = `
+<div
+ class="w-100 position-relative"
+>
+ <button
+ class="card design-dropzone-card design-dropzone-border w-100 h-100 gl-align-items-center gl-justify-content-center gl-p-3"
+ >
+ <div
+ class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
+ data-testid="dropzone-area"
+ >
+ <gl-icon-stub
+ class="gl-mb-2"
+ name="upload"
+ size="24"
+ />
+
+ <p
+ class="gl-font-weight-bold gl-mb-0"
+ >
+ <gl-sprintf-stub
+ message="Drop or %{linkStart}upload%{linkEnd} Designs to attach"
+ />
+ </p>
+ </div>
+ </button>
+
+ <input
+ accept="image/*"
+ class="hide"
+ multiple="multiple"
+ name="design_file"
+ type="file"
+ />
+
+ <transition-stub
+ name="design-dropzone-fade"
+ >
+ <div
+ class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
+ style=""
+ >
+ <div
+ class="mw-50 text-center"
+ style="display: none;"
+ >
+ <h3
+ class=""
+ >
+ Oh no!
+ </h3>
+
+ <span>
+ You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.
+ </span>
+ </div>
+
+ <div
+ class="mw-50 text-center"
+ style=""
+ >
+ <h3
+ class=""
+ >
+ Incoming!
+ </h3>
+
+ <span>
+ Drop your designs to start your upload.
+ </span>
+ </div>
+ </div>
+ </transition-stub>
+</div>
+`;
+
+exports[`Design management dropzone component when dragging renders correct template when drag event contains text 1`] = `
+<div
+ class="w-100 position-relative"
+>
+ <button
+ class="card design-dropzone-card design-dropzone-border w-100 h-100 gl-align-items-center gl-justify-content-center gl-p-3"
+ >
+ <div
+ class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
+ data-testid="dropzone-area"
+ >
+ <gl-icon-stub
+ class="gl-mb-2"
+ name="upload"
+ size="24"
+ />
+
+ <p
+ class="gl-font-weight-bold gl-mb-0"
+ >
+ <gl-sprintf-stub
+ message="Drop or %{linkStart}upload%{linkEnd} Designs to attach"
+ />
+ </p>
+ </div>
+ </button>
+
+ <input
+ accept="image/*"
+ class="hide"
+ multiple="multiple"
+ name="design_file"
+ type="file"
+ />
+
+ <transition-stub
+ name="design-dropzone-fade"
+ >
+ <div
+ class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
+ style=""
+ >
+ <div
+ class="mw-50 text-center"
+ >
+ <h3
+ class=""
+ >
+ Oh no!
+ </h3>
+
+ <span>
+ You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.
+ </span>
+ </div>
+
+ <div
+ class="mw-50 text-center"
+ style="display: none;"
+ >
+ <h3
+ class=""
+ >
+ Incoming!
+ </h3>
+
+ <span>
+ Drop your designs to start your upload.
+ </span>
+ </div>
+ </div>
+ </transition-stub>
+</div>
+`;
+
+exports[`Design management dropzone component when dragging renders correct template when drag event is empty 1`] = `
+<div
+ class="w-100 position-relative"
+>
+ <button
+ class="card design-dropzone-card design-dropzone-border w-100 h-100 gl-align-items-center gl-justify-content-center gl-p-3"
+ >
+ <div
+ class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
+ data-testid="dropzone-area"
+ >
+ <gl-icon-stub
+ class="gl-mb-2"
+ name="upload"
+ size="24"
+ />
+
+ <p
+ class="gl-font-weight-bold gl-mb-0"
+ >
+ <gl-sprintf-stub
+ message="Drop or %{linkStart}upload%{linkEnd} Designs to attach"
+ />
+ </p>
+ </div>
+ </button>
+
+ <input
+ accept="image/*"
+ class="hide"
+ multiple="multiple"
+ name="design_file"
+ type="file"
+ />
+
+ <transition-stub
+ name="design-dropzone-fade"
+ >
+ <div
+ class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
+ style=""
+ >
+ <div
+ class="mw-50 text-center"
+ >
+ <h3
+ class=""
+ >
+ Oh no!
+ </h3>
+
+ <span>
+ You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.
+ </span>
+ </div>
+
+ <div
+ class="mw-50 text-center"
+ style="display: none;"
+ >
+ <h3
+ class=""
+ >
+ Incoming!
+ </h3>
+
+ <span>
+ Drop your designs to start your upload.
+ </span>
+ </div>
+ </div>
+ </transition-stub>
+</div>
+`;
+
+exports[`Design management dropzone component when dragging renders correct template when dragging stops 1`] = `
+<div
+ class="w-100 position-relative"
+>
+ <button
+ class="card design-dropzone-card design-dropzone-border w-100 h-100 gl-align-items-center gl-justify-content-center gl-p-3"
+ >
+ <div
+ class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
+ data-testid="dropzone-area"
+ >
+ <gl-icon-stub
+ class="gl-mb-2"
+ name="upload"
+ size="24"
+ />
+
+ <p
+ class="gl-font-weight-bold gl-mb-0"
+ >
+ <gl-sprintf-stub
+ message="Drop or %{linkStart}upload%{linkEnd} Designs to attach"
+ />
+ </p>
+ </div>
+ </button>
+
+ <input
+ accept="image/*"
+ class="hide"
+ multiple="multiple"
+ name="design_file"
+ type="file"
+ />
+
+ <transition-stub
+ name="design-dropzone-fade"
+ >
+ <div
+ class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
+ style="display: none;"
+ >
+ <div
+ class="mw-50 text-center"
+ >
+ <h3
+ class=""
+ >
+ Oh no!
+ </h3>
+
+ <span>
+ You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.
+ </span>
+ </div>
+
+ <div
+ class="mw-50 text-center"
+ style="display: none;"
+ >
+ <h3
+ class=""
+ >
+ Incoming!
+ </h3>
+
+ <span>
+ Drop your designs to start your upload.
+ </span>
+ </div>
+ </div>
+ </transition-stub>
+</div>
+`;
+
+exports[`Design management dropzone component when no slot provided renders default dropzone card 1`] = `
+<div
+ class="w-100 position-relative"
+>
+ <button
+ class="card design-dropzone-card design-dropzone-border w-100 h-100 gl-align-items-center gl-justify-content-center gl-p-3"
+ >
+ <div
+ class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
+ data-testid="dropzone-area"
+ >
+ <gl-icon-stub
+ class="gl-mb-2"
+ name="upload"
+ size="24"
+ />
+
+ <p
+ class="gl-font-weight-bold gl-mb-0"
+ >
+ <gl-sprintf-stub
+ message="Drop or %{linkStart}upload%{linkEnd} Designs to attach"
+ />
+ </p>
+ </div>
+ </button>
+
+ <input
+ accept="image/*"
+ class="hide"
+ multiple="multiple"
+ name="design_file"
+ type="file"
+ />
+
+ <transition-stub
+ name="design-dropzone-fade"
+ >
+ <div
+ class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
+ style="display: none;"
+ >
+ <div
+ class="mw-50 text-center"
+ >
+ <h3
+ class=""
+ >
+ Oh no!
+ </h3>
+
+ <span>
+ You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.
+ </span>
+ </div>
+
+ <div
+ class="mw-50 text-center"
+ style="display: none;"
+ >
+ <h3
+ class=""
+ >
+ Incoming!
+ </h3>
+
+ <span>
+ Drop your designs to start your upload.
+ </span>
+ </div>
+ </div>
+ </transition-stub>
+</div>
+`;
+
+exports[`Design management dropzone component when slot provided renders dropzone with slot content 1`] = `
+<div
+ class="w-100 position-relative"
+>
+ <div>
+ dropzone slot
+ </div>
+
+ <transition-stub
+ name="design-dropzone-fade"
+ >
+ <div
+ class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
+ style="display: none;"
+ >
+ <div
+ class="mw-50 text-center"
+ >
+ <h3
+ class=""
+ >
+ Oh no!
+ </h3>
+
+ <span>
+ You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.
+ </span>
+ </div>
+
+ <div
+ class="mw-50 text-center"
+ style="display: none;"
+ >
+ <h3
+ class=""
+ >
+ Incoming!
+ </h3>
+
+ <span>
+ Drop your designs to start your upload.
+ </span>
+ </div>
+ </div>
+ </transition-stub>
+</div>
+`;
diff --git a/spec/frontend/design_management_new/components/upload/__snapshots__/design_version_dropdown_spec.js.snap b/spec/frontend/design_management_new/components/upload/__snapshots__/design_version_dropdown_spec.js.snap
new file mode 100644
index 00000000000..0d16acdef54
--- /dev/null
+++ b/spec/frontend/design_management_new/components/upload/__snapshots__/design_version_dropdown_spec.js.snap
@@ -0,0 +1,141 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management design version dropdown component renders design version dropdown button 1`] = `
+<gl-new-dropdown-stub
+ category="tertiary"
+ class="design-version-dropdown"
+ headertext=""
+ issueiid=""
+ projectpath=""
+ size="small"
+ text="Showing Latest Version"
+ variant="default"
+>
+ <gl-new-dropdown-item-stub
+ avatarurl=""
+ iconcolor=""
+ iconname=""
+ iconrightname=""
+ secondarytext=""
+ >
+ <router-link-stub
+ class="d-flex js-version-link"
+ to="[object Object]"
+ >
+ <div
+ class="flex-grow-1 ml-2"
+ >
+ <div>
+ <strong>
+ Version 2
+
+ <span>
+ (latest)
+ </span>
+ </strong>
+ </div>
+ </div>
+
+ <i
+ class="fa fa-check pull-right"
+ />
+ </router-link-stub>
+ </gl-new-dropdown-item-stub>
+ <gl-new-dropdown-item-stub
+ avatarurl=""
+ iconcolor=""
+ iconname=""
+ iconrightname=""
+ secondarytext=""
+ >
+ <router-link-stub
+ class="d-flex js-version-link"
+ to="[object Object]"
+ >
+ <div
+ class="flex-grow-1 ml-2"
+ >
+ <div>
+ <strong>
+ Version 1
+
+ <!---->
+ </strong>
+ </div>
+ </div>
+
+ <!---->
+ </router-link-stub>
+ </gl-new-dropdown-item-stub>
+</gl-new-dropdown-stub>
+`;
+
+exports[`Design management design version dropdown component renders design version list 1`] = `
+<gl-new-dropdown-stub
+ category="tertiary"
+ class="design-version-dropdown"
+ headertext=""
+ issueiid=""
+ projectpath=""
+ size="small"
+ text="Showing Latest Version"
+ variant="default"
+>
+ <gl-new-dropdown-item-stub
+ avatarurl=""
+ iconcolor=""
+ iconname=""
+ iconrightname=""
+ secondarytext=""
+ >
+ <router-link-stub
+ class="d-flex js-version-link"
+ to="[object Object]"
+ >
+ <div
+ class="flex-grow-1 ml-2"
+ >
+ <div>
+ <strong>
+ Version 2
+
+ <span>
+ (latest)
+ </span>
+ </strong>
+ </div>
+ </div>
+
+ <i
+ class="fa fa-check pull-right"
+ />
+ </router-link-stub>
+ </gl-new-dropdown-item-stub>
+ <gl-new-dropdown-item-stub
+ avatarurl=""
+ iconcolor=""
+ iconname=""
+ iconrightname=""
+ secondarytext=""
+ >
+ <router-link-stub
+ class="d-flex js-version-link"
+ to="[object Object]"
+ >
+ <div
+ class="flex-grow-1 ml-2"
+ >
+ <div>
+ <strong>
+ Version 1
+
+ <!---->
+ </strong>
+ </div>
+ </div>
+
+ <!---->
+ </router-link-stub>
+ </gl-new-dropdown-item-stub>
+</gl-new-dropdown-stub>
+`;
diff --git a/spec/frontend/design_management_new/components/upload/button_spec.js b/spec/frontend/design_management_new/components/upload/button_spec.js
new file mode 100644
index 00000000000..7f751982491
--- /dev/null
+++ b/spec/frontend/design_management_new/components/upload/button_spec.js
@@ -0,0 +1,59 @@
+import { shallowMount } from '@vue/test-utils';
+import UploadButton from '~/design_management_new/components/upload/button.vue';
+
+describe('Design management upload button component', () => {
+ let wrapper;
+
+ function createComponent(isSaving = false, isInverted = false) {
+ wrapper = shallowMount(UploadButton, {
+ propsData: {
+ isSaving,
+ isInverted,
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders upload design button', () => {
+ createComponent();
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders inverted upload design button', () => {
+ createComponent(false, true);
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders loading icon', () => {
+ createComponent(true);
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ describe('onFileUploadChange', () => {
+ it('emits upload event', () => {
+ createComponent();
+
+ wrapper.vm.onFileUploadChange({ target: { files: 'test' } });
+
+ expect(wrapper.emitted().upload[0]).toEqual(['test']);
+ });
+ });
+
+ describe('openFileUpload', () => {
+ it('triggers click on input', () => {
+ createComponent();
+
+ const clickSpy = jest.spyOn(wrapper.find('input').element, 'click');
+
+ wrapper.vm.openFileUpload();
+
+ expect(clickSpy).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/upload/design_dropzone_spec.js b/spec/frontend/design_management_new/components/upload/design_dropzone_spec.js
new file mode 100644
index 00000000000..c48cbb10172
--- /dev/null
+++ b/spec/frontend/design_management_new/components/upload/design_dropzone_spec.js
@@ -0,0 +1,151 @@
+import { shallowMount } from '@vue/test-utils';
+import DesignDropzone from '~/design_management_new/components/upload/design_dropzone.vue';
+import createFlash from '~/flash';
+import { GlIcon } from '@gitlab/ui';
+
+jest.mock('~/flash');
+
+describe('Design management dropzone component', () => {
+ let wrapper;
+
+ const mockDragEvent = ({ types = ['Files'], files = [] }) => {
+ return { dataTransfer: { types, files } };
+ };
+
+ const findDropzoneCard = () => wrapper.find('.design-dropzone-card');
+ const findDropzoneArea = () => wrapper.find('[data-testid="dropzone-area"]');
+ const findIcon = () => wrapper.find(GlIcon);
+
+ function createComponent({ slots = {}, data = {}, props = {} } = {}) {
+ wrapper = shallowMount(DesignDropzone, {
+ slots,
+ propsData: {
+ hasDesigns: true,
+ ...props,
+ },
+ data() {
+ return data;
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when slot provided', () => {
+ it('renders dropzone with slot content', () => {
+ createComponent({
+ slots: {
+ default: ['<div>dropzone slot</div>'],
+ },
+ });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ describe('when no slot provided', () => {
+ it('renders default dropzone card', () => {
+ createComponent();
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('triggers click event on file input element when clicked', () => {
+ createComponent();
+ const clickSpy = jest.spyOn(wrapper.find('input').element, 'click');
+
+ findDropzoneCard().trigger('click');
+ expect(clickSpy).toHaveBeenCalled();
+ });
+ });
+
+ describe('when dragging', () => {
+ it.each`
+ description | eventPayload
+ ${'is empty'} | ${{}}
+ ${'contains text'} | ${mockDragEvent({ types: ['text'] })}
+ ${'contains files and text'} | ${mockDragEvent({ types: ['Files', 'text'] })}
+ ${'contains files'} | ${mockDragEvent({ types: ['Files'] })}
+ `('renders correct template when drag event $description', ({ eventPayload }) => {
+ createComponent();
+
+ wrapper.trigger('dragenter', eventPayload);
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ it('renders correct template when dragging stops', () => {
+ createComponent();
+
+ wrapper.trigger('dragenter');
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ wrapper.trigger('dragleave');
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+ });
+
+ describe('when dropping', () => {
+ it('emits upload event', () => {
+ createComponent();
+ const mockFile = { name: 'test', type: 'image/jpg' };
+ const mockEvent = mockDragEvent({ files: [mockFile] });
+
+ wrapper.trigger('dragenter', mockEvent);
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ wrapper.trigger('drop', mockEvent);
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(wrapper.emitted().change[0]).toEqual([[mockFile]]);
+ });
+ });
+ });
+
+ describe('ondrop', () => {
+ const mockData = { dragCounter: 1, isDragDataValid: true };
+
+ describe('when drag data is valid', () => {
+ it('emits upload event for valid files', () => {
+ createComponent({ data: mockData });
+
+ const mockFile = { type: 'image/jpg' };
+ const mockEvent = mockDragEvent({ files: [mockFile] });
+
+ wrapper.vm.ondrop(mockEvent);
+ expect(wrapper.emitted().change[0]).toEqual([[mockFile]]);
+ });
+
+ it('calls createFlash when files are invalid', () => {
+ createComponent({ data: mockData });
+
+ const mockEvent = mockDragEvent({ files: [{ type: 'audio/midi' }] });
+
+ wrapper.vm.ondrop(mockEvent);
+ expect(createFlash).toHaveBeenCalledTimes(1);
+ });
+ });
+ });
+
+ it('applies correct classes when there are no designs or no design saving loader', () => {
+ createComponent({ props: { hasDesigns: false } });
+ expect(findDropzoneArea().classes()).not.toContain('gl-flex-direction-column');
+ expect(findIcon().classes()).toEqual(['gl-mr-4']);
+ });
+
+ it('applies correct classes when there are designs or design saving loader', () => {
+ createComponent({ props: { hasDesigns: true } });
+ expect(findDropzoneArea().classes()).toContain('gl-flex-direction-column');
+ expect(findIcon().classes()).toEqual(['gl-mb-2']);
+ });
+});
diff --git a/spec/frontend/design_management_new/components/upload/design_version_dropdown_spec.js b/spec/frontend/design_management_new/components/upload/design_version_dropdown_spec.js
new file mode 100644
index 00000000000..74e7f3f88fc
--- /dev/null
+++ b/spec/frontend/design_management_new/components/upload/design_version_dropdown_spec.js
@@ -0,0 +1,114 @@
+import { shallowMount } from '@vue/test-utils';
+import DesignVersionDropdown from '~/design_management_new/components/upload/design_version_dropdown.vue';
+import { GlNewDropdown, GlNewDropdownItem } from '@gitlab/ui';
+import mockAllVersions from './mock_data/all_versions';
+
+const LATEST_VERSION_ID = 3;
+const PREVIOUS_VERSION_ID = 2;
+
+const designRouteFactory = versionId => ({
+ path: `/designs?version=${versionId}`,
+ query: {
+ version: `${versionId}`,
+ },
+});
+
+const MOCK_ROUTE = {
+ path: '/designs',
+ query: {},
+};
+
+describe('Design management design version dropdown component', () => {
+ let wrapper;
+
+ function createComponent({ maxVersions = -1, $route = MOCK_ROUTE } = {}) {
+ wrapper = shallowMount(DesignVersionDropdown, {
+ propsData: {
+ projectPath: '',
+ issueIid: '',
+ },
+ mocks: {
+ $route,
+ },
+ stubs: ['router-link'],
+ });
+
+ wrapper.setData({
+ allVersions: maxVersions > -1 ? mockAllVersions.slice(0, maxVersions) : mockAllVersions,
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findVersionLink = index => wrapper.findAll('.js-version-link').at(index);
+
+ it('renders design version dropdown button', () => {
+ createComponent();
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ it('renders design version list', () => {
+ createComponent();
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ describe('selected version name', () => {
+ it('has "latest" on most recent version item', () => {
+ createComponent();
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findVersionLink(0).text()).toContain('latest');
+ });
+ });
+ });
+
+ describe('versions list', () => {
+ it('displays latest version text by default', () => {
+ createComponent();
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(GlNewDropdown).attributes('text')).toBe('Showing Latest Version');
+ });
+ });
+
+ it('displays latest version text when only 1 version is present', () => {
+ createComponent({ maxVersions: 1 });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(GlNewDropdown).attributes('text')).toBe('Showing Latest Version');
+ });
+ });
+
+ it('displays version text when the current version is not the latest', () => {
+ createComponent({ $route: designRouteFactory(PREVIOUS_VERSION_ID) });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(GlNewDropdown).attributes('text')).toBe(`Showing Version #1`);
+ });
+ });
+
+ it('displays latest version text when the current version is the latest', () => {
+ createComponent({ $route: designRouteFactory(LATEST_VERSION_ID) });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(GlNewDropdown).attributes('text')).toBe('Showing Latest Version');
+ });
+ });
+
+ it('should have the same length as apollo query', () => {
+ createComponent();
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.findAll(GlNewDropdownItem)).toHaveLength(wrapper.vm.allVersions.length);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/components/upload/mock_data/all_versions.js b/spec/frontend/design_management_new/components/upload/mock_data/all_versions.js
new file mode 100644
index 00000000000..e76bbd261bd
--- /dev/null
+++ b/spec/frontend/design_management_new/components/upload/mock_data/all_versions.js
@@ -0,0 +1,14 @@
+export default [
+ {
+ node: {
+ id: 'gid://gitlab/DesignManagement::Version/3',
+ sha: '0945756378e0b1588b9dd40d5a6b99e8b7198f55',
+ },
+ },
+ {
+ node: {
+ id: 'gid://gitlab/DesignManagement::Version/2',
+ sha: '5b063fef0cd7213b312db65b30e24f057df21b20',
+ },
+ },
+];
diff --git a/spec/frontend/design_management_new/mock_data/all_versions.js b/spec/frontend/design_management_new/mock_data/all_versions.js
new file mode 100644
index 00000000000..c389fdb8747
--- /dev/null
+++ b/spec/frontend/design_management_new/mock_data/all_versions.js
@@ -0,0 +1,8 @@
+export default [
+ {
+ node: {
+ id: 'gid://gitlab/DesignManagement::Version/1',
+ sha: 'b389071a06c153509e11da1f582005b316667001',
+ },
+ },
+];
diff --git a/spec/frontend/design_management_new/mock_data/design.js b/spec/frontend/design_management_new/mock_data/design.js
new file mode 100644
index 00000000000..675198b9408
--- /dev/null
+++ b/spec/frontend/design_management_new/mock_data/design.js
@@ -0,0 +1,74 @@
+export default {
+ id: 'design-id',
+ filename: 'test.jpg',
+ fullPath: 'full-design-path',
+ image: 'test.jpg',
+ updatedAt: '01-01-2019',
+ updatedBy: {
+ name: 'test',
+ },
+ issue: {
+ title: 'My precious issue',
+ webPath: 'full-issue-path',
+ webUrl: 'full-issue-url',
+ participants: {
+ edges: [
+ {
+ node: {
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'link-to-author',
+ avatarUrl: 'link-to-avatar',
+ },
+ },
+ ],
+ },
+ },
+ discussions: {
+ nodes: [
+ {
+ id: 'discussion-id',
+ replyId: 'discussion-reply-id',
+ resolved: false,
+ notes: {
+ nodes: [
+ {
+ id: 'note-id',
+ body: '123',
+ author: {
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'link-to-author',
+ avatarUrl: 'link-to-avatar',
+ },
+ },
+ ],
+ },
+ },
+ {
+ id: 'discussion-resolved',
+ replyId: 'discussion-reply-resolved',
+ resolved: true,
+ notes: {
+ nodes: [
+ {
+ id: 'note-resolved',
+ body: '123',
+ author: {
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'link-to-author',
+ avatarUrl: 'link-to-avatar',
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ diffRefs: {
+ headSha: 'headSha',
+ baseSha: 'baseSha',
+ startSha: 'startSha',
+ },
+};
diff --git a/spec/frontend/design_management_new/mock_data/designs.js b/spec/frontend/design_management_new/mock_data/designs.js
new file mode 100644
index 00000000000..07f5c1b7457
--- /dev/null
+++ b/spec/frontend/design_management_new/mock_data/designs.js
@@ -0,0 +1,17 @@
+import design from './design';
+
+export default {
+ project: {
+ issue: {
+ designCollection: {
+ designs: {
+ edges: [
+ {
+ node: design,
+ },
+ ],
+ },
+ },
+ },
+ },
+};
diff --git a/spec/frontend/design_management_new/mock_data/no_designs.js b/spec/frontend/design_management_new/mock_data/no_designs.js
new file mode 100644
index 00000000000..9db0ffcade2
--- /dev/null
+++ b/spec/frontend/design_management_new/mock_data/no_designs.js
@@ -0,0 +1,11 @@
+export default {
+ project: {
+ issue: {
+ designCollection: {
+ designs: {
+ edges: [],
+ },
+ },
+ },
+ },
+};
diff --git a/spec/frontend/design_management_new/mock_data/notes.js b/spec/frontend/design_management_new/mock_data/notes.js
new file mode 100644
index 00000000000..80cb3944786
--- /dev/null
+++ b/spec/frontend/design_management_new/mock_data/notes.js
@@ -0,0 +1,46 @@
+export default [
+ {
+ id: 'note-id-1',
+ index: 1,
+ position: {
+ height: 100,
+ width: 100,
+ x: 10,
+ y: 15,
+ },
+ author: {
+ name: 'John',
+ webUrl: 'link-to-john-profile',
+ },
+ createdAt: '2020-05-08T07:10:45Z',
+ userPermissions: {
+ adminNote: true,
+ },
+ discussion: {
+ id: 'discussion-id-1',
+ },
+ resolved: false,
+ },
+ {
+ id: 'note-id-2',
+ index: 2,
+ position: {
+ height: 50,
+ width: 50,
+ x: 25,
+ y: 25,
+ },
+ author: {
+ name: 'Mary',
+ webUrl: 'link-to-mary-profile',
+ },
+ createdAt: '2020-05-08T07:10:45Z',
+ userPermissions: {
+ adminNote: true,
+ },
+ discussion: {
+ id: 'discussion-id-2',
+ },
+ resolved: true,
+ },
+];
diff --git a/spec/frontend/design_management_new/pages/__snapshots__/index_spec.js.snap b/spec/frontend/design_management_new/pages/__snapshots__/index_spec.js.snap
new file mode 100644
index 00000000000..3d1fe143ac3
--- /dev/null
+++ b/spec/frontend/design_management_new/pages/__snapshots__/index_spec.js.snap
@@ -0,0 +1,317 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management index page designs does not render toolbar when there is no permission 1`] = `
+<div
+ class="gl-mt-5"
+ data-testid="designs-root"
+>
+ <!---->
+
+ <div
+ class="mt-4"
+ >
+ <ol
+ class="list-unstyled row"
+ >
+ <!---->
+
+ <li
+ class="gl-flex-direction-column col-md-6 col-lg-3 gl-mb-3"
+ data-testid="design-dropzone-wrapper"
+ >
+ <design-dropzone-stub
+ class="design-list-item design-list-item-new"
+ hasdesigns="true"
+ />
+ </li>
+
+ <li
+ class="col-md-6 col-lg-3 gl-mb-3"
+ >
+ <design-dropzone-stub
+ hasdesigns="true"
+ >
+ <design-stub
+ event="NONE"
+ filename="design-1-name"
+ id="design-1"
+ image="design-1-image"
+ notescount="0"
+ />
+ </design-dropzone-stub>
+
+ <!---->
+ </li>
+ <li
+ class="col-md-6 col-lg-3 gl-mb-3"
+ >
+ <design-dropzone-stub
+ hasdesigns="true"
+ >
+ <design-stub
+ event="NONE"
+ filename="design-2-name"
+ id="design-2"
+ image="design-2-image"
+ notescount="1"
+ />
+ </design-dropzone-stub>
+
+ <!---->
+ </li>
+ <li
+ class="col-md-6 col-lg-3 gl-mb-3"
+ >
+ <design-dropzone-stub
+ hasdesigns="true"
+ >
+ <design-stub
+ event="NONE"
+ filename="design-3-name"
+ id="design-3"
+ image="design-3-image"
+ notescount="0"
+ />
+ </design-dropzone-stub>
+
+ <!---->
+ </li>
+ </ol>
+ </div>
+
+ <router-view-stub
+ name="default"
+ />
+</div>
+`;
+
+exports[`Design management index page designs renders designs list and header with upload button 1`] = `
+<div
+ class="gl-mt-5"
+ data-testid="designs-root"
+>
+ <header
+ class="row-content-block border-top-0 p-2 d-flex"
+ >
+ <div
+ class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-w-full"
+ >
+ <div>
+ <span
+ class="gl-font-weight-bold gl-mr-3"
+ >
+ Designs
+ </span>
+
+ <design-version-dropdown-stub />
+ </div>
+
+ <div
+ class="qa-selector-toolbar gl-display-flex"
+ >
+ <gl-button-stub
+ category="tertiary"
+ class="gl-mr-2 js-select-all"
+ icon=""
+ size="small"
+ variant="link"
+ >
+ Select all
+
+ </gl-button-stub>
+
+ <div>
+ <delete-button-stub
+ buttonclass="gl-mr-4"
+ buttonsize="small"
+ buttonvariant="danger"
+ >
+
+ Delete selected
+
+ <!---->
+ </delete-button-stub>
+ </div>
+
+ <upload-button-stub />
+ </div>
+ </div>
+ </header>
+
+ <div
+ class="mt-4"
+ >
+ <ol
+ class="list-unstyled row"
+ >
+ <!---->
+
+ <li
+ class="gl-flex-direction-column col-md-6 col-lg-3 gl-mb-3"
+ data-testid="design-dropzone-wrapper"
+ >
+ <design-dropzone-stub
+ class="design-list-item design-list-item-new"
+ hasdesigns="true"
+ />
+ </li>
+
+ <li
+ class="col-md-6 col-lg-3 gl-mb-3"
+ >
+ <design-dropzone-stub
+ hasdesigns="true"
+ >
+ <design-stub
+ event="NONE"
+ filename="design-1-name"
+ id="design-1"
+ image="design-1-image"
+ notescount="0"
+ />
+ </design-dropzone-stub>
+
+ <input
+ class="design-checkbox"
+ type="checkbox"
+ />
+ </li>
+ <li
+ class="col-md-6 col-lg-3 gl-mb-3"
+ >
+ <design-dropzone-stub
+ hasdesigns="true"
+ >
+ <design-stub
+ event="NONE"
+ filename="design-2-name"
+ id="design-2"
+ image="design-2-image"
+ notescount="1"
+ />
+ </design-dropzone-stub>
+
+ <input
+ class="design-checkbox"
+ type="checkbox"
+ />
+ </li>
+ <li
+ class="col-md-6 col-lg-3 gl-mb-3"
+ >
+ <design-dropzone-stub
+ hasdesigns="true"
+ >
+ <design-stub
+ event="NONE"
+ filename="design-3-name"
+ id="design-3"
+ image="design-3-image"
+ notescount="0"
+ />
+ </design-dropzone-stub>
+
+ <input
+ class="design-checkbox"
+ type="checkbox"
+ />
+ </li>
+ </ol>
+ </div>
+
+ <router-view-stub
+ name="default"
+ />
+</div>
+`;
+
+exports[`Design management index page designs renders error 1`] = `
+<div
+ class="gl-mt-5"
+ data-testid="designs-root"
+>
+ <!---->
+
+ <div
+ class="mt-4"
+ >
+ <gl-alert-stub
+ dismisslabel="Dismiss"
+ primarybuttonlink=""
+ primarybuttontext=""
+ secondarybuttonlink=""
+ secondarybuttontext=""
+ title=""
+ variant="danger"
+ >
+
+ An error occurred while loading designs. Please try again.
+
+ </gl-alert-stub>
+ </div>
+
+ <router-view-stub
+ name="default"
+ />
+</div>
+`;
+
+exports[`Design management index page designs renders loading icon 1`] = `
+<div
+ class="gl-mt-5"
+ data-testid="designs-root"
+>
+ <!---->
+
+ <div
+ class="mt-4"
+ >
+ <gl-loading-icon-stub
+ color="orange"
+ label="Loading"
+ size="md"
+ />
+ </div>
+
+ <router-view-stub
+ name="default"
+ />
+</div>
+`;
+
+exports[`Design management index page when has no designs renders design dropzone 1`] = `
+<div
+ class="gl-mt-5"
+ data-testid="designs-root"
+>
+ <!---->
+
+ <div
+ class="mt-4"
+ >
+ <ol
+ class="list-unstyled row"
+ >
+ <span
+ class="gl-font-weight-bold gl-font-weight-bold gl-ml-5 gl-mb-4"
+ >
+ Designs
+ </span>
+
+ <li
+ class="col-12"
+ data-testid="design-dropzone-wrapper"
+ >
+ <design-dropzone-stub
+ class=""
+ />
+ </li>
+
+ </ol>
+ </div>
+
+ <router-view-stub
+ name="default"
+ />
+</div>
+`;
diff --git a/spec/frontend/design_management_new/pages/design/__snapshots__/index_spec.js.snap b/spec/frontend/design_management_new/pages/design/__snapshots__/index_spec.js.snap
new file mode 100644
index 00000000000..83bcebd513e
--- /dev/null
+++ b/spec/frontend/design_management_new/pages/design/__snapshots__/index_spec.js.snap
@@ -0,0 +1,216 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Design management design index page renders design index 1`] = `
+<div
+ class="design-detail js-design-detail fixed-top w-100 position-bottom-0 d-flex justify-content-center flex-column flex-lg-row"
+>
+ <div
+ class="d-flex overflow-hidden flex-grow-1 flex-column position-relative"
+ >
+ <design-destroyer-stub
+ filenames="test.jpg"
+ iid="1"
+ project-path="project-path"
+ />
+
+ <!---->
+
+ <design-presentation-stub
+ discussions="[object Object],[object Object]"
+ image="test.jpg"
+ imagename="test.jpg"
+ scale="1"
+ />
+
+ <div
+ class="design-scaler-wrapper position-absolute mb-4 d-flex-center"
+ >
+ <design-scaler-stub />
+ </div>
+ </div>
+
+ <div
+ class="image-notes"
+ >
+ <h2
+ class="gl-font-weight-bold gl-mt-0"
+ >
+
+ My precious issue
+
+ </h2>
+
+ <a
+ class="gl-text-gray-600 gl-text-decoration-none gl-mb-6 gl-display-block"
+ href="full-issue-url"
+ >
+ ull-issue-path
+ </a>
+
+ <participants-stub
+ class="gl-mb-4"
+ numberoflessparticipants="7"
+ participants="[object Object]"
+ />
+
+ <!---->
+
+ <design-discussion-stub
+ data-testid="unresolved-discussion"
+ designid="test"
+ discussion="[object Object]"
+ discussionwithopenform=""
+ markdownpreviewpath="/project-path/preview_markdown?target_type=Issue"
+ noteableid="design-id"
+ />
+
+ <gl-button-stub
+ category="tertiary"
+ class="link-inherit-color gl-text-black-normal gl-text-decoration-none gl-font-weight-bold gl-mb-4"
+ data-testid="resolved-comments"
+ icon="chevron-right"
+ id="resolved-comments"
+ size="medium"
+ variant="link"
+ >
+ Resolved Comments (1)
+
+ </gl-button-stub>
+
+ <gl-popover-stub
+ container="popovercontainer"
+ cssclasses=""
+ placement="top"
+ show="true"
+ target="resolved-comments"
+ title="Resolved Comments"
+ >
+ <p>
+
+ Comments you resolve can be viewed and unresolved by going to the "Resolved Comments" section below
+
+ </p>
+
+ <a
+ href="#"
+ rel="noopener noreferrer"
+ target="_blank"
+ >
+ Learn more about resolving comments
+ </a>
+ </gl-popover-stub>
+
+ <gl-collapse-stub
+ class="gl-mt-3"
+ >
+ <design-discussion-stub
+ data-testid="resolved-discussion"
+ designid="test"
+ discussion="[object Object]"
+ discussionwithopenform=""
+ markdownpreviewpath="/project-path/preview_markdown?target_type=Issue"
+ noteableid="design-id"
+ />
+ </gl-collapse-stub>
+
+ </div>
+</div>
+`;
+
+exports[`Design management design index page sets loading state 1`] = `
+<div
+ class="design-detail js-design-detail fixed-top w-100 position-bottom-0 d-flex justify-content-center flex-column flex-lg-row"
+>
+ <gl-loading-icon-stub
+ class="align-self-center"
+ color="orange"
+ label="Loading"
+ size="xl"
+ />
+</div>
+`;
+
+exports[`Design management design index page with error GlAlert is rendered in correct position with correct content 1`] = `
+<div
+ class="design-detail js-design-detail fixed-top w-100 position-bottom-0 d-flex justify-content-center flex-column flex-lg-row"
+>
+ <div
+ class="d-flex overflow-hidden flex-grow-1 flex-column position-relative"
+ >
+ <design-destroyer-stub
+ filenames="test.jpg"
+ iid="1"
+ project-path="project-path"
+ />
+
+ <div
+ class="p-3"
+ >
+ <gl-alert-stub
+ dismissible="true"
+ dismisslabel="Dismiss"
+ primarybuttonlink=""
+ primarybuttontext=""
+ secondarybuttonlink=""
+ secondarybuttontext=""
+ title=""
+ variant="danger"
+ >
+
+ woops
+
+ </gl-alert-stub>
+ </div>
+
+ <design-presentation-stub
+ discussions=""
+ image="test.jpg"
+ imagename="test.jpg"
+ scale="1"
+ />
+
+ <div
+ class="design-scaler-wrapper position-absolute mb-4 d-flex-center"
+ >
+ <design-scaler-stub />
+ </div>
+ </div>
+
+ <div
+ class="image-notes"
+ >
+ <h2
+ class="gl-font-weight-bold gl-mt-0"
+ >
+
+ My precious issue
+
+ </h2>
+
+ <a
+ class="gl-text-gray-600 gl-text-decoration-none gl-mb-6 gl-display-block"
+ href="full-issue-url"
+ >
+ ull-issue-path
+ </a>
+
+ <participants-stub
+ class="gl-mb-4"
+ numberoflessparticipants="7"
+ participants="[object Object]"
+ />
+
+ <h2
+ class="new-discussion-disclaimer gl-font-base gl-m-0 gl-mb-4"
+ data-testid="new-discussion-disclaimer"
+ >
+
+ Click the image where you'd like to start a new discussion
+
+ </h2>
+
+ <!---->
+
+ </div>
+</div>
+`;
diff --git a/spec/frontend/design_management_new/pages/design/index_spec.js b/spec/frontend/design_management_new/pages/design/index_spec.js
new file mode 100644
index 00000000000..3822b0b3b71
--- /dev/null
+++ b/spec/frontend/design_management_new/pages/design/index_spec.js
@@ -0,0 +1,294 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import VueRouter from 'vue-router';
+import { GlAlert } from '@gitlab/ui';
+import { ApolloMutation } from 'vue-apollo';
+import createFlash from '~/flash';
+import DesignIndex from '~/design_management_new/pages/design/index.vue';
+import DesignSidebar from '~/design_management_new/components/design_sidebar.vue';
+import DesignPresentation from '~/design_management_new/components/design_presentation.vue';
+import createImageDiffNoteMutation from '~/design_management_new/graphql/mutations/create_image_diff_note.mutation.graphql';
+import design from '../../mock_data/design';
+import mockResponseWithDesigns from '../../mock_data/designs';
+import mockResponseNoDesigns from '../../mock_data/no_designs';
+import mockAllVersions from '../../mock_data/all_versions';
+import {
+ DESIGN_NOT_FOUND_ERROR,
+ DESIGN_VERSION_NOT_EXIST_ERROR,
+} from '~/design_management_new/utils/error_messages';
+import { DESIGNS_ROUTE_NAME } from '~/design_management_new/router/constants';
+import createRouter from '~/design_management_new/router';
+import * as utils from '~/design_management_new/utils/design_management_utils';
+import { DESIGN_DETAIL_LAYOUT_CLASSLIST } from '~/design_management_new/constants';
+
+jest.mock('~/flash');
+jest.mock('mousetrap', () => ({
+ bind: jest.fn(),
+ unbind: jest.fn(),
+}));
+
+const focusInput = jest.fn();
+
+const DesignReplyForm = {
+ template: '<div><textarea ref="textarea"></textarea></div>',
+ methods: {
+ focusInput,
+ },
+};
+
+const localVue = createLocalVue();
+localVue.use(VueRouter);
+
+describe('Design management design index page', () => {
+ let wrapper;
+ let router;
+
+ const newComment = 'new comment';
+ const annotationCoordinates = {
+ x: 10,
+ y: 10,
+ width: 100,
+ height: 100,
+ };
+ const createDiscussionMutationVariables = {
+ mutation: createImageDiffNoteMutation,
+ update: expect.anything(),
+ variables: {
+ input: {
+ body: newComment,
+ noteableId: design.id,
+ position: {
+ headSha: 'headSha',
+ baseSha: 'baseSha',
+ startSha: 'startSha',
+ paths: {
+ newPath: 'full-design-path',
+ },
+ ...annotationCoordinates,
+ },
+ },
+ },
+ };
+
+ const mutate = jest.fn().mockResolvedValue();
+
+ const findDiscussionForm = () => wrapper.find(DesignReplyForm);
+ const findSidebar = () => wrapper.find(DesignSidebar);
+ const findDesignPresentation = () => wrapper.find(DesignPresentation);
+
+ function createComponent(loading = false, data = {}) {
+ const $apollo = {
+ queries: {
+ design: {
+ loading,
+ },
+ },
+ mutate,
+ };
+
+ router = createRouter();
+
+ wrapper = shallowMount(DesignIndex, {
+ propsData: { id: '1' },
+ mocks: { $apollo },
+ stubs: {
+ ApolloMutation,
+ DesignSidebar,
+ DesignReplyForm,
+ },
+ provide: {
+ issueIid: '1',
+ projectPath: 'project-path',
+ },
+ data() {
+ return {
+ activeDiscussion: {
+ id: null,
+ source: null,
+ },
+ ...data,
+ };
+ },
+ localVue,
+ router,
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when navigating', () => {
+ it('applies fullscreen layout', () => {
+ const mockEl = {
+ classList: {
+ add: jest.fn(),
+ remove: jest.fn(),
+ },
+ };
+ jest.spyOn(utils, 'getPageLayoutElement').mockReturnValue(mockEl);
+ createComponent(true);
+
+ wrapper.vm.$router.push('/designs/test');
+ expect(mockEl.classList.add).toHaveBeenCalledTimes(1);
+ expect(mockEl.classList.add).toHaveBeenCalledWith(...DESIGN_DETAIL_LAYOUT_CLASSLIST);
+ });
+ });
+
+ it('sets loading state', () => {
+ createComponent(true);
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders design index', () => {
+ createComponent(false, { design });
+
+ expect(wrapper.element).toMatchSnapshot();
+ expect(wrapper.find(GlAlert).exists()).toBe(false);
+ });
+
+ it('passes correct props to sidebar component', () => {
+ createComponent(false, { design });
+
+ expect(findSidebar().props()).toEqual({
+ design,
+ markdownPreviewPath: '/project-path/preview_markdown?target_type=Issue',
+ resolvedDiscussionsExpanded: false,
+ });
+ });
+
+ it('opens a new discussion form', () => {
+ createComponent(false, {
+ design: {
+ ...design,
+ discussions: {
+ nodes: [],
+ },
+ },
+ });
+
+ findDesignPresentation().vm.$emit('openCommentForm', { x: 0, y: 0 });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findDiscussionForm().exists()).toBe(true);
+ });
+ });
+
+ it('keeps new discussion form focused', () => {
+ createComponent(false, {
+ design: {
+ ...design,
+ discussions: {
+ nodes: [],
+ },
+ },
+ annotationCoordinates,
+ });
+
+ findDesignPresentation().vm.$emit('openCommentForm', { x: 10, y: 10 });
+
+ expect(focusInput).toHaveBeenCalled();
+ });
+
+ it('sends a mutation on submitting form and closes form', () => {
+ createComponent(false, {
+ design: {
+ ...design,
+ discussions: {
+ nodes: [],
+ },
+ },
+ annotationCoordinates,
+ comment: newComment,
+ });
+
+ findDiscussionForm().vm.$emit('submitForm');
+ expect(mutate).toHaveBeenCalledWith(createDiscussionMutationVariables);
+
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ return mutate({ variables: createDiscussionMutationVariables });
+ })
+ .then(() => {
+ expect(findDiscussionForm().exists()).toBe(false);
+ });
+ });
+
+ it('closes the form and clears the comment on canceling form', () => {
+ createComponent(false, {
+ design: {
+ ...design,
+ discussions: {
+ nodes: [],
+ },
+ },
+ annotationCoordinates,
+ comment: newComment,
+ });
+
+ findDiscussionForm().vm.$emit('cancelForm');
+
+ expect(wrapper.vm.comment).toBe('');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findDiscussionForm().exists()).toBe(false);
+ });
+ });
+
+ describe('with error', () => {
+ beforeEach(() => {
+ createComponent(false, {
+ design: {
+ ...design,
+ discussions: {
+ nodes: [],
+ },
+ },
+ errorMessage: 'woops',
+ });
+ });
+
+ it('GlAlert is rendered in correct position with correct content', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ describe('onDesignQueryResult', () => {
+ describe('with no designs', () => {
+ it('redirects to /designs', () => {
+ createComponent(true);
+ router.push = jest.fn();
+
+ wrapper.vm.onDesignQueryResult({ data: mockResponseNoDesigns, loading: false });
+ return wrapper.vm.$nextTick().then(() => {
+ expect(createFlash).toHaveBeenCalledTimes(1);
+ expect(createFlash).toHaveBeenCalledWith(DESIGN_NOT_FOUND_ERROR);
+ expect(router.push).toHaveBeenCalledTimes(1);
+ expect(router.push).toHaveBeenCalledWith({ name: DESIGNS_ROUTE_NAME });
+ });
+ });
+ });
+
+ describe('when no design exists for given version', () => {
+ it('redirects to /designs', () => {
+ createComponent(true);
+ wrapper.setData({
+ allVersions: mockAllVersions,
+ });
+
+ // attempt to query for a version of the design that doesn't exist
+ router.push({ query: { version: '999' } });
+ router.push = jest.fn();
+
+ wrapper.vm.onDesignQueryResult({ data: mockResponseWithDesigns, loading: false });
+ return wrapper.vm.$nextTick().then(() => {
+ expect(createFlash).toHaveBeenCalledTimes(1);
+ expect(createFlash).toHaveBeenCalledWith(DESIGN_VERSION_NOT_EXIST_ERROR);
+ expect(router.push).toHaveBeenCalledTimes(1);
+ expect(router.push).toHaveBeenCalledWith({ name: DESIGNS_ROUTE_NAME });
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/pages/index_spec.js b/spec/frontend/design_management_new/pages/index_spec.js
new file mode 100644
index 00000000000..40a462eabb8
--- /dev/null
+++ b/spec/frontend/design_management_new/pages/index_spec.js
@@ -0,0 +1,571 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { ApolloMutation } from 'vue-apollo';
+import VueRouter from 'vue-router';
+import { GlEmptyState } from '@gitlab/ui';
+import Index from '~/design_management_new/pages/index.vue';
+import uploadDesignQuery from '~/design_management_new/graphql/mutations/upload_design.mutation.graphql';
+import DesignDestroyer from '~/design_management_new/components/design_destroyer.vue';
+import DesignDropzone from '~/design_management_new/components/upload/design_dropzone.vue';
+import DeleteButton from '~/design_management_new/components/delete_button.vue';
+import { DESIGNS_ROUTE_NAME } from '~/design_management_new/router/constants';
+import {
+ EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE,
+ EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE,
+} from '~/design_management_new/utils/error_messages';
+import createFlash from '~/flash';
+import createRouter from '~/design_management_new/router';
+import * as utils from '~/design_management_new/utils/design_management_utils';
+import { DESIGN_DETAIL_LAYOUT_CLASSLIST } from '~/design_management_new/constants';
+
+jest.mock('~/flash.js');
+const mockPageEl = {
+ classList: {
+ remove: jest.fn(),
+ },
+};
+jest.spyOn(utils, 'getPageLayoutElement').mockReturnValue(mockPageEl);
+
+const localVue = createLocalVue();
+const router = createRouter();
+localVue.use(VueRouter);
+
+const mockDesigns = [
+ {
+ id: 'design-1',
+ image: 'design-1-image',
+ filename: 'design-1-name',
+ event: 'NONE',
+ notesCount: 0,
+ },
+ {
+ id: 'design-2',
+ image: 'design-2-image',
+ filename: 'design-2-name',
+ event: 'NONE',
+ notesCount: 1,
+ },
+ {
+ id: 'design-3',
+ image: 'design-3-image',
+ filename: 'design-3-name',
+ event: 'NONE',
+ notesCount: 0,
+ },
+];
+
+const mockVersion = {
+ node: {
+ id: 'gid://gitlab/DesignManagement::Version/1',
+ },
+};
+
+describe('Design management index page', () => {
+ let mutate;
+ let wrapper;
+
+ const findDesignCheckboxes = () => wrapper.findAll('.design-checkbox');
+ const findSelectAllButton = () => wrapper.find('.js-select-all');
+ const findToolbar = () => wrapper.find('.qa-selector-toolbar');
+ const findDeleteButton = () => wrapper.find(DeleteButton);
+ const findDropzone = () => wrapper.findAll(DesignDropzone).at(0);
+ const dropzoneClasses = () => findDropzone().classes();
+ const findDropzoneWrapper = () => wrapper.find('[data-testid="design-dropzone-wrapper"]');
+ const findFirstDropzoneWithDesign = () => wrapper.findAll(DesignDropzone).at(1);
+
+ function createComponent({
+ loading = false,
+ designs = [],
+ allVersions = [],
+ createDesign = true,
+ stubs = {},
+ mockMutate = jest.fn().mockResolvedValue(),
+ } = {}) {
+ mutate = mockMutate;
+ const $apollo = {
+ queries: {
+ designs: {
+ loading,
+ },
+ permissions: {
+ loading,
+ },
+ },
+ mutate,
+ };
+
+ wrapper = shallowMount(Index, {
+ data() {
+ return {
+ designs,
+ allVersions,
+ permissions: {
+ createDesign,
+ },
+ };
+ },
+ mocks: { $apollo },
+ localVue,
+ router,
+ stubs: { DesignDestroyer, ApolloMutation, ...stubs },
+ attachToDocument: true,
+ provide: {
+ projectPath: 'project-path',
+ issueIid: '1',
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('designs', () => {
+ it('renders loading icon', () => {
+ createComponent({ loading: true });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders error', () => {
+ createComponent();
+
+ wrapper.setData({ error: true });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ it('renders a toolbar with buttons when there are designs', () => {
+ createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
+
+ expect(findToolbar().exists()).toBe(true);
+ });
+
+ it('renders designs list and header with upload button', () => {
+ createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('does not render toolbar when there is no permission', () => {
+ createComponent({ designs: mockDesigns, allVersions: [mockVersion], createDesign: false });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('has correct classes applied to design dropzone', () => {
+ createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
+ expect(dropzoneClasses()).toContain('design-list-item');
+ expect(dropzoneClasses()).toContain('design-list-item-new');
+ });
+
+ it('has correct classes applied to dropzone wrapper', () => {
+ createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
+ expect(findDropzoneWrapper().classes()).toEqual([
+ 'gl-flex-direction-column',
+ 'col-md-6',
+ 'col-lg-3',
+ 'gl-mb-3',
+ ]);
+ });
+ });
+
+ describe('when has no designs', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders design dropzone', () =>
+ wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.element).toMatchSnapshot();
+ }));
+
+ it('has correct classes applied to design dropzone', () => {
+ expect(dropzoneClasses()).not.toContain('design-list-item');
+ expect(dropzoneClasses()).not.toContain('design-list-item-new');
+ });
+
+ it('has correct classes applied to dropzone wrapper', () => {
+ expect(findDropzoneWrapper().classes()).toEqual(['col-12']);
+ });
+
+ it('does not render a toolbar with buttons', () =>
+ wrapper.vm.$nextTick().then(() => {
+ expect(findToolbar().exists()).toBe(false);
+ }));
+ });
+
+ describe('uploading designs', () => {
+ it('calls mutation on upload', () => {
+ createComponent({ stubs: { GlEmptyState } });
+
+ const mutationVariables = {
+ update: expect.anything(),
+ context: {
+ hasUpload: true,
+ },
+ mutation: uploadDesignQuery,
+ variables: {
+ files: [{ name: 'test' }],
+ projectPath: 'project-path',
+ iid: '1',
+ },
+ optimisticResponse: {
+ __typename: 'Mutation',
+ designManagementUpload: {
+ __typename: 'DesignManagementUploadPayload',
+ designs: [
+ {
+ __typename: 'Design',
+ id: expect.anything(),
+ image: '',
+ imageV432x230: '',
+ filename: 'test',
+ fullPath: '',
+ event: 'NONE',
+ notesCount: 0,
+ diffRefs: {
+ __typename: 'DiffRefs',
+ baseSha: '',
+ startSha: '',
+ headSha: '',
+ },
+ discussions: {
+ __typename: 'DesignDiscussion',
+ nodes: [],
+ },
+ versions: {
+ __typename: 'DesignVersionConnection',
+ edges: {
+ __typename: 'DesignVersionEdge',
+ node: {
+ __typename: 'DesignVersion',
+ id: expect.anything(),
+ sha: expect.anything(),
+ },
+ },
+ },
+ },
+ ],
+ skippedDesigns: [],
+ errors: [],
+ },
+ },
+ };
+
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ findDropzone().vm.$emit('change', [{ name: 'test' }]);
+ expect(mutate).toHaveBeenCalledWith(mutationVariables);
+ expect(wrapper.vm.filesToBeSaved).toEqual([{ name: 'test' }]);
+ expect(wrapper.vm.isSaving).toBeTruthy();
+ })
+ .then(() => {
+ expect(dropzoneClasses()).toContain('design-list-item');
+ expect(dropzoneClasses()).toContain('design-list-item-new');
+ });
+ });
+
+ it('sets isSaving', () => {
+ createComponent();
+
+ const uploadDesign = wrapper.vm.onUploadDesign([
+ {
+ name: 'test',
+ },
+ ]);
+
+ expect(wrapper.vm.isSaving).toBe(true);
+
+ return uploadDesign.then(() => {
+ expect(wrapper.vm.isSaving).toBe(false);
+ });
+ });
+
+ it('updates state appropriately after upload complete', () => {
+ createComponent({ stubs: { GlEmptyState } });
+ wrapper.setData({ filesToBeSaved: [{ name: 'test' }] });
+
+ wrapper.vm.onUploadDesignDone();
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.vm.filesToBeSaved).toEqual([]);
+ expect(wrapper.vm.isSaving).toBeFalsy();
+ expect(wrapper.vm.isLatestVersion).toBe(true);
+ });
+ });
+
+ it('updates state appropriately after upload error', () => {
+ createComponent({ stubs: { GlEmptyState } });
+ wrapper.setData({ filesToBeSaved: [{ name: 'test' }] });
+
+ wrapper.vm.onUploadDesignError();
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.vm.filesToBeSaved).toEqual([]);
+ expect(wrapper.vm.isSaving).toBeFalsy();
+ expect(createFlash).toHaveBeenCalled();
+
+ createFlash.mockReset();
+ });
+ });
+
+ it('does not call mutation if createDesign is false', () => {
+ createComponent({ createDesign: false });
+
+ wrapper.vm.onUploadDesign([]);
+
+ expect(mutate).not.toHaveBeenCalled();
+ });
+
+ describe('upload count limit', () => {
+ const MAXIMUM_FILE_UPLOAD_LIMIT = 10;
+
+ afterEach(() => {
+ createFlash.mockReset();
+ });
+
+ it('does not warn when the max files are uploaded', () => {
+ createComponent();
+
+ wrapper.vm.onUploadDesign(new Array(MAXIMUM_FILE_UPLOAD_LIMIT).fill(mockDesigns[0]));
+
+ expect(createFlash).not.toHaveBeenCalled();
+ });
+
+ it('warns when too many files are uploaded', () => {
+ createComponent();
+
+ wrapper.vm.onUploadDesign(new Array(MAXIMUM_FILE_UPLOAD_LIMIT + 1).fill(mockDesigns[0]));
+
+ expect(createFlash).toHaveBeenCalled();
+ });
+ });
+
+ it('flashes warning if designs are skipped', () => {
+ createComponent({
+ mockMutate: () =>
+ Promise.resolve({
+ data: { designManagementUpload: { skippedDesigns: [{ filename: 'test.jpg' }] } },
+ }),
+ });
+
+ const uploadDesign = wrapper.vm.onUploadDesign([
+ {
+ name: 'test',
+ },
+ ]);
+
+ return uploadDesign.then(() => {
+ expect(createFlash).toHaveBeenCalledTimes(1);
+ expect(createFlash).toHaveBeenCalledWith(
+ 'Upload skipped. test.jpg did not change.',
+ 'warning',
+ );
+ });
+ });
+
+ describe('dragging onto an existing design', () => {
+ beforeEach(() => {
+ createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
+ });
+
+ it('calls onUploadDesign with valid upload', () => {
+ wrapper.setMethods({
+ onUploadDesign: jest.fn(),
+ });
+
+ const mockUploadPayload = [
+ {
+ name: mockDesigns[0].filename,
+ },
+ ];
+
+ const designDropzone = findFirstDropzoneWithDesign();
+ designDropzone.vm.$emit('change', mockUploadPayload);
+
+ expect(wrapper.vm.onUploadDesign).toHaveBeenCalledTimes(1);
+ expect(wrapper.vm.onUploadDesign).toHaveBeenCalledWith(mockUploadPayload);
+ });
+
+ it.each`
+ description | eventPayload | message
+ ${'> 1 file'} | ${[{ name: 'test' }, { name: 'test-2' }]} | ${EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE}
+ ${'different filename'} | ${[{ name: 'wrong-name' }]} | ${EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE}
+ `('calls createFlash when upload has $description', ({ eventPayload, message }) => {
+ const designDropzone = findFirstDropzoneWithDesign();
+ designDropzone.vm.$emit('change', eventPayload);
+
+ expect(createFlash).toHaveBeenCalledTimes(1);
+ expect(createFlash).toHaveBeenCalledWith(message);
+ });
+ });
+ });
+
+ describe('on latest version when has designs', () => {
+ beforeEach(() => {
+ createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
+ });
+
+ it('renders design checkboxes', () => {
+ expect(findDesignCheckboxes()).toHaveLength(mockDesigns.length);
+ });
+
+ it('renders toolbar buttons', () => {
+ expect(findToolbar().exists()).toBe(true);
+ expect(findToolbar().isVisible()).toBe(true);
+ });
+
+ it('adds two designs to selected designs when their checkboxes are checked', () => {
+ findDesignCheckboxes()
+ .at(0)
+ .trigger('click');
+
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ findDesignCheckboxes()
+ .at(1)
+ .trigger('click');
+
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(findDeleteButton().exists()).toBe(true);
+ expect(findSelectAllButton().text()).toBe('Deselect all');
+ findDeleteButton().vm.$emit('deleteSelectedDesigns');
+ const [{ variables }] = mutate.mock.calls[0];
+ expect(variables.filenames).toStrictEqual([
+ mockDesigns[0].filename,
+ mockDesigns[1].filename,
+ ]);
+ });
+ });
+
+ it('adds all designs to selected designs when Select All button is clicked', () => {
+ findSelectAllButton().vm.$emit('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findDeleteButton().props().hasSelectedDesigns).toBe(true);
+ expect(findSelectAllButton().text()).toBe('Deselect all');
+ expect(wrapper.vm.selectedDesigns).toEqual(mockDesigns.map(design => design.filename));
+ });
+ });
+
+ it('removes all designs from selected designs when at least one design was selected', () => {
+ findDesignCheckboxes()
+ .at(0)
+ .trigger('click');
+
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ findSelectAllButton().vm.$emit('click');
+ })
+ .then(() => {
+ expect(findDeleteButton().props().hasSelectedDesigns).toBe(false);
+ expect(findSelectAllButton().text()).toBe('Select all');
+ expect(wrapper.vm.selectedDesigns).toEqual([]);
+ });
+ });
+ });
+
+ it('on latest version when has no designs toolbar buttons are invisible', () => {
+ createComponent({ designs: [], allVersions: [mockVersion] });
+ expect(findToolbar().isVisible()).toBe(false);
+ });
+
+ describe('on non-latest version', () => {
+ beforeEach(() => {
+ createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
+
+ router.replace({
+ name: DESIGNS_ROUTE_NAME,
+ query: {
+ version: '2',
+ },
+ });
+ });
+
+ it('does not render design checkboxes', () => {
+ expect(findDesignCheckboxes()).toHaveLength(0);
+ });
+
+ it('does not render Delete selected button', () => {
+ expect(findDeleteButton().exists()).toBe(false);
+ });
+
+ it('does not render Select All button', () => {
+ expect(findSelectAllButton().exists()).toBe(false);
+ });
+ });
+
+ describe('pasting a design', () => {
+ let event;
+ beforeEach(() => {
+ createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
+
+ wrapper.setMethods({
+ onUploadDesign: jest.fn(),
+ });
+
+ event = new Event('paste');
+
+ router.replace({
+ name: DESIGNS_ROUTE_NAME,
+ query: {
+ version: '2',
+ },
+ });
+ });
+
+ it('calls onUploadDesign with valid paste', () => {
+ event.clipboardData = {
+ files: [{ name: 'image.png', type: 'image/png' }],
+ getData: () => 'test.png',
+ };
+
+ document.dispatchEvent(event);
+
+ expect(wrapper.vm.onUploadDesign).toHaveBeenCalledTimes(1);
+ expect(wrapper.vm.onUploadDesign).toHaveBeenCalledWith([
+ new File([{ name: 'image.png' }], 'test.png'),
+ ]);
+ });
+
+ it('renames a design if it has an image.png filename', () => {
+ event.clipboardData = {
+ files: [{ name: 'image.png', type: 'image/png' }],
+ getData: () => 'image.png',
+ };
+
+ document.dispatchEvent(event);
+
+ expect(wrapper.vm.onUploadDesign).toHaveBeenCalledTimes(1);
+ expect(wrapper.vm.onUploadDesign).toHaveBeenCalledWith([
+ new File([{ name: 'image.png' }], `design_${Date.now()}.png`),
+ ]);
+ });
+
+ it('does not call onUploadDesign with invalid paste', () => {
+ event.clipboardData = {
+ items: [{ type: 'text/plain' }, { type: 'text' }],
+ files: [],
+ };
+
+ document.dispatchEvent(event);
+
+ expect(wrapper.vm.onUploadDesign).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when navigating', () => {
+ it('ensures fullscreen layout is not applied', () => {
+ createComponent(true);
+
+ wrapper.vm.$router.push('/');
+ expect(mockPageEl.classList.remove).toHaveBeenCalledTimes(1);
+ expect(mockPageEl.classList.remove).toHaveBeenCalledWith(...DESIGN_DETAIL_LAYOUT_CLASSLIST);
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/router_spec.js b/spec/frontend/design_management_new/router_spec.js
new file mode 100644
index 00000000000..4d63e622724
--- /dev/null
+++ b/spec/frontend/design_management_new/router_spec.js
@@ -0,0 +1,70 @@
+import { mount, createLocalVue } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import VueRouter from 'vue-router';
+import App from '~/design_management_new/components/app.vue';
+import Designs from '~/design_management_new/pages/index.vue';
+import DesignDetail from '~/design_management_new/pages/design/index.vue';
+import createRouter from '~/design_management_new/router';
+import { DESIGNS_ROUTE_NAME, DESIGN_ROUTE_NAME } from '~/design_management_new/router/constants';
+import '~/commons/bootstrap';
+
+function factory(routeArg) {
+ const localVue = createLocalVue();
+ localVue.use(VueRouter);
+
+ window.gon = { sprite_icons: '' };
+
+ const router = createRouter('/');
+ if (routeArg !== undefined) {
+ router.push(routeArg);
+ }
+
+ return mount(App, {
+ localVue,
+ router,
+ mocks: {
+ $apollo: {
+ queries: {
+ designs: { loading: true },
+ design: { loading: true },
+ permissions: { loading: true },
+ },
+ mutate: jest.fn(),
+ },
+ },
+ });
+}
+
+jest.mock('mousetrap', () => ({
+ bind: jest.fn(),
+ unbind: jest.fn(),
+}));
+
+describe('Design management router', () => {
+ afterEach(() => {
+ window.location.hash = '';
+ });
+
+ describe.each([['/'], [{ name: DESIGNS_ROUTE_NAME }]])('root route', routeArg => {
+ it('pushes home component', () => {
+ const wrapper = factory(routeArg);
+
+ expect(wrapper.find(Designs).exists()).toBe(true);
+ });
+ });
+
+ describe.each([['/designs/1'], [{ name: DESIGN_ROUTE_NAME, params: { id: '1' } }]])(
+ 'designs detail route',
+ routeArg => {
+ it('pushes designs detail component', () => {
+ const wrapper = factory(routeArg);
+
+ return nextTick().then(() => {
+ const detail = wrapper.find(DesignDetail);
+ expect(detail.exists()).toBe(true);
+ expect(detail.props('id')).toEqual('1');
+ });
+ });
+ },
+ );
+});
diff --git a/spec/frontend/design_management_new/utils/cache_update_spec.js b/spec/frontend/design_management_new/utils/cache_update_spec.js
new file mode 100644
index 00000000000..611716d5aa7
--- /dev/null
+++ b/spec/frontend/design_management_new/utils/cache_update_spec.js
@@ -0,0 +1,44 @@
+import { InMemoryCache } from 'apollo-cache-inmemory';
+import {
+ updateStoreAfterDesignsDelete,
+ updateStoreAfterAddDiscussionComment,
+ updateStoreAfterAddImageDiffNote,
+ updateStoreAfterUploadDesign,
+ updateStoreAfterUpdateImageDiffNote,
+} from '~/design_management_new/utils/cache_update';
+import {
+ designDeletionError,
+ ADD_DISCUSSION_COMMENT_ERROR,
+ ADD_IMAGE_DIFF_NOTE_ERROR,
+ UPDATE_IMAGE_DIFF_NOTE_ERROR,
+} from '~/design_management_new/utils/error_messages';
+import design from '../mock_data/design';
+import createFlash from '~/flash';
+
+jest.mock('~/flash.js');
+
+describe('Design Management cache update', () => {
+ const mockErrors = ['code red!'];
+
+ let mockStore;
+
+ beforeEach(() => {
+ mockStore = new InMemoryCache();
+ });
+
+ describe('error handling', () => {
+ it.each`
+ fnName | subject | errorMessage | extraArgs
+ ${'updateStoreAfterDesignsDelete'} | ${updateStoreAfterDesignsDelete} | ${designDeletionError({ singular: true })} | ${[[design]]}
+ ${'updateStoreAfterAddDiscussionComment'} | ${updateStoreAfterAddDiscussionComment} | ${ADD_DISCUSSION_COMMENT_ERROR} | ${[]}
+ ${'updateStoreAfterAddImageDiffNote'} | ${updateStoreAfterAddImageDiffNote} | ${ADD_IMAGE_DIFF_NOTE_ERROR} | ${[]}
+ ${'updateStoreAfterUploadDesign'} | ${updateStoreAfterUploadDesign} | ${mockErrors[0]} | ${[]}
+ ${'updateStoreAfterUpdateImageDiffNote'} | ${updateStoreAfterUpdateImageDiffNote} | ${UPDATE_IMAGE_DIFF_NOTE_ERROR} | ${[]}
+ `('$fnName handles errors in response', ({ subject, extraArgs, errorMessage }) => {
+ expect(createFlash).not.toHaveBeenCalled();
+ expect(() => subject(mockStore, { errors: mockErrors }, {}, ...extraArgs)).toThrow();
+ expect(createFlash).toHaveBeenCalledTimes(1);
+ expect(createFlash).toHaveBeenCalledWith(errorMessage);
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/utils/design_management_utils_spec.js b/spec/frontend/design_management_new/utils/design_management_utils_spec.js
new file mode 100644
index 00000000000..8bc33e214be
--- /dev/null
+++ b/spec/frontend/design_management_new/utils/design_management_utils_spec.js
@@ -0,0 +1,176 @@
+import {
+ extractCurrentDiscussion,
+ extractDiscussions,
+ findVersionId,
+ designUploadOptimisticResponse,
+ updateImageDiffNoteOptimisticResponse,
+ isValidDesignFile,
+ extractDesign,
+} from '~/design_management_new/utils/design_management_utils';
+import mockResponseNoDesigns from '../mock_data/no_designs';
+import mockResponseWithDesigns from '../mock_data/designs';
+import mockDesign from '../mock_data/design';
+
+jest.mock('lodash/uniqueId', () => () => 1);
+
+describe('extractCurrentDiscussion', () => {
+ let discussions;
+
+ beforeEach(() => {
+ discussions = {
+ nodes: [
+ { id: 101, payload: 'w' },
+ { id: 102, payload: 'x' },
+ { id: 103, payload: 'y' },
+ { id: 104, payload: 'z' },
+ ],
+ };
+ });
+
+ it('finds the relevant discussion if it exists', () => {
+ const id = 103;
+ expect(extractCurrentDiscussion(discussions, id)).toEqual({ id, payload: 'y' });
+ });
+
+ it('returns null if the relevant discussion does not exist', () => {
+ expect(extractCurrentDiscussion(discussions, 0)).not.toBeDefined();
+ });
+});
+
+describe('extractDiscussions', () => {
+ let discussions;
+
+ beforeEach(() => {
+ discussions = {
+ nodes: [
+ { id: 1, notes: { nodes: ['a'] } },
+ { id: 2, notes: { nodes: ['b'] } },
+ { id: 3, notes: { nodes: ['c'] } },
+ { id: 4, notes: { nodes: ['d'] } },
+ ],
+ };
+ });
+
+ it('discards the edges.node artifacts of GraphQL', () => {
+ expect(extractDiscussions(discussions)).toEqual([
+ { id: 1, notes: ['a'], index: 1 },
+ { id: 2, notes: ['b'], index: 2 },
+ { id: 3, notes: ['c'], index: 3 },
+ { id: 4, notes: ['d'], index: 4 },
+ ]);
+ });
+});
+
+describe('version parser', () => {
+ it('correctly extracts version ID from a valid version string', () => {
+ const testVersionId = '123';
+ const testVersionString = `gid://gitlab/DesignManagement::Version/${testVersionId}`;
+
+ expect(findVersionId(testVersionString)).toEqual(testVersionId);
+ });
+
+ it('fails to extract version ID from an invalid version string', () => {
+ const testInvalidVersionString = `gid://gitlab/DesignManagement::Version`;
+
+ expect(findVersionId(testInvalidVersionString)).toBeUndefined();
+ });
+});
+
+describe('optimistic responses', () => {
+ it('correctly generated for designManagementUpload', () => {
+ const expectedResponse = {
+ __typename: 'Mutation',
+ designManagementUpload: {
+ __typename: 'DesignManagementUploadPayload',
+ designs: [
+ {
+ __typename: 'Design',
+ id: -1,
+ image: '',
+ imageV432x230: '',
+ filename: 'test',
+ fullPath: '',
+ notesCount: 0,
+ event: 'NONE',
+ diffRefs: { __typename: 'DiffRefs', baseSha: '', startSha: '', headSha: '' },
+ discussions: { __typename: 'DesignDiscussion', nodes: [] },
+ versions: {
+ __typename: 'DesignVersionConnection',
+ edges: {
+ __typename: 'DesignVersionEdge',
+ node: { __typename: 'DesignVersion', id: -1, sha: -1 },
+ },
+ },
+ },
+ ],
+ errors: [],
+ skippedDesigns: [],
+ },
+ };
+ expect(designUploadOptimisticResponse([{ name: 'test' }])).toEqual(expectedResponse);
+ });
+
+ it('correctly generated for updateImageDiffNoteOptimisticResponse', () => {
+ const mockNote = {
+ id: 'test-note-id',
+ };
+
+ const mockPosition = {
+ x: 10,
+ y: 10,
+ width: 10,
+ height: 10,
+ };
+
+ const expectedResponse = {
+ __typename: 'Mutation',
+ updateImageDiffNote: {
+ __typename: 'UpdateImageDiffNotePayload',
+ note: {
+ ...mockNote,
+ position: mockPosition,
+ },
+ errors: [],
+ },
+ };
+ expect(updateImageDiffNoteOptimisticResponse(mockNote, { position: mockPosition })).toEqual(
+ expectedResponse,
+ );
+ });
+});
+
+describe('isValidDesignFile', () => {
+ // test every filetype that Design Management supports
+ // https://docs.gitlab.com/ee/user/project/issues/design_management.html#limitations
+ it.each`
+ mimetype | isValid
+ ${'image/svg'} | ${true}
+ ${'image/png'} | ${true}
+ ${'image/jpg'} | ${true}
+ ${'image/jpeg'} | ${true}
+ ${'image/gif'} | ${true}
+ ${'image/bmp'} | ${true}
+ ${'image/tiff'} | ${true}
+ ${'image/ico'} | ${true}
+ ${'image/svg'} | ${true}
+ ${'video/mpeg'} | ${false}
+ ${'audio/midi'} | ${false}
+ ${'application/octet-stream'} | ${false}
+ `('returns $isValid for file type $mimetype', ({ mimetype, isValid }) => {
+ expect(isValidDesignFile({ type: mimetype })).toBe(isValid);
+ });
+});
+
+describe('extractDesign', () => {
+ describe('with no designs', () => {
+ it('returns undefined', () => {
+ expect(extractDesign(mockResponseNoDesigns)).toBeUndefined();
+ });
+ });
+
+ describe('with designs', () => {
+ it('returns the first design available', () => {
+ expect(extractDesign(mockResponseWithDesigns)).toEqual(mockDesign);
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/utils/error_messages_spec.js b/spec/frontend/design_management_new/utils/error_messages_spec.js
new file mode 100644
index 00000000000..eb5dc0fad20
--- /dev/null
+++ b/spec/frontend/design_management_new/utils/error_messages_spec.js
@@ -0,0 +1,62 @@
+import {
+ designDeletionError,
+ designUploadSkippedWarning,
+} from '~/design_management_new/utils/error_messages';
+
+const mockFilenames = n =>
+ Array(n)
+ .fill(0)
+ .map((_, i) => ({ filename: `${i + 1}.jpg` }));
+
+describe('Error message', () => {
+ describe('designDeletionError', () => {
+ const singularMsg = 'Could not delete a design. Please try again.';
+ const pluralMsg = 'Could not delete designs. Please try again.';
+
+ describe('when [singular=true]', () => {
+ it.each([[undefined], [true]])('uses singular grammar', singularOption => {
+ expect(designDeletionError({ singular: singularOption })).toEqual(singularMsg);
+ });
+ });
+
+ describe('when [singular=false]', () => {
+ it('uses plural grammar', () => {
+ expect(designDeletionError({ singular: false })).toEqual(pluralMsg);
+ });
+ });
+ });
+
+ describe.each([
+ [[], [], null],
+ [mockFilenames(1), mockFilenames(1), 'Upload skipped. 1.jpg did not change.'],
+ [
+ mockFilenames(2),
+ mockFilenames(2),
+ 'Upload skipped. The designs you tried uploading did not change.',
+ ],
+ [
+ mockFilenames(2),
+ mockFilenames(1),
+ 'Upload skipped. Some of the designs you tried uploading did not change: 1.jpg.',
+ ],
+ [
+ mockFilenames(6),
+ mockFilenames(5),
+ 'Upload skipped. Some of the designs you tried uploading did not change: 1.jpg, 2.jpg, 3.jpg, 4.jpg, 5.jpg.',
+ ],
+ [
+ mockFilenames(7),
+ mockFilenames(6),
+ 'Upload skipped. Some of the designs you tried uploading did not change: 1.jpg, 2.jpg, 3.jpg, 4.jpg, 5.jpg, and 1 more.',
+ ],
+ [
+ mockFilenames(8),
+ mockFilenames(7),
+ 'Upload skipped. Some of the designs you tried uploading did not change: 1.jpg, 2.jpg, 3.jpg, 4.jpg, 5.jpg, and 2 more.',
+ ],
+ ])('designUploadSkippedWarning', (uploadedFiles, skippedFiles, expected) => {
+ test('returns expected warning message', () => {
+ expect(designUploadSkippedWarning(uploadedFiles, skippedFiles)).toBe(expected);
+ });
+ });
+});
diff --git a/spec/frontend/design_management_new/utils/tracking_spec.js b/spec/frontend/design_management_new/utils/tracking_spec.js
new file mode 100644
index 00000000000..ac7267642cb
--- /dev/null
+++ b/spec/frontend/design_management_new/utils/tracking_spec.js
@@ -0,0 +1,59 @@
+import { mockTracking } from 'helpers/tracking_helper';
+import { trackDesignDetailView } from '~/design_management_new/utils/tracking';
+
+function getTrackingSpy(key) {
+ return mockTracking(key, undefined, jest.spyOn);
+}
+
+describe('Tracking Events', () => {
+ describe('trackDesignDetailView', () => {
+ const eventKey = 'projects:issues:design';
+ const eventName = 'view_design';
+
+ it('trackDesignDetailView fires a tracking event when called', () => {
+ const trackingSpy = getTrackingSpy(eventKey);
+
+ trackDesignDetailView();
+
+ expect(trackingSpy).toHaveBeenCalledWith(
+ eventKey,
+ eventName,
+ expect.objectContaining({
+ label: eventName,
+ context: {
+ schema: expect.any(String),
+ data: {
+ 'design-version-number': 1,
+ 'design-is-current-version': false,
+ 'internal-object-referrer': '',
+ 'design-collection-owner': '',
+ },
+ },
+ }),
+ );
+ });
+
+ it('trackDesignDetailView allows to customize the value payload', () => {
+ const trackingSpy = getTrackingSpy(eventKey);
+
+ trackDesignDetailView('from-a-test', 'test', 100, true);
+
+ expect(trackingSpy).toHaveBeenCalledWith(
+ eventKey,
+ eventName,
+ expect.objectContaining({
+ label: eventName,
+ context: {
+ schema: expect.any(String),
+ data: {
+ 'design-version-number': 100,
+ 'design-is-current-version': true,
+ 'internal-object-referrer': 'from-a-test',
+ 'design-collection-owner': 'test',
+ },
+ },
+ }),
+ );
+ });
+ });
+});
diff --git a/spec/frontend/diffs/components/app_spec.js b/spec/frontend/diffs/components/app_spec.js
index 57e3a93c6f4..b7f03f35dfb 100644
--- a/spec/frontend/diffs/components/app_spec.js
+++ b/spec/frontend/diffs/components/app_spec.js
@@ -56,6 +56,7 @@ describe('diffs/components/app', () => {
changesEmptyStateIllustration: '',
dismissEndpoint: '',
showSuggestPopover: true,
+ viewDiffsFileByFile: false,
...props,
},
provide,
@@ -829,4 +830,58 @@ describe('diffs/components/app', () => {
expect(toggleShowTreeList).not.toHaveBeenCalled();
});
});
+
+ describe('file-by-file', () => {
+ it('renders a single diff', () => {
+ createComponent({ viewDiffsFileByFile: true }, ({ state }) => {
+ state.diffs.diffFiles.push({ file_hash: '123' });
+ state.diffs.diffFiles.push({ file_hash: '312' });
+ });
+
+ expect(wrapper.findAll(DiffFile).length).toBe(1);
+ });
+
+ describe('pagination', () => {
+ it('sets previous button as disabled', () => {
+ createComponent({ viewDiffsFileByFile: true }, ({ state }) => {
+ state.diffs.diffFiles.push({ file_hash: '123' }, { file_hash: '312' });
+ });
+
+ expect(wrapper.find('[data-testid="singleFilePrevious"]').props('disabled')).toBe(true);
+ expect(wrapper.find('[data-testid="singleFileNext"]').props('disabled')).toBe(false);
+ });
+
+ it('sets next button as disabled', () => {
+ createComponent({ viewDiffsFileByFile: true }, ({ state }) => {
+ state.diffs.diffFiles.push({ file_hash: '123' }, { file_hash: '312' });
+ state.diffs.currentDiffFileId = '312';
+ });
+
+ expect(wrapper.find('[data-testid="singleFilePrevious"]').props('disabled')).toBe(false);
+ expect(wrapper.find('[data-testid="singleFileNext"]').props('disabled')).toBe(true);
+ });
+
+ it.each`
+ currentDiffFileId | button | index
+ ${'123'} | ${'singleFileNext'} | ${1}
+ ${'312'} | ${'singleFilePrevious'} | ${0}
+ `(
+ 'it calls navigateToDiffFileIndex with $index when $button is clicked',
+ ({ currentDiffFileId, button, index }) => {
+ createComponent({ viewDiffsFileByFile: true }, ({ state }) => {
+ state.diffs.diffFiles.push({ file_hash: '123' }, { file_hash: '312' });
+ state.diffs.currentDiffFileId = currentDiffFileId;
+ });
+
+ jest.spyOn(wrapper.vm, 'navigateToDiffFileIndex');
+
+ wrapper.find(`[data-testid="${button}"]`).vm.$emit('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.vm.navigateToDiffFileIndex).toHaveBeenCalledWith(index);
+ });
+ },
+ );
+ });
+ });
});
diff --git a/spec/frontend/diffs/components/diff_expansion_cell_spec.js b/spec/frontend/diffs/components/diff_expansion_cell_spec.js
index 0504f3933e0..ef2e0dfe59b 100644
--- a/spec/frontend/diffs/components/diff_expansion_cell_spec.js
+++ b/spec/frontend/diffs/components/diff_expansion_cell_spec.js
@@ -6,10 +6,10 @@ import DiffExpansionCell from '~/diffs/components/diff_expansion_cell.vue';
import { getPreviousLineIndex } from '~/diffs/store/utils';
import { INLINE_DIFF_VIEW_TYPE, PARALLEL_DIFF_VIEW_TYPE } from '~/diffs/constants';
import diffFileMockData from '../mock_data/diff_file';
+import { getByText } from '@testing-library/dom';
const EXPAND_UP_CLASS = '.js-unfold';
const EXPAND_DOWN_CLASS = '.js-unfold-down';
-const EXPAND_ALL_CLASS = '.js-unfold-all';
const LINE_TO_USE = 5;
const lineSources = {
[INLINE_DIFF_VIEW_TYPE]: 'highlighted_diff_lines',
@@ -88,7 +88,7 @@ describe('DiffExpansionCell', () => {
const findExpandUp = () => vm.$el.querySelector(EXPAND_UP_CLASS);
const findExpandDown = () => vm.$el.querySelector(EXPAND_DOWN_CLASS);
- const findExpandAll = () => vm.$el.querySelector(EXPAND_ALL_CLASS);
+ const findExpandAll = () => getByText(vm.$el, 'Show unchanged lines');
describe('top row', () => {
it('should have "expand up" and "show all" option', () => {
diff --git a/spec/frontend/diffs/components/diff_file_header_spec.js b/spec/frontend/diffs/components/diff_file_header_spec.js
index e0b7e0bc0f3..671dced080c 100644
--- a/spec/frontend/diffs/components/diff_file_header_spec.js
+++ b/spec/frontend/diffs/components/diff_file_header_spec.js
@@ -87,6 +87,7 @@ describe('DiffFileHeader component', () => {
propsData: {
diffFile,
canCurrentUserFork: false,
+ viewDiffsFileByFile: false,
...props,
},
localVue,
diff --git a/spec/frontend/diffs/components/diff_file_row_spec.js b/spec/frontend/diffs/components/diff_file_row_spec.js
index 856622b89cb..afdd4bfb335 100644
--- a/spec/frontend/diffs/components/diff_file_row_spec.js
+++ b/spec/frontend/diffs/components/diff_file_row_spec.js
@@ -73,4 +73,15 @@ describe('Diff File Row component', () => {
expect(wrapper.find(FileRowStats).exists()).toEqual(value);
});
});
+
+ it('adds is-active class when currentDiffFileId matches file_hash', () => {
+ createComponent({
+ level: 0,
+ currentDiffFileId: '123',
+ file: { fileHash: '123' },
+ hideFileStats: false,
+ });
+
+ expect(wrapper.classes('is-active')).toBe(true);
+ });
});
diff --git a/spec/frontend/diffs/components/diff_file_spec.js b/spec/frontend/diffs/components/diff_file_spec.js
index 71e975f2409..7e154d76f45 100644
--- a/spec/frontend/diffs/components/diff_file_spec.js
+++ b/spec/frontend/diffs/components/diff_file_spec.js
@@ -15,6 +15,7 @@ describe('DiffFile', () => {
vm = createComponentWithStore(Vue.extend(DiffFileComponent), createStore(), {
file: JSON.parse(JSON.stringify(diffFileMockDataReadable)),
canCurrentUserFork: false,
+ viewDiffsFileByFile: false,
}).$mount();
trackingSpy = mockTracking('_category_', vm.$el, jest.spyOn);
});
@@ -113,6 +114,7 @@ describe('DiffFile', () => {
vm = createComponentWithStore(Vue.extend(DiffFileComponent), createStore(), {
file: JSON.parse(JSON.stringify(diffFileMockDataUnreadable)),
canCurrentUserFork: false,
+ viewDiffsFileByFile: false,
}).$mount();
vm.renderIt = false;
@@ -235,6 +237,7 @@ describe('DiffFile', () => {
vm = createComponentWithStore(Vue.extend(DiffFileComponent), createStore(), {
file: JSON.parse(JSON.stringify(diffFileMockDataUnreadable)),
canCurrentUserFork: false,
+ viewDiffsFileByFile: false,
}).$mount();
jest.spyOn(vm, 'handleLoadCollapsedDiff').mockImplementation(() => {});
diff --git a/spec/frontend/diffs/components/diff_gutter_avatars_spec.js b/spec/frontend/diffs/components/diff_gutter_avatars_spec.js
index da18d8e7894..61e110b345a 100644
--- a/spec/frontend/diffs/components/diff_gutter_avatars_spec.js
+++ b/spec/frontend/diffs/components/diff_gutter_avatars_spec.js
@@ -110,7 +110,7 @@ describe('DiffGutterAvatars', () => {
it('returns truncated version of comment if it is longer than max length', () => {
const note = wrapper.vm.discussions[0].notes[1];
- expect(wrapper.vm.getTooltipText(note)).toEqual('Fatih Acet: comment 2 is r...');
+ expect(wrapper.vm.getTooltipText(note)).toEqual('Fatih Acet: comment 2 is rea…');
});
});
});
diff --git a/spec/frontend/diffs/components/diff_line_note_form_spec.js b/spec/frontend/diffs/components/diff_line_note_form_spec.js
index 623df8bd55e..75ec5c202af 100644
--- a/spec/frontend/diffs/components/diff_line_note_form_spec.js
+++ b/spec/frontend/diffs/components/diff_line_note_form_spec.js
@@ -78,10 +78,18 @@ describe('DiffLineNoteForm', () => {
.mockReturnValue(Promise.resolve());
const lineRange = {
- start_line_code: wrapper.vm.commentLineStart.lineCode,
- start_line_type: wrapper.vm.commentLineStart.type,
- end_line_code: wrapper.vm.line.line_code,
- end_line_type: wrapper.vm.line.type,
+ start: {
+ line_code: wrapper.vm.commentLineStart.line_code,
+ type: wrapper.vm.commentLineStart.type,
+ new_line: 1,
+ old_line: null,
+ },
+ end: {
+ line_code: wrapper.vm.line.line_code,
+ type: wrapper.vm.line.type,
+ new_line: 1,
+ old_line: null,
+ },
};
const formData = {
diff --git a/spec/frontend/diffs/components/diff_table_cell_spec.js b/spec/frontend/diffs/components/diff_table_cell_spec.js
index e871d86d901..9693fe68b57 100644
--- a/spec/frontend/diffs/components/diff_table_cell_spec.js
+++ b/spec/frontend/diffs/components/diff_table_cell_spec.js
@@ -100,7 +100,11 @@ describe('DiffTableCell', () => {
setWindowLocation({ href: `${TEST_HOST}?${query}` });
createComponent({ showCommentButton });
- expect(findNoteButton().exists()).toBe(expectation);
+ wrapper.setData({ isCommentButtonRendered: showCommentButton });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findNoteButton().exists()).toBe(expectation);
+ });
},
);
@@ -108,7 +112,6 @@ describe('DiffTableCell', () => {
isHover | otherProps | discussions | expectation
${true} | ${{}} | ${[]} | ${true}
${false} | ${{}} | ${[]} | ${false}
- ${true} | ${{ line: { ...line, type: 'match' } }} | ${[]} | ${false}
${true} | ${{ line: { ...line, type: 'context' } }} | ${[]} | ${false}
${true} | ${{ line: { ...line, type: 'old-nonewline' } }} | ${[]} | ${false}
${true} | ${{}} | ${[{}]} | ${false}
@@ -122,7 +125,13 @@ describe('DiffTableCell', () => {
...otherProps,
});
- expect(findNoteButton().isVisible()).toBe(expectation);
+ wrapper.setData({
+ isCommentButtonRendered: true,
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findNoteButton().isVisible()).toBe(expectation);
+ });
},
);
});
diff --git a/spec/frontend/diffs/components/inline_diff_table_row_spec.js b/spec/frontend/diffs/components/inline_diff_table_row_spec.js
index 66349727b11..f929f97b598 100644
--- a/spec/frontend/diffs/components/inline_diff_table_row_spec.js
+++ b/spec/frontend/diffs/components/inline_diff_table_row_spec.js
@@ -1,27 +1,31 @@
-import Vue from 'vue';
-import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
+import { shallowMount } from '@vue/test-utils';
import { createStore } from '~/mr_notes/stores';
import InlineDiffTableRow from '~/diffs/components/inline_diff_table_row.vue';
import diffFileMockData from '../mock_data/diff_file';
describe('InlineDiffTableRow', () => {
+ let wrapper;
let vm;
const thisLine = diffFileMockData.highlighted_diff_lines[0];
beforeEach(() => {
- vm = createComponentWithStore(Vue.extend(InlineDiffTableRow), createStore(), {
- line: thisLine,
- fileHash: diffFileMockData.file_hash,
- filePath: diffFileMockData.file_path,
- contextLinesPath: 'contextLinesPath',
- isHighlighted: false,
- }).$mount();
+ wrapper = shallowMount(InlineDiffTableRow, {
+ store: createStore(),
+ propsData: {
+ line: thisLine,
+ fileHash: diffFileMockData.file_hash,
+ filePath: diffFileMockData.file_path,
+ contextLinesPath: 'contextLinesPath',
+ isHighlighted: false,
+ },
+ });
+ vm = wrapper.vm;
});
it('does not add hll class to line content when line does not match highlighted row', done => {
vm.$nextTick()
.then(() => {
- expect(vm.$el.querySelector('.line_content').classList).not.toContain('hll');
+ expect(wrapper.find('.line_content').classes('hll')).toBe(false);
})
.then(done)
.catch(done.fail);
@@ -35,12 +39,19 @@ describe('InlineDiffTableRow', () => {
return vm.$nextTick();
})
.then(() => {
- expect(vm.$el.querySelector('.line_content').classList).toContain('hll');
+ expect(wrapper.find('.line_content').classes('hll')).toBe(true);
})
.then(done)
.catch(done.fail);
});
+ it('adds hll class to lineContent when line is part of a multiline comment', () => {
+ wrapper.setProps({ isCommented: true });
+ return vm.$nextTick().then(() => {
+ expect(wrapper.find('.line_content').classes('hll')).toBe(true);
+ });
+ });
+
describe('sets coverage title and class', () => {
it('for lines with coverage', done => {
vm.$nextTick()
@@ -53,10 +64,10 @@ describe('InlineDiffTableRow', () => {
return vm.$nextTick();
})
.then(() => {
- const coverage = vm.$el.querySelector('.line-coverage');
+ const coverage = wrapper.find('.line-coverage');
- expect(coverage.title).toContain('Test coverage: 5 hits');
- expect(coverage.classList).toContain('coverage');
+ expect(coverage.attributes('title')).toContain('Test coverage: 5 hits');
+ expect(coverage.classes('coverage')).toBe(true);
})
.then(done)
.catch(done.fail);
@@ -73,10 +84,10 @@ describe('InlineDiffTableRow', () => {
return vm.$nextTick();
})
.then(() => {
- const coverage = vm.$el.querySelector('.line-coverage');
+ const coverage = wrapper.find('.line-coverage');
- expect(coverage.title).toContain('No test coverage');
- expect(coverage.classList).toContain('no-coverage');
+ expect(coverage.attributes('title')).toContain('No test coverage');
+ expect(coverage.classes('no-coverage')).toBe(true);
})
.then(done)
.catch(done.fail);
@@ -90,11 +101,11 @@ describe('InlineDiffTableRow', () => {
return vm.$nextTick();
})
.then(() => {
- const coverage = vm.$el.querySelector('.line-coverage');
+ const coverage = wrapper.find('.line-coverage');
- expect(coverage.title).not.toContain('Coverage');
- expect(coverage.classList).not.toContain('coverage');
- expect(coverage.classList).not.toContain('no-coverage');
+ expect(coverage.attributes('title')).toBeUndefined();
+ expect(coverage.classes('coverage')).toBe(false);
+ expect(coverage.classes('no-coverage')).toBe(false);
})
.then(done)
.catch(done.fail);
diff --git a/spec/frontend/diffs/components/no_changes_spec.js b/spec/frontend/diffs/components/no_changes_spec.js
index 245651af61c..2eca97a47fd 100644
--- a/spec/frontend/diffs/components/no_changes_spec.js
+++ b/spec/frontend/diffs/components/no_changes_spec.js
@@ -2,6 +2,7 @@ import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import { createStore } from '~/mr_notes/stores';
import NoChanges from '~/diffs/components/no_changes.vue';
+import { GlButton } from '@gitlab/ui';
describe('Diff no changes empty state', () => {
let vm;
@@ -37,4 +38,11 @@ describe('Diff no changes empty state', () => {
expect(vm.contains('script')).toBe(false);
});
+
+ describe('Renders', () => {
+ it('Show create commit button', () => {
+ createComponent();
+ expect(vm.find(GlButton).exists()).toBe(true);
+ });
+ });
});
diff --git a/spec/frontend/diffs/components/parallel_diff_table_row_spec.js b/spec/frontend/diffs/components/parallel_diff_table_row_spec.js
index 6b92d448cf5..339352943a9 100644
--- a/spec/frontend/diffs/components/parallel_diff_table_row_spec.js
+++ b/spec/frontend/diffs/components/parallel_diff_table_row_spec.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import { shallowMount } from '@vue/test-utils';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import { createStore } from '~/mr_notes/stores';
import ParallelDiffTableRow from '~/diffs/components/parallel_diff_table_row.vue';
@@ -6,18 +7,24 @@ import diffFileMockData from '../mock_data/diff_file';
describe('ParallelDiffTableRow', () => {
describe('when one side is empty', () => {
+ let wrapper;
let vm;
const thisLine = diffFileMockData.parallel_diff_lines[0];
const rightLine = diffFileMockData.parallel_diff_lines[0].right;
beforeEach(() => {
- vm = createComponentWithStore(Vue.extend(ParallelDiffTableRow), createStore(), {
- line: thisLine,
- fileHash: diffFileMockData.file_hash,
- filePath: diffFileMockData.file_path,
- contextLinesPath: 'contextLinesPath',
- isHighlighted: false,
- }).$mount();
+ wrapper = shallowMount(ParallelDiffTableRow, {
+ store: createStore(),
+ propsData: {
+ line: thisLine,
+ fileHash: diffFileMockData.file_hash,
+ filePath: diffFileMockData.file_path,
+ contextLinesPath: 'contextLinesPath',
+ isHighlighted: false,
+ },
+ });
+
+ vm = wrapper.vm;
});
it('does not highlight non empty line content when line does not match highlighted row', done => {
@@ -42,6 +49,13 @@ describe('ParallelDiffTableRow', () => {
.then(done)
.catch(done.fail);
});
+
+ it('highlights nonempty line content when line is part of a multiline comment', () => {
+ wrapper.setProps({ isCommented: true });
+ return vm.$nextTick().then(() => {
+ expect(vm.$el.querySelector('.line_content.right-side').classList).toContain('hll');
+ });
+ });
});
describe('when both sides have content', () => {
diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js
index 7d79dcfbfe3..ec6ad031813 100644
--- a/spec/frontend/diffs/store/actions_spec.js
+++ b/spec/frontend/diffs/store/actions_spec.js
@@ -6,6 +6,8 @@ import {
INLINE_DIFF_VIEW_TYPE,
PARALLEL_DIFF_VIEW_TYPE,
DIFFS_PER_PAGE,
+ DIFF_WHITESPACE_COOKIE_NAME,
+ SHOW_WHITESPACE,
} from '~/diffs/constants';
import {
setBaseConfig,
@@ -44,6 +46,8 @@ import {
setSuggestPopoverDismissed,
changeCurrentCommit,
moveToNeighboringCommit,
+ setCurrentDiffFileIdFromNote,
+ navigateToDiffFileIndex,
} from '~/diffs/store/actions';
import eventHub from '~/notes/event_hub';
import * as types from '~/diffs/store/mutation_types';
@@ -55,6 +59,7 @@ import { mergeUrlParams } from '~/lib/utils/url_utility';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import { diffMetadata } from '../mock_data/diff_metadata';
import createFlash from '~/flash';
+import { TEST_HOST } from 'jest/helpers/test_constants';
jest.mock('~/flash', () => jest.fn());
@@ -187,8 +192,8 @@ describe('DiffsStoreActions', () => {
it('should fetch batch diff files', done => {
const endpointBatch = '/fetch/diffs_batch';
- const res1 = { diff_files: [], pagination: { next_page: 2 } };
- const res2 = { diff_files: [], pagination: {} };
+ const res1 = { diff_files: [{ file_hash: 'test' }], pagination: { next_page: 2 } };
+ const res2 = { diff_files: [{ file_hash: 'test2' }], pagination: {} };
mock
.onGet(
mergeUrlParams(
@@ -224,8 +229,10 @@ describe('DiffsStoreActions', () => {
{ type: types.SET_RETRIEVING_BATCHES, payload: true },
{ type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: res1.diff_files } },
{ type: types.SET_BATCH_LOADING, payload: false },
- { type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: [] } },
+ { type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: 'test' },
+ { type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: res2.diff_files } },
{ type: types.SET_BATCH_LOADING, payload: false },
+ { type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: 'test2' },
{ type: types.SET_RETRIEVING_BATCHES, payload: false },
],
[],
@@ -309,6 +316,7 @@ describe('DiffsStoreActions', () => {
showWhitespace: false,
diffViewType: 'inline',
useSingleDiffStyle: false,
+ currentDiffFileId: null,
},
[
{ type: types.SET_LOADING, payload: true },
@@ -345,8 +353,8 @@ describe('DiffsStoreActions', () => {
it('should fetch batch diff files', done => {
const endpointBatch = '/fetch/diffs_batch';
- const res1 = { diff_files: [], pagination: { next_page: 2 } };
- const res2 = { diff_files: [], pagination: {} };
+ const res1 = { diff_files: [{ file_hash: 'test' }], pagination: { next_page: 2 } };
+ const res2 = { diff_files: [{ file_hash: 'test2' }], pagination: {} };
mock
.onGet(mergeUrlParams({ per_page: DIFFS_PER_PAGE, w: '1', page: 1 }, endpointBatch))
.reply(200, res1)
@@ -356,14 +364,16 @@ describe('DiffsStoreActions', () => {
testAction(
fetchDiffFilesBatch,
{},
- { endpointBatch, useSingleDiffStyle: false },
+ { endpointBatch, useSingleDiffStyle: false, currentDiffFileId: null },
[
{ type: types.SET_BATCH_LOADING, payload: true },
{ type: types.SET_RETRIEVING_BATCHES, payload: true },
{ type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: res1.diff_files } },
{ type: types.SET_BATCH_LOADING, payload: false },
- { type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: [] } },
+ { type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: 'test' },
+ { type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: res2.diff_files } },
{ type: types.SET_BATCH_LOADING, payload: false },
+ { type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: 'test2' },
{ type: types.SET_RETRIEVING_BATCHES, payload: false },
],
[],
@@ -475,6 +485,10 @@ describe('DiffsStoreActions', () => {
});
describe('assignDiscussionsToDiff', () => {
+ afterEach(() => {
+ window.location.hash = '';
+ });
+
it('should merge discussions into diffs', done => {
window.location.hash = 'ABC_123';
@@ -568,6 +582,19 @@ describe('DiffsStoreActions', () => {
done,
);
});
+
+ it('dispatches setCurrentDiffFileIdFromNote with note ID', done => {
+ window.location.hash = 'note_123';
+
+ testAction(
+ assignDiscussionsToDiff,
+ [],
+ { diffFiles: [], useSingleDiffStyle: true },
+ [],
+ [{ type: 'setCurrentDiffFileIdFromNote', payload: '123' }],
+ done,
+ );
+ });
});
describe('removeDiscussionsFromDiff', () => {
@@ -1187,10 +1214,10 @@ describe('DiffsStoreActions', () => {
);
});
- it('sets localStorage', () => {
+ it('sets cookie', () => {
setShowWhitespace({ commit() {} }, { showWhitespace: true });
- expect(localStorage.setItem).toHaveBeenCalledWith('mr_show_whitespace', true);
+ expect(Cookies.get(DIFF_WHITESPACE_COOKIE_NAME)).toEqual(SHOW_WHITESPACE);
});
it('calls history pushState', () => {
@@ -1250,12 +1277,12 @@ describe('DiffsStoreActions', () => {
describe('success', () => {
beforeEach(() => {
- mock.onGet(`${gl.TEST_HOST}/context`).replyOnce(200, ['test']);
+ mock.onGet(`${TEST_HOST}/context`).replyOnce(200, ['test']);
});
it('commits the success and dispatches an action to expand the new lines', done => {
const file = {
- context_lines_path: `${gl.TEST_HOST}/context`,
+ context_lines_path: `${TEST_HOST}/context`,
file_path: 'test',
file_hash: 'test',
};
@@ -1272,13 +1299,13 @@ describe('DiffsStoreActions', () => {
describe('error', () => {
beforeEach(() => {
- mock.onGet(`${gl.TEST_HOST}/context`).replyOnce(500);
+ mock.onGet(`${TEST_HOST}/context`).replyOnce(500);
});
it('dispatches receiveFullDiffError', done => {
testAction(
fetchFullDiff,
- { context_lines_path: `${gl.TEST_HOST}/context`, file_path: 'test', file_hash: 'test' },
+ { context_lines_path: `${TEST_HOST}/context`, file_path: 'test', file_hash: 'test' },
null,
[],
[{ type: 'receiveFullDiffError', payload: 'test' }],
@@ -1442,7 +1469,7 @@ describe('DiffsStoreActions', () => {
describe('setSuggestPopoverDismissed', () => {
it('commits SET_SHOW_SUGGEST_POPOVER', done => {
- const state = { dismissEndpoint: `${gl.TEST_HOST}/-/user_callouts` };
+ const state = { dismissEndpoint: `${TEST_HOST}/-/user_callouts` };
const mock = new MockAdapter(axios);
mock.onPost(state.dismissEndpoint).reply(200, {});
@@ -1563,4 +1590,31 @@ describe('DiffsStoreActions', () => {
},
);
});
+
+ describe('setCurrentDiffFileIdFromNote', () => {
+ it('commits UPDATE_CURRENT_DIFF_FILE_ID', () => {
+ const commit = jest.fn();
+ const rootGetters = {
+ getDiscussion: () => ({ diff_file: { file_hash: '123' } }),
+ notesById: { '1': { discussion_id: '2' } },
+ };
+
+ setCurrentDiffFileIdFromNote({ commit, rootGetters }, '1');
+
+ expect(commit).toHaveBeenCalledWith(types.UPDATE_CURRENT_DIFF_FILE_ID, '123');
+ });
+ });
+
+ describe('navigateToDiffFileIndex', () => {
+ it('commits UPDATE_CURRENT_DIFF_FILE_ID', done => {
+ testAction(
+ navigateToDiffFileIndex,
+ 0,
+ { diffFiles: [{ file_hash: '123' }] },
+ [{ type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: '123' }],
+ [],
+ done,
+ );
+ });
+ });
});
diff --git a/spec/frontend/diffs/store/utils_spec.js b/spec/frontend/diffs/store/utils_spec.js
index 891de45e268..d87619e1e3c 100644
--- a/spec/frontend/diffs/store/utils_spec.js
+++ b/spec/frontend/diffs/store/utils_spec.js
@@ -1090,4 +1090,26 @@ describe('DiffsStoreUtils', () => {
]);
});
});
+
+ describe('getDefaultWhitespace', () => {
+ it('defaults to true if querystring and cookie are undefined', () => {
+ expect(utils.getDefaultWhitespace()).toBe(true);
+ });
+
+ it('returns false if querystring is `1`', () => {
+ expect(utils.getDefaultWhitespace('1', '0')).toBe(false);
+ });
+
+ it('returns true if querystring is `0`', () => {
+ expect(utils.getDefaultWhitespace('0', undefined)).toBe(true);
+ });
+
+ it('returns false if cookie is `1`', () => {
+ expect(utils.getDefaultWhitespace(undefined, '1')).toBe(false);
+ });
+
+ it('returns true if cookie is `0`', () => {
+ expect(utils.getDefaultWhitespace(undefined, '0')).toBe(true);
+ });
+ });
});
diff --git a/spec/frontend/editor/editor_lite_spec.js b/spec/frontend/editor/editor_lite_spec.js
index cb07bcf8f28..92a136835bf 100644
--- a/spec/frontend/editor/editor_lite_spec.js
+++ b/spec/frontend/editor/editor_lite_spec.js
@@ -115,6 +115,76 @@ describe('Base editor', () => {
});
});
+ describe('extensions', () => {
+ const foo1 = jest.fn();
+ const foo2 = jest.fn();
+ const bar = jest.fn();
+ const MyExt1 = {
+ foo: foo1,
+ };
+ const MyExt2 = {
+ bar,
+ };
+ const MyExt3 = {
+ foo: foo2,
+ };
+ beforeEach(() => {
+ editor.createInstance({ el: editorEl, blobPath, blobContent });
+ });
+
+ afterEach(() => {
+ editor.model.dispose();
+ });
+
+ it('is extensible with the extensions', () => {
+ expect(editor.foo).toBeUndefined();
+
+ editor.use(MyExt1);
+ expect(editor.foo).toEqual(foo1);
+ });
+
+ it('does not fail if no extensions supplied', () => {
+ const spy = jest.spyOn(global.console, 'error');
+ editor.use();
+
+ expect(spy).not.toHaveBeenCalled();
+ });
+
+ it('is extensible with multiple extensions', () => {
+ expect(editor.foo).toBeUndefined();
+ expect(editor.bar).toBeUndefined();
+
+ editor.use([MyExt1, MyExt2]);
+
+ expect(editor.foo).toEqual(foo1);
+ expect(editor.bar).toEqual(bar);
+ });
+
+ it('uses the last definition of a method in case of an overlap', () => {
+ editor.use([MyExt1, MyExt2, MyExt3]);
+ expect(editor).toEqual(
+ expect.objectContaining({
+ foo: foo2,
+ bar,
+ }),
+ );
+ });
+
+ it('correctly resolves references withing extensions', () => {
+ const FunctionExt = {
+ inst() {
+ return this.instance;
+ },
+ mod() {
+ return this.model;
+ },
+ };
+ editor.use(FunctionExt);
+ expect(editor.inst()).toEqual(editor.instance);
+ expect(editor.mod()).toEqual(editor.model);
+ });
+ });
+
describe('languages', () => {
it('registers custom languages defined with Monaco', () => {
expect(monacoLanguages.getLanguages()).toEqual(
diff --git a/spec/frontend/editor/editor_markdown_ext_spec.js b/spec/frontend/editor/editor_markdown_ext_spec.js
new file mode 100644
index 00000000000..aad2400c0f0
--- /dev/null
+++ b/spec/frontend/editor/editor_markdown_ext_spec.js
@@ -0,0 +1,204 @@
+import EditorLite from '~/editor/editor_lite';
+import { Range, Position } from 'monaco-editor';
+import EditorMarkdownExtension from '~/editor/editor_markdown_ext';
+
+describe('Markdown Extension for Editor Lite', () => {
+ let editor;
+ let editorEl;
+ const firstLine = 'This is a';
+ const secondLine = 'multiline';
+ const thirdLine = 'string with some **markup**';
+ const text = `${firstLine}\n${secondLine}\n${thirdLine}`;
+ const filePath = 'foo.md';
+
+ const setSelection = (startLineNumber = 1, startColumn = 1, endLineNumber = 1, endColumn = 1) => {
+ const selection = new Range(startLineNumber, startColumn, endLineNumber, endColumn);
+ editor.instance.setSelection(selection);
+ };
+ const selectSecondString = () => setSelection(2, 1, 2, secondLine.length + 1); // select the whole second line
+ const selectSecondAndThirdLines = () => setSelection(2, 1, 3, thirdLine.length + 1); // select second and third lines
+
+ const selectionToString = () => editor.instance.getSelection().toString();
+ const positionToString = () => editor.instance.getPosition().toString();
+
+ beforeEach(() => {
+ setFixtures('<div id="editor" data-editor-loading></div>');
+ editorEl = document.getElementById('editor');
+ editor = new EditorLite();
+ editor.createInstance({
+ el: editorEl,
+ blobPath: filePath,
+ blobContent: text,
+ });
+ editor.use(EditorMarkdownExtension);
+ });
+
+ afterEach(() => {
+ editor.instance.dispose();
+ editor.model.dispose();
+ editorEl.remove();
+ });
+
+ describe('getSelectedText', () => {
+ it('does not fail if there is no selection and returns the empty string', () => {
+ jest.spyOn(editor.instance, 'getSelection');
+ const resText = editor.getSelectedText();
+
+ expect(editor.instance.getSelection).toHaveBeenCalled();
+ expect(resText).toBe('');
+ });
+
+ it.each`
+ description | selection | expectedString
+ ${'same-line'} | ${[1, 1, 1, firstLine.length + 1]} | ${firstLine}
+ ${'two-lines'} | ${[1, 1, 2, secondLine.length + 1]} | ${`${firstLine}\n${secondLine}`}
+ ${'multi-lines'} | ${[1, 1, 3, thirdLine.length + 1]} | ${text}
+ `('correctly returns selected text for $description', ({ selection, expectedString }) => {
+ setSelection(...selection);
+
+ const resText = editor.getSelectedText();
+
+ expect(resText).toBe(expectedString);
+ });
+
+ it('accepts selection object that serves as a source instead of current selection', () => {
+ selectSecondString();
+ const firstLineSelection = new Range(1, 1, 1, firstLine.length + 1);
+
+ const resText = editor.getSelectedText(firstLineSelection);
+
+ expect(resText).toBe(firstLine);
+ });
+ });
+
+ describe('replaceSelectedText', () => {
+ const expectedStr = 'foo';
+
+ it('replaces selected text with the supplied one', () => {
+ selectSecondString();
+ editor.replaceSelectedText(expectedStr);
+
+ expect(editor.getValue()).toBe(`${firstLine}\n${expectedStr}\n${thirdLine}`);
+ });
+
+ it('prepends the supplied text if no text is selected', () => {
+ editor.replaceSelectedText(expectedStr);
+ expect(editor.getValue()).toBe(`${expectedStr}${firstLine}\n${secondLine}\n${thirdLine}`);
+ });
+
+ it('replaces selection with empty string if no text is supplied', () => {
+ selectSecondString();
+ editor.replaceSelectedText();
+ expect(editor.getValue()).toBe(`${firstLine}\n\n${thirdLine}`);
+ });
+
+ it('puts cursor at the end of the new string and collapses selection by default', () => {
+ selectSecondString();
+ editor.replaceSelectedText(expectedStr);
+
+ expect(positionToString()).toBe(`(2,${expectedStr.length + 1})`);
+ expect(selectionToString()).toBe(
+ `[2,${expectedStr.length + 1} -> 2,${expectedStr.length + 1}]`,
+ );
+ });
+
+ it('puts cursor at the end of the new string and keeps selection if "select" is supplied', () => {
+ const select = 'url';
+ const complexReplacementString = `[${secondLine}](${select})`;
+ selectSecondString();
+ editor.replaceSelectedText(complexReplacementString, select);
+
+ expect(positionToString()).toBe(`(2,${complexReplacementString.length + 1})`);
+ expect(selectionToString()).toBe(`[2,1 -> 2,${complexReplacementString.length + 1}]`);
+ });
+ });
+
+ describe('moveCursor', () => {
+ const setPosition = endCol => {
+ const currentPos = new Position(2, endCol);
+ editor.instance.setPosition(currentPos);
+ };
+
+ it.each`
+ direction | condition | startColumn | shift | endPosition
+ ${'left'} | ${'negative'} | ${secondLine.length + 1} | ${-1} | ${`(2,${secondLine.length})`}
+ ${'left'} | ${'negative'} | ${secondLine.length} | ${secondLine.length * -1} | ${'(2,1)'}
+ ${'right'} | ${'positive'} | ${1} | ${1} | ${'(2,2)'}
+ ${'right'} | ${'positive'} | ${2} | ${secondLine.length} | ${`(2,${secondLine.length + 1})`}
+ ${'up'} | ${'positive'} | ${1} | ${[0, -1]} | ${'(1,1)'}
+ ${'top of file'} | ${'positive'} | ${1} | ${[0, -100]} | ${'(1,1)'}
+ ${'down'} | ${'negative'} | ${1} | ${[0, 1]} | ${'(3,1)'}
+ ${'end of file'} | ${'negative'} | ${1} | ${[0, 100]} | ${`(3,${thirdLine.length + 1})`}
+ ${'end of line'} | ${'too large'} | ${1} | ${secondLine.length + 100} | ${`(2,${secondLine.length + 1})`}
+ ${'start of line'} | ${'too low'} | ${1} | ${-100} | ${'(2,1)'}
+ `(
+ 'moves cursor to the $direction if $condition supplied',
+ ({ startColumn, shift, endPosition }) => {
+ setPosition(startColumn);
+ if (Array.isArray(shift)) {
+ editor.moveCursor(...shift);
+ } else {
+ editor.moveCursor(shift);
+ }
+ expect(positionToString()).toBe(endPosition);
+ },
+ );
+ });
+
+ describe('selectWithinSelection', () => {
+ it('scopes down current selection to supplied text', () => {
+ const selectedText = `${secondLine}\n${thirdLine}`;
+ const toSelect = 'string';
+ selectSecondAndThirdLines();
+
+ expect(selectionToString()).toBe(`[2,1 -> 3,${thirdLine.length + 1}]`);
+
+ editor.selectWithinSelection(toSelect, selectedText);
+ expect(selectionToString()).toBe(`[3,1 -> 3,${toSelect.length + 1}]`);
+ });
+
+ it('does not fail when only `toSelect` is supplied and fetches the text from selection', () => {
+ jest.spyOn(editor, 'getSelectedText');
+ const toSelect = 'string';
+ selectSecondAndThirdLines();
+
+ editor.selectWithinSelection(toSelect);
+
+ expect(editor.getSelectedText).toHaveBeenCalled();
+ expect(selectionToString()).toBe(`[3,1 -> 3,${toSelect.length + 1}]`);
+ });
+
+ it('does nothing if no `toSelect` is supplied', () => {
+ selectSecondAndThirdLines();
+ const expectedPos = `(3,${thirdLine.length + 1})`;
+ const expectedSelection = `[2,1 -> 3,${thirdLine.length + 1}]`;
+
+ expect(positionToString()).toBe(expectedPos);
+ expect(selectionToString()).toBe(expectedSelection);
+
+ editor.selectWithinSelection();
+
+ expect(positionToString()).toBe(expectedPos);
+ expect(selectionToString()).toBe(expectedSelection);
+ });
+
+ it('does nothing if no selection is set in the editor', () => {
+ const expectedPos = '(1,1)';
+ const expectedSelection = '[1,1 -> 1,1]';
+ const toSelect = 'string';
+
+ expect(positionToString()).toBe(expectedPos);
+ expect(selectionToString()).toBe(expectedSelection);
+
+ editor.selectWithinSelection(toSelect);
+
+ expect(positionToString()).toBe(expectedPos);
+ expect(selectionToString()).toBe(expectedSelection);
+
+ editor.selectWithinSelection();
+
+ expect(positionToString()).toBe(expectedPos);
+ expect(selectionToString()).toBe(expectedSelection);
+ });
+ });
+});
diff --git a/spec/frontend/emoji_spec.js b/spec/frontend/emoji/emoji_spec.js
index 25bc95e0dd6..c6a15d5976a 100644
--- a/spec/frontend/emoji_spec.js
+++ b/spec/frontend/emoji/emoji_spec.js
@@ -1,4 +1,6 @@
-import { glEmojiTag } from '~/emoji';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import { initEmojiMap, glEmojiTag, EMOJI_VERSION } from '~/emoji';
import isEmojiUnicodeSupported, {
isFlagEmoji,
isRainbowFlagEmoji,
@@ -7,6 +9,7 @@ import isEmojiUnicodeSupported, {
isHorceRacingSkinToneComboEmoji,
isPersonZwjEmoji,
} from '~/emoji/support/is_emoji_unicode_supported';
+import { trimText } from 'helpers/text_helper';
const emptySupportMap = {
personZwj: false,
@@ -50,77 +53,28 @@ const emojiFixtureMap = {
},
};
-function markupToDomElement(markup) {
- const div = document.createElement('div');
- div.innerHTML = markup;
- return div.firstElementChild;
-}
-
-function testGlEmojiImageFallback(element, name, src) {
- expect(element.tagName.toLowerCase()).toBe('img');
- expect(element.getAttribute('src')).toBe(src);
- expect(element.getAttribute('title')).toBe(`:${name}:`);
- expect(element.getAttribute('alt')).toBe(`:${name}:`);
-}
-
-const defaults = {
- forceFallback: false,
- sprite: false,
-};
+describe('gl_emoji', () => {
+ let mock;
+ const emojiData = getJSONFixture('emojis/emojis.json');
-function testGlEmojiElement(element, name, unicodeVersion, unicodeMoji, options = {}) {
- const opts = { ...defaults, ...options };
- expect(element.tagName.toLowerCase()).toBe('gl-emoji');
- expect(element.dataset.name).toBe(name);
- expect(element.dataset.fallbackSrc.length).toBeGreaterThan(0);
- expect(element.dataset.unicodeVersion).toBe(unicodeVersion);
-
- const fallbackSpriteClass = `emoji-${name}`;
- if (opts.sprite) {
- expect(element.dataset.fallbackSpriteClass).toBe(fallbackSpriteClass);
- }
-
- if (opts.forceFallback && opts.sprite) {
- expect(element.getAttribute('class')).toBe(`emoji-icon ${fallbackSpriteClass}`);
- }
-
- if (opts.forceFallback && !opts.sprite) {
- // Check for image fallback
- testGlEmojiImageFallback(element.firstElementChild, name, element.dataset.fallbackSrc);
- } else {
- // Otherwise make sure things are still unicode text
- expect(element.textContent.trim()).toBe(unicodeMoji);
- }
-}
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, emojiData);
+
+ return initEmojiMap().catch(() => {});
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
-describe('gl_emoji', () => {
describe('glEmojiTag', () => {
it('bomb emoji', () => {
const emojiKey = 'bomb';
const markup = glEmojiTag(emojiFixtureMap[emojiKey].name);
- const glEmojiElement = markupToDomElement(markup);
- testGlEmojiElement(
- glEmojiElement,
- emojiFixtureMap[emojiKey].name,
- emojiFixtureMap[emojiKey].unicodeVersion,
- emojiFixtureMap[emojiKey].moji,
- );
- });
- it('bomb emoji with image fallback', () => {
- const emojiKey = 'bomb';
- const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, {
- forceFallback: true,
- });
- const glEmojiElement = markupToDomElement(markup);
- testGlEmojiElement(
- glEmojiElement,
- emojiFixtureMap[emojiKey].name,
- emojiFixtureMap[emojiKey].unicodeVersion,
- emojiFixtureMap[emojiKey].moji,
- {
- forceFallback: true,
- },
+ expect(trimText(markup)).toMatchInlineSnapshot(
+ `"<gl-emoji data-name=\\"bomb\\"></gl-emoji>"`,
);
});
@@ -129,65 +83,8 @@ describe('gl_emoji', () => {
const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, {
sprite: true,
});
- const glEmojiElement = markupToDomElement(markup);
- testGlEmojiElement(
- glEmojiElement,
- emojiFixtureMap[emojiKey].name,
- emojiFixtureMap[emojiKey].unicodeVersion,
- emojiFixtureMap[emojiKey].moji,
- {
- sprite: true,
- },
- );
- });
-
- it('bomb emoji with sprite fallback', () => {
- const emojiKey = 'bomb';
- const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, {
- forceFallback: true,
- sprite: true,
- });
- const glEmojiElement = markupToDomElement(markup);
- testGlEmojiElement(
- glEmojiElement,
- emojiFixtureMap[emojiKey].name,
- emojiFixtureMap[emojiKey].unicodeVersion,
- emojiFixtureMap[emojiKey].moji,
- {
- forceFallback: true,
- sprite: true,
- },
- );
- });
-
- it('question mark when invalid emoji name given', () => {
- const name = 'invalid_emoji';
- const emojiKey = 'grey_question';
- const markup = glEmojiTag(name);
- const glEmojiElement = markupToDomElement(markup);
- testGlEmojiElement(
- glEmojiElement,
- emojiFixtureMap[emojiKey].name,
- emojiFixtureMap[emojiKey].unicodeVersion,
- emojiFixtureMap[emojiKey].moji,
- );
- });
-
- it('question mark with image fallback when invalid emoji name given', () => {
- const name = 'invalid_emoji';
- const emojiKey = 'grey_question';
- const markup = glEmojiTag(name, {
- forceFallback: true,
- });
- const glEmojiElement = markupToDomElement(markup);
- testGlEmojiElement(
- glEmojiElement,
- emojiFixtureMap[emojiKey].name,
- emojiFixtureMap[emojiKey].unicodeVersion,
- emojiFixtureMap[emojiKey].moji,
- {
- forceFallback: true,
- },
+ expect(trimText(markup)).toMatchInlineSnapshot(
+ `"<gl-emoji data-fallback-sprite-class=\\"emoji-bomb\\" data-name=\\"bomb\\"></gl-emoji>"`,
);
});
});
diff --git a/spec/frontend/behaviors/gl_emoji/unicode_support_map_spec.js b/spec/frontend/emoji/support/unicode_support_map_spec.js
index aaee9c30cac..aaee9c30cac 100644
--- a/spec/frontend/behaviors/gl_emoji/unicode_support_map_spec.js
+++ b/spec/frontend/emoji/support/unicode_support_map_spec.js
diff --git a/spec/frontend/environment.js b/spec/frontend/environment.js
index 08da34aa27a..c9d77a34595 100644
--- a/spec/frontend/environment.js
+++ b/spec/frontend/environment.js
@@ -3,12 +3,14 @@
const path = require('path');
const { ErrorWithStack } = require('jest-util');
const JSDOMEnvironment = require('jest-environment-jsdom-sixteen');
+const { TEST_HOST } = require('./helpers/test_constants');
const ROOT_PATH = path.resolve(__dirname, '../..');
class CustomEnvironment extends JSDOMEnvironment {
constructor(config, context) {
- super(config, context);
+ // Setup testURL so that window.location is setup properly
+ super({ ...config, testURL: TEST_HOST }, context);
Object.assign(context.console, {
error(...args) {
@@ -57,6 +59,9 @@ class CustomEnvironment extends JSDOMEnvironment {
ownerDocument: this.global.document,
},
});
+
+ // Expose the jsdom (created in super class) to the global so that we can call reconfigure({ url: '' }) to properly set `window.location`
+ this.global.dom = this.dom;
}
async teardown() {
diff --git a/spec/frontend/environments/emtpy_state_spec.js b/spec/frontend/environments/emtpy_state_spec.js
index ed90c13f1e1..862d90e50dc 100644
--- a/spec/frontend/environments/emtpy_state_spec.js
+++ b/spec/frontend/environments/emtpy_state_spec.js
@@ -7,8 +7,6 @@ describe('environments empty state', () => {
beforeEach(() => {
vm = shallowMount(EmptyState, {
propsData: {
- newPath: 'foo',
- canCreateEnvironment: true,
helpPath: 'bar',
},
});
@@ -23,18 +21,4 @@ describe('environments empty state', () => {
"You don't have any environments right now",
);
});
-
- it('renders the new environment button', () => {
- expect(vm.find('.js-new-environment-button').attributes('href')).toEqual('foo');
- });
-
- describe('without permission', () => {
- beforeEach(() => {
- vm.setProps({ canCreateEnvironment: false });
- });
-
- it('does not render the new environment button', () => {
- expect(vm.find('.js-new-environment-button').exists()).toBe(false);
- });
- });
});
diff --git a/spec/frontend/error_tracking/components/error_details_spec.js b/spec/frontend/error_tracking/components/error_details_spec.js
index fd2164d05fc..6124602e038 100644
--- a/spec/frontend/error_tracking/components/error_details_spec.js
+++ b/spec/frontend/error_tracking/components/error_details_spec.js
@@ -3,7 +3,7 @@ import Vuex from 'vuex';
import { __ } from '~/locale';
import createFlash from '~/flash';
import {
- GlDeprecatedButton,
+ GlButton,
GlLoadingIcon,
GlLink,
GlBadge,
@@ -48,10 +48,11 @@ describe('ErrorDetails', () => {
const findUpdateResolveStatusButton = () =>
wrapper.find('[data-testid="update-resolve-status-btn"]');
const findExternalUrl = () => wrapper.find('[data-testid="external-url-link"]');
+ const findAlert = () => wrapper.find(GlAlert);
function mountComponent() {
wrapper = shallowMount(ErrorDetails, {
- stubs: { GlDeprecatedButton, GlSprintf },
+ stubs: { GlButton, GlSprintf },
localVue,
store,
mocks,
@@ -194,7 +195,7 @@ describe('ErrorDetails', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
expect(wrapper.find(Stacktrace).exists()).toBe(false);
expect(wrapper.find(GlBadge).exists()).toBe(false);
- expect(wrapper.findAll(GlDeprecatedButton).length).toBe(3);
+ expect(wrapper.findAll(GlButton)).toHaveLength(3);
});
describe('unsafe chars for culprit field', () => {
@@ -278,15 +279,17 @@ describe('ErrorDetails', () => {
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
expect(wrapper.find(Stacktrace).exists()).toBe(true);
+ expect(findAlert().exists()).toBe(false);
});
});
- it('should NOT show stacktrace if no entries', () => {
+ it('should NOT show stacktrace if no entries and show Alert message', () => {
store.state.details.loadingStacktrace = false;
store.getters = { 'details/sentryUrl': () => 'sentry.io', 'details/stacktrace': () => [] };
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
expect(wrapper.find(Stacktrace).exists()).toBe(false);
+ expect(findAlert().text()).toBe('No stack trace for this error');
});
});
});
@@ -404,7 +407,6 @@ describe('ErrorDetails', () => {
});
it('should show alert with closed issueId', () => {
- const findAlert = () => wrapper.find(GlAlert);
const closedIssueId = 123;
wrapper.setData({
isAlertVisible: true,
diff --git a/spec/frontend/filtered_search/dropdown_user_spec.js b/spec/frontend/filtered_search/dropdown_user_spec.js
index 8eef10290bf..c1c09ea5d3e 100644
--- a/spec/frontend/filtered_search/dropdown_user_spec.js
+++ b/spec/frontend/filtered_search/dropdown_user_spec.js
@@ -48,13 +48,13 @@ describe('Dropdown User', () => {
};
const dropdown = new DropdownUser();
- expect(dropdown.config.AjaxFilter.endpoint).toBe('/autocomplete/users.json');
+ expect(dropdown.config.AjaxFilter.endpoint).toBe('/-/autocomplete/users.json');
});
it('should return endpoint when relative_url_root is undefined', () => {
const dropdown = new DropdownUser();
- expect(dropdown.config.AjaxFilter.endpoint).toBe('/autocomplete/users.json');
+ expect(dropdown.config.AjaxFilter.endpoint).toBe('/-/autocomplete/users.json');
});
it('should return endpoint with relative url when available', () => {
@@ -63,7 +63,9 @@ describe('Dropdown User', () => {
};
const dropdown = new DropdownUser();
- expect(dropdown.config.AjaxFilter.endpoint).toBe('/gitlab_directory/autocomplete/users.json');
+ expect(dropdown.config.AjaxFilter.endpoint).toBe(
+ '/gitlab_directory/-/autocomplete/users.json',
+ );
});
afterEach(() => {
diff --git a/spec/frontend/filtered_search/filtered_search_manager_spec.js b/spec/frontend/filtered_search/filtered_search_manager_spec.js
index ef87662a1ef..70e8b339d4b 100644
--- a/spec/frontend/filtered_search/filtered_search_manager_spec.js
+++ b/spec/frontend/filtered_search/filtered_search_manager_spec.js
@@ -2,7 +2,6 @@ import RecentSearchesService from '~/filtered_search/services/recent_searches_se
import RecentSearchesServiceError from '~/filtered_search/services/recent_searches_service_error';
import RecentSearchesRoot from '~/filtered_search/recent_searches_root';
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
-import '~/lib/utils/common_utils';
import DropdownUtils from '~/filtered_search/dropdown_utils';
import FilteredSearchVisualTokens from '~/filtered_search/filtered_search_visual_tokens';
import FilteredSearchDropdownManager from '~/filtered_search/filtered_search_dropdown_manager';
@@ -10,6 +9,7 @@ import FilteredSearchManager from '~/filtered_search/filtered_search_manager';
import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper';
import { BACKSPACE_KEY_CODE, DELETE_KEY_CODE } from '~/lib/utils/keycodes';
import { visitUrl } from '~/lib/utils/url_utility';
+import * as commonUtils from '~/lib/utils/common_utils';
jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
@@ -83,7 +83,7 @@ describe('Filtered Search Manager', () => {
jest
.spyOn(FilteredSearchDropdownManager.prototype, 'updateDropdownOffset')
.mockImplementation();
- jest.spyOn(gl.utils, 'getParameterByName').mockReturnValue(null);
+ jest.spyOn(commonUtils, 'getParameterByName').mockReturnValue(null);
jest.spyOn(FilteredSearchVisualTokens, 'unselectTokens');
input = document.querySelector('.filtered-search');
diff --git a/spec/frontend/filtered_search/stores/recent_searches_store_spec.js b/spec/frontend/filtered_search/stores/recent_searches_store_spec.js
index 56bb82ae941..320aaa99bcc 100644
--- a/spec/frontend/filtered_search/stores/recent_searches_store_spec.js
+++ b/spec/frontend/filtered_search/stores/recent_searches_store_spec.js
@@ -44,6 +44,15 @@ describe('RecentSearchesStore', () => {
expect(store.state.recentSearches).toEqual(['baz', 'qux']);
});
+ it('handles non-string values', () => {
+ store.setRecentSearches(['foo ', { foo: 'bar' }, { foo: 'bar' }, ['foobar']]);
+
+ // 1. String values will be trimmed of leading/trailing spaces
+ // 2. Comparison will account for objects to remove duplicates
+ // 3. Old behaviour of handling string values stays as it is.
+ expect(store.state.recentSearches).toEqual(['foo', { foo: 'bar' }, ['foobar']]);
+ });
+
it('only keeps track of 5 items', () => {
store.setRecentSearches(['1', '2', '3', '4', '5', '6', '7']);
diff --git a/spec/frontend/filtered_search/visual_token_value_spec.js b/spec/frontend/filtered_search/visual_token_value_spec.js
index ea501423403..3a64b688c7a 100644
--- a/spec/frontend/filtered_search/visual_token_value_spec.js
+++ b/spec/frontend/filtered_search/visual_token_value_spec.js
@@ -4,6 +4,7 @@ import AjaxCache from '~/lib/utils/ajax_cache';
import UsersCache from '~/lib/utils/users_cache';
import DropdownUtils from '~/filtered_search//dropdown_utils';
import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('Filtered Search Visual Tokens', () => {
const findElements = tokenElement => {
@@ -106,7 +107,7 @@ describe('Filtered Search Visual Tokens', () => {
it('escapes user name when creating token', done => {
const dummyUser = {
name: '<script>',
- avatar_url: `${gl.TEST_HOST}/mypics/avatar.png`,
+ avatar_url: `${TEST_HOST}/mypics/avatar.png`,
};
const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
const tokenValue = tokenValueElement.innerText;
diff --git a/spec/frontend/fixtures/branches.rb b/spec/frontend/fixtures/branches.rb
index 4667dfb69f8..df2d1af7ecf 100644
--- a/spec/frontend/fixtures/branches.rb
+++ b/spec/frontend/fixtures/branches.rb
@@ -2,33 +2,51 @@
require 'spec_helper'
-RSpec.describe Projects::BranchesController, '(JavaScript fixtures)', type: :controller do
+RSpec.describe 'Branches (JavaScript fixtures)' do
include JavaScriptFixturesHelpers
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
-
- render_views
+ let_it_be(:admin) { create(:admin) }
+ let_it_be(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let_it_be(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
before(:all) do
clean_frontend_fixtures('branches/')
+ clean_frontend_fixtures('api/branches/')
end
- before do
- sign_in(admin)
+ after(:all) do
+ remove_repository(project)
end
- after do
- remove_repository(project)
+ describe Projects::BranchesController, '(JavaScript fixtures)', type: :controller do
+ render_views
+
+ before do
+ sign_in(admin)
+ end
+
+ it 'branches/new_branch.html' do
+ get :new, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project
+ }
+
+ expect(response).to be_successful
+ end
end
- it 'branches/new_branch.html' do
- get :new, params: {
- namespace_id: project.namespace.to_param,
- project_id: project
- }
+ describe API::Branches, '(JavaScript fixtures)', type: :request do
+ include ApiHelpers
+
+ it 'api/branches/branches.json' do
+ # The search query "ma" matches a few branch names in the test
+ # repository with a variety of different properties, including:
+ # - "master": default, protected
+ # - "markdown": non-default, protected
+ # - "many_files": non-default, not protected
+ get api("/projects/#{project.id}/repository/branches?search=ma", admin)
- expect(response).to be_successful
+ expect(response).to be_successful
+ end
end
end
diff --git a/spec/frontend/fixtures/commit.rb b/spec/frontend/fixtures/commit.rb
index c5c00afd4ca..9175a757b73 100644
--- a/spec/frontend/fixtures/commit.rb
+++ b/spec/frontend/fixtures/commit.rb
@@ -2,34 +2,55 @@
require 'spec_helper'
-RSpec.describe Projects::CommitController, '(JavaScript fixtures)', type: :controller do
+RSpec.describe 'Commit (JavaScript fixtures)' do
include JavaScriptFixturesHelpers
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
- let(:commit) { project.commit("master") }
-
- render_views
+ let_it_be(:commit) { project.commit("master") }
before(:all) do
clean_frontend_fixtures('commit/')
+ clean_frontend_fixtures('api/commits/')
+
+ project.add_maintainer(user)
end
before do
- project.add_maintainer(user)
- sign_in(user)
allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon')
end
- it 'commit/show.html' do
- params = {
- namespace_id: project.namespace,
- project_id: project,
- id: commit.id
- }
+ after(:all) do
+ remove_repository(project)
+ end
+
+ describe Projects::CommitController, '(JavaScript fixtures)', type: :controller do
+ render_views
+
+ before do
+ sign_in(user)
+ end
+
+ it 'commit/show.html' do
+ params = {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: commit.id
+ }
+
+ get :show, params: params
+
+ expect(response).to be_successful
+ end
+ end
+
+ describe API::Commits, '(JavaScript fixtures)', type: :request do
+ include ApiHelpers
- get :show, params: params
+ it 'api/commits/commit.json' do
+ get api("/projects/#{project.id}/repository/commits/#{commit.id}", user)
- expect(response).to be_successful
+ expect(response).to be_successful
+ end
end
end
diff --git a/spec/frontend/fixtures/emojis.rb b/spec/frontend/fixtures/emojis.rb
new file mode 100644
index 00000000000..b95c7632917
--- /dev/null
+++ b/spec/frontend/fixtures/emojis.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Emojis (JavaScript fixtures)', type: :request do
+ include JavaScriptFixturesHelpers
+
+ before(:all) do
+ clean_frontend_fixtures('emojis/')
+ end
+
+ it 'emojis/emojis.json' do |example|
+ get '/-/emojis/1/emojis.json'
+
+ expect(response).to be_successful
+ end
+end
diff --git a/spec/frontend/fixtures/metrics_dashboard.rb b/spec/frontend/fixtures/metrics_dashboard.rb
index b5dee7525f6..6ee730f5c3d 100644
--- a/spec/frontend/fixtures/metrics_dashboard.rb
+++ b/spec/frontend/fixtures/metrics_dashboard.rb
@@ -6,10 +6,11 @@ RSpec.describe MetricsDashboard, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
include MetricsDashboardHelpers
- let(:user) { create(:user) }
- let(:project) { project_with_dashboard('.gitlab/dashboards/test.yml') }
- let(:environment) { create(:environment, project: project) }
- let(:params) { { environment: environment } }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:namespace) { create(:namespace, name: 'monitoring' )}
+ let_it_be(:project) { project_with_dashboard_namespace('.gitlab/dashboards/test.yml', namespace: namespace) }
+ let_it_be(:environment) { create(:environment, id: 1, project: project) }
+ let_it_be(:params) { { environment: environment } }
before(:all) do
clean_frontend_fixtures('metrics_dashboard/')
@@ -24,6 +25,7 @@ RSpec.describe MetricsDashboard, '(JavaScript fixtures)', type: :controller do
project.add_maintainer(user)
allow(controller).to receive(:project).and_return(project)
+ allow(controller).to receive(:environment).and_return(environment)
allow(controller)
.to receive(:metrics_dashboard_params)
.and_return(params)
@@ -35,7 +37,9 @@ RSpec.describe MetricsDashboard, '(JavaScript fixtures)', type: :controller do
it 'metrics_dashboard/environment_metrics_dashboard.json' do
routes.draw { get "metrics_dashboard" => "anonymous#metrics_dashboard" }
+
response = get :metrics_dashboard, format: :json
+
expect(response).to be_successful
end
end
diff --git a/spec/frontend/fixtures/services.rb b/spec/frontend/fixtures/services.rb
index 0877998cc9d..43230301296 100644
--- a/spec/frontend/fixtures/services.rb
+++ b/spec/frontend/fixtures/services.rb
@@ -8,7 +8,7 @@ RSpec.describe Projects::ServicesController, '(JavaScript fixtures)', type: :con
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'services-project') }
- let!(:service) { create(:custom_issue_tracker_service, project: project, title: 'Custom Issue Tracker') }
+ let!(:service) { create(:custom_issue_tracker_service, project: project) }
render_views
diff --git a/spec/frontend/fixtures/static/mini_dropdown_graph.html b/spec/frontend/fixtures/static/mini_dropdown_graph.html
index cd0b8dec3fc..cb55698b709 100644
--- a/spec/frontend/fixtures/static/mini_dropdown_graph.html
+++ b/spec/frontend/fixtures/static/mini_dropdown_graph.html
@@ -1,13 +1,13 @@
-<div class="js-builds-dropdown-tests dropdown dropdown js-mini-pipeline-graph">
-<button class="js-builds-dropdown-button" data-toggle="dropdown" data-stage-endpoint="foobar">
-Dropdown
-</button>
-<ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
-<li class="js-builds-dropdown-list scrollable-menu">
-<ul></ul>
-</li>
-<li class="js-builds-dropdown-loading hidden">
-<span class="fa fa-spinner"></span>
-</li>
-</ul>
+<div class="js-builds-dropdown-tests dropdown dropdown" data-testid="widget-mini-pipeline-graph">
+ <button class="js-builds-dropdown-button" data-toggle="dropdown" data-stage-endpoint="foobar">
+ Dropdown
+ </button>
+ <ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
+ <li class="js-builds-dropdown-list scrollable-menu">
+ <ul></ul>
+ </li>
+ <li class="js-builds-dropdown-loading hidden">
+ <span class="fa fa-spinner"></span>
+ </li>
+ </ul>
</div>
diff --git a/spec/frontend/fixtures/static/global_search_input.html b/spec/frontend/fixtures/static/search_autocomplete.html
index 29db9020424..29db9020424 100644
--- a/spec/frontend/fixtures/static/global_search_input.html
+++ b/spec/frontend/fixtures/static/search_autocomplete.html
diff --git a/spec/frontend/fixtures/tags.rb b/spec/frontend/fixtures/tags.rb
new file mode 100644
index 00000000000..b2a5429fac8
--- /dev/null
+++ b/spec/frontend/fixtures/tags.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Tags (JavaScript fixtures)' do
+ include JavaScriptFixturesHelpers
+
+ let_it_be(:admin) { create(:admin) }
+ let_it_be(:project) { create(:project, :repository, path: 'tags-project') }
+
+ before(:all) do
+ clean_frontend_fixtures('api/tags/')
+ end
+
+ after(:all) do
+ remove_repository(project)
+ end
+
+ describe API::Tags, '(JavaScript fixtures)', type: :request do
+ include ApiHelpers
+
+ it 'api/tags/tags.json' do
+ get api("/projects/#{project.id}/repository/tags", admin)
+
+ expect(response).to be_successful
+ end
+ end
+end
diff --git a/spec/frontend/gfm_auto_complete_spec.js b/spec/frontend/gfm_auto_complete_spec.js
index f58615000ee..869347128e5 100644
--- a/spec/frontend/gfm_auto_complete_spec.js
+++ b/spec/frontend/gfm_auto_complete_spec.js
@@ -312,7 +312,7 @@ describe('GfmAutoComplete', () => {
title: 'My Group',
search: 'my-group My Group',
icon:
- '<svg class="s16 vertical-align-middle prepend-left-5"><use xlink:href="undefined#notifications-off" /></svg>',
+ '<svg class="s16 vertical-align-middle gl-ml-2"><use xlink:href="undefined#notifications-off" /></svg>',
},
]);
});
diff --git a/spec/frontend/gl_form_spec.js b/spec/frontend/gl_form_spec.js
index 150d8a053d5..52e1693f8a6 100644
--- a/spec/frontend/gl_form_spec.js
+++ b/spec/frontend/gl_form_spec.js
@@ -9,6 +9,8 @@ describe('GLForm', () => {
describe('when instantiated', () => {
beforeEach(done => {
+ window.gl = window.gl || {};
+
testContext.form = $('<form class="gfm-form"><textarea class="js-gfm-input"></form>');
testContext.textarea = testContext.form.find('textarea');
jest.spyOn($.prototype, 'off').mockReturnValue(testContext.textarea);
@@ -111,5 +113,21 @@ describe('GLForm', () => {
expect(autosize.destroy).not.toHaveBeenCalled();
});
});
+
+ describe('supportsQuickActions', () => {
+ it('should return false if textarea does not support quick actions', () => {
+ const glForm = new GLForm(testContext.form, false);
+
+ expect(glForm.supportsQuickActions).toEqual(false);
+ });
+
+ it('should return true if textarea supports quick actions', () => {
+ testContext.textarea.attr('data-supports-quick-actions', true);
+
+ const glForm = new GLForm(testContext.form, false);
+
+ expect(glForm.supportsQuickActions).toEqual(true);
+ });
+ });
});
});
diff --git a/spec/frontend/helpers/event_hub_factory_spec.js b/spec/frontend/helpers/event_hub_factory_spec.js
index dcfec6b836a..c4f63ff6049 100644
--- a/spec/frontend/helpers/event_hub_factory_spec.js
+++ b/spec/frontend/helpers/event_hub_factory_spec.js
@@ -1,77 +1,72 @@
import createEventHub from '~/helpers/event_hub_factory';
+const TEST_EVENT = 'foobar';
+const TEST_EVENT_2 = 'testevent';
+
describe('event bus factory', () => {
let eventBus;
+ let handler;
+ let otherHandlers;
beforeEach(() => {
eventBus = createEventHub();
+ handler = jest.fn();
+ otherHandlers = [jest.fn(), jest.fn()];
});
afterEach(() => {
+ eventBus.dispose();
eventBus = null;
});
- describe('underlying module', () => {
- let mitt;
+ describe('instance', () => {
+ it.each`
+ method
+ ${'$on'}
+ ${'$once'}
+ ${'$off'}
+ ${'$emit'}
+ `('has $method method', ({ method }) => {
+ expect(eventBus[method]).toEqual(expect.any(Function));
+ });
+ });
+ describe('$on', () => {
beforeEach(() => {
- jest.resetModules();
- jest.mock('mitt');
-
- // eslint-disable-next-line global-require
- mitt = require('mitt');
- mitt.mockReturnValue(() => ({}));
-
- const createEventHubActual = jest.requireActual('~/helpers/event_hub_factory').default;
- eventBus = createEventHubActual();
+ eventBus.$on(TEST_EVENT, handler);
});
- it('creates an emitter', () => {
- expect(mitt).toHaveBeenCalled();
+ it('calls handler when event is emitted', () => {
+ eventBus.$emit(TEST_EVENT);
+ expect(handler).toHaveBeenCalledWith();
});
- });
- describe('instance', () => {
- it.each`
- method
- ${'on'}
- ${'once'}
- ${'off'}
- ${'emit'}
- `('binds $$method to $method ', ({ method }) => {
- expect(typeof eventBus[method]).toBe('function');
- expect(eventBus[method]).toBe(eventBus[`$${method}`]);
+ it('calls handler with multiple args', () => {
+ eventBus.$emit(TEST_EVENT, 'arg1', 'arg2', 'arg3');
+ expect(handler).toHaveBeenCalledWith('arg1', 'arg2', 'arg3');
});
- });
- describe('once', () => {
- const event = 'foobar';
- let handler;
+ it('calls handler multiple times', () => {
+ eventBus.$emit(TEST_EVENT, 'arg1', 'arg2', 'arg3');
+ eventBus.$emit(TEST_EVENT, 'arg1', 'arg2', 'arg3');
- beforeEach(() => {
- jest.spyOn(eventBus, 'on');
- jest.spyOn(eventBus, 'off');
- handler = jest.fn();
- eventBus.once(event, handler);
+ expect(handler).toHaveBeenCalledTimes(2);
});
+ });
- it('calls on internally', () => {
- expect(eventBus.on).toHaveBeenCalled();
+ describe('$once', () => {
+ beforeEach(() => {
+ eventBus.$once(TEST_EVENT, handler);
});
it('calls handler when event is emitted', () => {
- eventBus.emit(event);
+ eventBus.$emit(TEST_EVENT);
expect(handler).toHaveBeenCalled();
});
- it('calls off when event is emitted', () => {
- eventBus.emit(event);
- expect(eventBus.off).toHaveBeenCalled();
- });
-
it('calls the handler only once when event is emitted multiple times', () => {
- eventBus.emit(event);
- eventBus.emit(event);
+ eventBus.$emit(TEST_EVENT);
+ eventBus.$emit(TEST_EVENT);
expect(handler).toHaveBeenCalledTimes(1);
});
@@ -80,15 +75,70 @@ describe('event bus factory', () => {
handler = jest.fn().mockImplementation(() => {
throw new Error();
});
- eventBus.once(event, handler);
+ eventBus.$once(TEST_EVENT, handler);
});
it('calls off when event is emitted', () => {
expect(() => {
- eventBus.emit(event);
+ eventBus.$emit(TEST_EVENT);
}).toThrow();
- expect(eventBus.off).toHaveBeenCalled();
+ expect(() => {
+ eventBus.$emit(TEST_EVENT);
+ }).not.toThrow();
+
+ expect(handler).toHaveBeenCalledTimes(1);
});
});
});
+
+ describe('$off', () => {
+ beforeEach(() => {
+ otherHandlers.forEach(x => eventBus.$on(TEST_EVENT, x));
+ eventBus.$on(TEST_EVENT, handler);
+ });
+
+ it('can be called on event with no handlers', () => {
+ expect(() => {
+ eventBus.$off(TEST_EVENT_2);
+ }).not.toThrow();
+ });
+
+ it('can be called on event with no handlers, with a handler', () => {
+ expect(() => {
+ eventBus.$off(TEST_EVENT_2, handler);
+ }).not.toThrow();
+ });
+
+ it('with a handler, will no longer call that handler', () => {
+ eventBus.$off(TEST_EVENT, handler);
+
+ eventBus.$emit(TEST_EVENT);
+
+ expect(handler).not.toHaveBeenCalled();
+ expect(otherHandlers.map(x => x.mock.calls.length)).toEqual(otherHandlers.map(() => 1));
+ });
+
+ it('without a handler, will no longer call any handlers', () => {
+ eventBus.$off(TEST_EVENT);
+
+ eventBus.$emit(TEST_EVENT);
+
+ expect(handler).not.toHaveBeenCalled();
+ expect(otherHandlers.map(x => x.mock.calls.length)).toEqual(otherHandlers.map(() => 0));
+ });
+ });
+
+ describe('$emit', () => {
+ beforeEach(() => {
+ otherHandlers.forEach(x => eventBus.$on(TEST_EVENT_2, x));
+ eventBus.$on(TEST_EVENT, handler);
+ });
+
+ it('only calls handlers for given type', () => {
+ eventBus.$emit(TEST_EVENT, 'arg1');
+
+ expect(handler).toHaveBeenCalledWith('arg1');
+ expect(otherHandlers.map(x => x.mock.calls.length)).toEqual(otherHandlers.map(() => 0));
+ });
+ });
});
diff --git a/spec/frontend/helpers/fake_request_animation_frame.js b/spec/frontend/helpers/fake_request_animation_frame.js
new file mode 100644
index 00000000000..b01ae5b7c5f
--- /dev/null
+++ b/spec/frontend/helpers/fake_request_animation_frame.js
@@ -0,0 +1,13 @@
+// eslint-disable-next-line import/prefer-default-export
+export const useFakeRequestAnimationFrame = () => {
+ let orig;
+
+ beforeEach(() => {
+ orig = global.requestAnimationFrame;
+ global.requestAnimationFrame = cb => cb();
+ });
+
+ afterEach(() => {
+ global.requestAnimationFrame = orig;
+ });
+};
diff --git a/spec/javascripts/helpers/init_vue_mr_page_helper.js b/spec/frontend/helpers/init_vue_mr_page_helper.js
index 1ba08199764..c1d608cc5a0 100644
--- a/spec/javascripts/helpers/init_vue_mr_page_helper.js
+++ b/spec/frontend/helpers/init_vue_mr_page_helper.js
@@ -1,8 +1,8 @@
import MockAdapter from 'axios-mock-adapter';
-import initMRPage from '~/mr_notes/index';
+import initMRPage from '~/mr_notes';
import axios from '~/lib/utils/axios_utils';
-import { userDataMock, notesDataMock, noteableDataMock } from '../../frontend/notes/mock_data';
-import diffFileMockData from '../../frontend/diffs/mock_data/diff_file';
+import { userDataMock, notesDataMock, noteableDataMock } from '../notes/mock_data';
+import diffFileMockData from '../diffs/mock_data/diff_file';
export default function initVueMRPage() {
const mrTestEl = document.createElement('div');
diff --git a/spec/frontend/helpers/monitor_helper_spec.js b/spec/frontend/helpers/monitor_helper_spec.js
index f7163d496d2..083b6404125 100644
--- a/spec/frontend/helpers/monitor_helper_spec.js
+++ b/spec/frontend/helpers/monitor_helper_spec.js
@@ -33,15 +33,6 @@ describe('monitor helper', () => {
]);
});
- it('excludes NaN values', () => {
- expect(
- monitorHelper.makeDataSeries(
- data({ metric: {}, values: [[1, 1], [2, NaN]] }),
- defaultConfig,
- ),
- ).toEqual([{ ...expectedDataSeries[0], data: [[1, 1]] }]);
- });
-
it('updates series name from templates', () => {
const config = {
...defaultConfig,
diff --git a/spec/frontend/helpers/test_constants.js b/spec/frontend/helpers/test_constants.js
index c97d47a6406..69b78f556aa 100644
--- a/spec/frontend/helpers/test_constants.js
+++ b/spec/frontend/helpers/test_constants.js
@@ -1,7 +1,19 @@
-export const FIXTURES_PATH = `/fixtures`;
-export const TEST_HOST = 'http://test.host';
+const FIXTURES_PATH = `/fixtures`;
+const TEST_HOST = 'http://test.host';
-export const DUMMY_IMAGE_URL = `${FIXTURES_PATH}/static/images/one_white_pixel.png`;
+const DUMMY_IMAGE_URL = `${FIXTURES_PATH}/static/images/one_white_pixel.png`;
-export const GREEN_BOX_IMAGE_URL = `${FIXTURES_PATH}/static/images/green_box.png`;
-export const RED_BOX_IMAGE_URL = `${FIXTURES_PATH}/static/images/red_box.png`;
+const GREEN_BOX_IMAGE_URL = `${FIXTURES_PATH}/static/images/green_box.png`;
+const RED_BOX_IMAGE_URL = `${FIXTURES_PATH}/static/images/red_box.png`;
+
+// NOTE: module.exports is needed so that this file can be used
+// by environment.js
+//
+// eslint-disable-next-line import/no-commonjs
+module.exports = {
+ FIXTURES_PATH,
+ TEST_HOST,
+ DUMMY_IMAGE_URL,
+ GREEN_BOX_IMAGE_URL,
+ RED_BOX_IMAGE_URL,
+};
diff --git a/spec/frontend/helpers/vue_mock_directive.js b/spec/frontend/helpers/vue_mock_directive.js
index 699fe3eab26..28d4708835d 100644
--- a/spec/frontend/helpers/vue_mock_directive.js
+++ b/spec/frontend/helpers/vue_mock_directive.js
@@ -2,13 +2,21 @@ export const getKey = name => `$_gl_jest_${name}`;
export const getBinding = (el, name) => el[getKey(name)];
+const writeBindingToElement = (el, { name, value, arg, modifiers }) => {
+ el[getKey(name)] = {
+ value,
+ arg,
+ modifiers,
+ };
+};
+
export const createMockDirective = () => ({
- bind(el, { name, value, arg, modifiers }) {
- el[getKey(name)] = {
- value,
- arg,
- modifiers,
- };
+ bind(el, binding) {
+ writeBindingToElement(el, binding);
+ },
+
+ update(el, binding) {
+ writeBindingToElement(el, binding);
},
unbind(el, { name }) {
diff --git a/spec/frontend/helpers/wait_using_real_timer.js b/spec/frontend/helpers/wait_using_real_timer.js
new file mode 100644
index 00000000000..ddf23cd97b4
--- /dev/null
+++ b/spec/frontend/helpers/wait_using_real_timer.js
@@ -0,0 +1,7 @@
+/* useful for timing promises when jest fakeTimers are not reliable enough */
+export default timeout =>
+ new Promise(resolve => {
+ jest.useRealTimers();
+ setTimeout(resolve, timeout);
+ jest.useFakeTimers();
+ });
diff --git a/spec/frontend/ide/commit_icon_spec.js b/spec/frontend/ide/commit_icon_spec.js
index 90b8e34497c..e4a7394b089 100644
--- a/spec/frontend/ide/commit_icon_spec.js
+++ b/spec/frontend/ide/commit_icon_spec.js
@@ -11,7 +11,6 @@ const createFile = (name = 'name', id = name, type = '', parent = null) =>
name,
path: parent ? `${parent.path}/${name}` : name,
parentPath: parent ? parent.path : '',
- lastCommit: {},
});
describe('getCommitIconMap', () => {
diff --git a/spec/frontend/ide/components/ide_status_list_spec.js b/spec/frontend/ide/components/ide_status_list_spec.js
index 847464ed806..fed61233e55 100644
--- a/spec/frontend/ide/components/ide_status_list_spec.js
+++ b/spec/frontend/ide/components/ide_status_list_spec.js
@@ -1,5 +1,6 @@
import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { GlLink } from '@gitlab/ui';
import IdeStatusList from '~/ide/components/ide_status_list.vue';
import TerminalSyncStatusSafe from '~/ide/components/terminal_sync/terminal_sync_status_safe.vue';
@@ -9,6 +10,7 @@ const TEST_FILE = {
editorColumn: 23,
fileLanguage: 'markdown',
content: 'abc\nndef',
+ permalink: '/lorem.md',
};
const localVue = createLocalVue();
@@ -19,6 +21,7 @@ describe('ide/components/ide_status_list', () => {
let store;
let wrapper;
+ const findLink = () => wrapper.find(GlLink);
const createComponent = (options = {}) => {
store = new Vuex.Store({
getters: {
@@ -51,8 +54,9 @@ describe('ide/components/ide_status_list', () => {
createComponent();
});
- it('shows file name', () => {
- expect(wrapper.text()).toContain(TEST_FILE.name);
+ it('shows a link to the file that contains the file name', () => {
+ expect(findLink().attributes('href')).toBe(TEST_FILE.permalink);
+ expect(findLink().text()).toBe(TEST_FILE.name);
});
it('shows file eol', () => {
diff --git a/spec/frontend/ide/components/jobs/__snapshots__/stage_spec.js.snap b/spec/frontend/ide/components/jobs/__snapshots__/stage_spec.js.snap
index bdd3d439fd4..dbfacb98813 100644
--- a/spec/frontend/ide/components/jobs/__snapshots__/stage_spec.js.snap
+++ b/spec/frontend/ide/components/jobs/__snapshots__/stage_spec.js.snap
@@ -2,7 +2,7 @@
exports[`IDE pipeline stage renders stage details & icon 1`] = `
<div
- class="ide-stage card prepend-top-default"
+ class="ide-stage card gl-mt-3"
>
<div
class="card-header"
diff --git a/spec/frontend/ide/components/repo_editor_spec.js b/spec/frontend/ide/components/repo_editor_spec.js
index 4967434dfd7..a4336b8f2eb 100644
--- a/spec/frontend/ide/components/repo_editor_spec.js
+++ b/spec/frontend/ide/components/repo_editor_spec.js
@@ -4,19 +4,25 @@ import MockAdapter from 'axios-mock-adapter';
import '~/behaviors/markdown/render_gfm';
import { Range } from 'monaco-editor';
import axios from '~/lib/utils/axios_utils';
+import service from '~/ide/services';
import { createStoreOptions } from '~/ide/stores';
import RepoEditor from '~/ide/components/repo_editor.vue';
import Editor from '~/ide/lib/editor';
-import { leftSidebarViews, FILE_VIEW_MODE_EDITOR, FILE_VIEW_MODE_PREVIEW } from '~/ide/constants';
+import {
+ leftSidebarViews,
+ FILE_VIEW_MODE_EDITOR,
+ FILE_VIEW_MODE_PREVIEW,
+ viewerTypes,
+} from '~/ide/constants';
import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { file } from '../helpers';
import { exampleConfigs, exampleFiles } from '../lib/editorconfig/mock_data';
+import waitUsingRealTimer from 'helpers/wait_using_real_timer';
describe('RepoEditor', () => {
let vm;
let store;
- let mockActions;
const waitForEditorSetup = () =>
new Promise(resolve => {
@@ -30,6 +36,10 @@ describe('RepoEditor', () => {
vm = createComponentWithStore(Vue.extend(RepoEditor), store, {
file: store.state.openFiles[0],
});
+
+ jest.spyOn(vm, 'getFileData').mockResolvedValue();
+ jest.spyOn(vm, 'getRawFileData').mockResolvedValue();
+
vm.$mount();
};
@@ -43,21 +53,12 @@ describe('RepoEditor', () => {
};
beforeEach(() => {
- mockActions = {
- getFileData: jest.fn().mockResolvedValue(),
- getRawFileData: jest.fn().mockResolvedValue(),
- };
-
const f = {
...file(),
viewMode: FILE_VIEW_MODE_EDITOR,
};
const storeOptions = createStoreOptions();
- storeOptions.actions = {
- ...storeOptions.actions,
- ...mockActions,
- };
store = new Vuex.Store(storeOptions);
f.active = true;
@@ -438,7 +439,7 @@ describe('RepoEditor', () => {
vm.initEditor();
vm.$nextTick()
.then(() => {
- expect(mockActions.getFileData).not.toHaveBeenCalled();
+ expect(vm.getFileData).not.toHaveBeenCalled();
})
.then(done)
.catch(done.fail);
@@ -449,10 +450,11 @@ describe('RepoEditor', () => {
vm.file.raw = '';
vm.initEditor();
+
vm.$nextTick()
.then(() => {
- expect(mockActions.getFileData).toHaveBeenCalled();
- expect(mockActions.getRawFileData).toHaveBeenCalled();
+ expect(vm.getFileData).toHaveBeenCalled();
+ expect(vm.getRawFileData).toHaveBeenCalled();
})
.then(done)
.catch(done.fail);
@@ -464,8 +466,8 @@ describe('RepoEditor', () => {
vm.initEditor();
vm.$nextTick()
.then(() => {
- expect(mockActions.getFileData).not.toHaveBeenCalled();
- expect(mockActions.getRawFileData).not.toHaveBeenCalled();
+ expect(vm.getFileData).not.toHaveBeenCalled();
+ expect(vm.getRawFileData).not.toHaveBeenCalled();
expect(vm.editor.createInstance).not.toHaveBeenCalled();
})
.then(done)
@@ -526,6 +528,63 @@ describe('RepoEditor', () => {
});
});
+ describe('populates editor with the fetched content', () => {
+ beforeEach(() => {
+ vm.getRawFileData.mockRestore();
+ });
+
+ const createRemoteFile = name => ({
+ ...file(name),
+ tmpFile: false,
+ });
+
+ it('after switching viewer from edit to diff', async () => {
+ jest.spyOn(service, 'getRawFileData').mockImplementation(async () => {
+ expect(vm.file.loading).toBe(true);
+
+ // switching from edit to diff mode usually triggers editor initialization
+ store.state.viewer = viewerTypes.diff;
+
+ // we delay returning the file to make sure editor doesn't initialize before we fetch file content
+ await waitUsingRealTimer(30);
+ return 'rawFileData123\n';
+ });
+
+ const f = createRemoteFile('newFile');
+ Vue.set(store.state.entries, f.path, f);
+
+ vm.file = f;
+
+ await waitForEditorSetup();
+ expect(vm.model.getModel().getValue()).toBe('rawFileData123\n');
+ });
+
+ it('after opening multiple files at the same time', async () => {
+ const fileA = createRemoteFile('fileA');
+ const fileB = createRemoteFile('fileB');
+ Vue.set(store.state.entries, fileA.path, fileA);
+ Vue.set(store.state.entries, fileB.path, fileB);
+
+ jest
+ .spyOn(service, 'getRawFileData')
+ .mockImplementationOnce(async () => {
+ // opening fileB while the content of fileA is still being fetched
+ vm.file = fileB;
+ return 'fileA-rawContent\n';
+ })
+ .mockImplementationOnce(async () => {
+ // we delay returning fileB content to make sure the editor doesn't initialize prematurely
+ await waitUsingRealTimer(30);
+ return 'fileB-rawContent\n';
+ });
+
+ vm.file = fileA;
+
+ await waitForEditorSetup();
+ expect(vm.model.getModel().getValue()).toBe('fileB-rawContent\n');
+ });
+ });
+
describe('onPaste', () => {
const setFileName = name => {
Vue.set(vm, 'file', {
@@ -557,6 +616,11 @@ describe('RepoEditor', () => {
});
});
+ // Pasting an image does a lot of things like using the FileReader API,
+ // so, waitForPromises isn't very reliable (and causes a flaky spec)
+ // Read more about state.watch: https://vuex.vuejs.org/api/#watch
+ const waitForFileContentChange = () => watchState(s => s.entries['foo/bar.md'].content);
+
beforeEach(() => {
setFileName('bar.md');
@@ -573,13 +637,15 @@ describe('RepoEditor', () => {
// set cursor to line 2, column 1
vm.editor.instance.setSelection(new Range(2, 1, 2, 1));
vm.editor.instance.focus();
+
+ jest.spyOn(vm.editor.instance, 'hasTextFocus').mockReturnValue(true);
});
});
it('adds an image entry to the same folder for a pasted image in a markdown file', () => {
pasteImage();
- return waitForPromises().then(() => {
+ return waitForFileContentChange().then(() => {
expect(vm.$store.state.entries['foo/foo.png']).toMatchObject({
path: 'foo/foo.png',
type: 'blob',
@@ -593,10 +659,7 @@ describe('RepoEditor', () => {
it("adds a markdown image tag to the file's contents", () => {
pasteImage();
- // Pasting an image does a lot of things like using the FileReader API,
- // so, waitForPromises isn't very reliable (and causes a flaky spec)
- // Read more about state.watch: https://vuex.vuejs.org/api/#watch
- return watchState(s => s.entries['foo/bar.md'].content).then(() => {
+ return waitForFileContentChange().then(() => {
expect(vm.file.content).toBe('hello world\n![foo.png](./foo.png)');
});
});
@@ -629,8 +692,8 @@ describe('RepoEditor', () => {
return waitForEditorSetup().then(() => {
expect(vm.rules).toEqual(monacoRules);
expect(vm.model.options).toMatchObject(monacoRules);
- expect(mockActions.getFileData).not.toHaveBeenCalled();
- expect(mockActions.getRawFileData).not.toHaveBeenCalled();
+ expect(vm.getFileData).not.toHaveBeenCalled();
+ expect(vm.getRawFileData).not.toHaveBeenCalled();
});
},
);
@@ -646,13 +709,13 @@ describe('RepoEditor', () => {
createComponent();
return waitForEditorSetup().then(() => {
- expect(mockActions.getFileData.mock.calls.map(([, args]) => args)).toEqual([
+ expect(vm.getFileData.mock.calls.map(([args]) => args)).toEqual([
{ makeFileActive: false, path: 'foo/bar/baz/.editorconfig' },
{ makeFileActive: false, path: 'foo/bar/.editorconfig' },
{ makeFileActive: false, path: 'foo/.editorconfig' },
{ makeFileActive: false, path: '.editorconfig' },
]);
- expect(mockActions.getRawFileData.mock.calls.map(([, args]) => args)).toEqual([
+ expect(vm.getRawFileData.mock.calls.map(([args]) => args)).toEqual([
{ path: 'foo/bar/baz/.editorconfig' },
{ path: 'foo/bar/.editorconfig' },
{ path: 'foo/.editorconfig' },
diff --git a/spec/frontend/ide/helpers.js b/spec/frontend/ide/helpers.js
index de839fa99ca..a9620d26313 100644
--- a/spec/frontend/ide/helpers.js
+++ b/spec/frontend/ide/helpers.js
@@ -30,7 +30,6 @@ export const file = (name = 'name', id = name, type = '', parent = null) =>
name,
path: parent ? `${parent.path}/${name}` : name,
parentPath: parent ? parent.path : '',
- lastCommit: {},
});
export const createEntriesFromPaths = paths =>
diff --git a/spec/frontend/ide/lib/editor_spec.js b/spec/frontend/ide/lib/editor_spec.js
index f5815771cdf..5f28309422d 100644
--- a/spec/frontend/ide/lib/editor_spec.js
+++ b/spec/frontend/ide/lib/editor_spec.js
@@ -199,6 +199,28 @@ describe('Multi-file editor library', () => {
});
});
+ describe('schemas', () => {
+ let originalGon;
+
+ beforeEach(() => {
+ originalGon = window.gon;
+ window.gon = { features: { schemaLinting: true } };
+
+ delete Editor.editorInstance;
+ instance = Editor.create();
+ });
+
+ afterEach(() => {
+ window.gon = originalGon;
+ });
+
+ it('registers custom schemas defined with Monaco', () => {
+ expect(monacoLanguages.yaml.yamlDefaults.diagnosticsOptions).toMatchObject({
+ schemas: [{ fileMatch: ['*.gitlab-ci.yml'] }],
+ });
+ });
+ });
+
describe('replaceSelectedText', () => {
let model;
let editor;
diff --git a/spec/frontend/ide/services/index_spec.js b/spec/frontend/ide/services/index_spec.js
index 3cb6e064aa2..bc3f86702cf 100644
--- a/spec/frontend/ide/services/index_spec.js
+++ b/spec/frontend/ide/services/index_spec.js
@@ -2,7 +2,7 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import services from '~/ide/services';
import Api from '~/api';
-import gqClient from '~/ide/services/gql';
+import { query } from '~/ide/services/gql';
import { escapeFileUrl } from '~/lib/utils/url_utility';
import getUserPermissions from '~/ide/queries/getUserPermissions.query.graphql';
import { projectData } from '../mock_data';
@@ -207,12 +207,12 @@ describe('IDE services', () => {
},
};
Api.project.mockReturnValue(Promise.resolve({ data: { ...projectData } }));
- gqClient.query.mockReturnValue(Promise.resolve({ data: { project: gqlProjectData } }));
+ query.mockReturnValue(Promise.resolve({ data: { project: gqlProjectData } }));
return services.getProjectData(TEST_NAMESPACE, TEST_PROJECT).then(response => {
expect(response).toEqual({ data: { ...projectData, ...gqlProjectData } });
expect(Api.project).toHaveBeenCalledWith(TEST_PROJECT_ID);
- expect(gqClient.query).toHaveBeenCalledWith({
+ expect(query).toHaveBeenCalledWith({
query: getUserPermissions,
variables: {
projectPath: TEST_PROJECT_ID,
diff --git a/spec/frontend/ide/stores/actions/file_spec.js b/spec/frontend/ide/stores/actions/file_spec.js
index e2dc7626c67..88e7a9fff36 100644
--- a/spec/frontend/ide/stores/actions/file_spec.js
+++ b/spec/frontend/ide/stores/actions/file_spec.js
@@ -51,35 +51,27 @@ describe('IDE store file actions', () => {
store.state.entries[localFile.path] = localFile;
});
- it('closes open files', done => {
- store
- .dispatch('closeFile', localFile)
- .then(() => {
- expect(localFile.opened).toBeFalsy();
- expect(localFile.active).toBeFalsy();
- expect(store.state.openFiles.length).toBe(0);
-
- done();
- })
- .catch(done.fail);
+ it('closes open files', () => {
+ return store.dispatch('closeFile', localFile).then(() => {
+ expect(localFile.opened).toBeFalsy();
+ expect(localFile.active).toBeFalsy();
+ expect(store.state.openFiles.length).toBe(0);
+ });
});
- it('closes file even if file has changes', done => {
+ it('closes file even if file has changes', () => {
store.state.changedFiles.push(localFile);
- store
+ return store
.dispatch('closeFile', localFile)
.then(Vue.nextTick)
.then(() => {
expect(store.state.openFiles.length).toBe(0);
expect(store.state.changedFiles.length).toBe(1);
-
- done();
- })
- .catch(done.fail);
+ });
});
- it('closes file & opens next available file', done => {
+ it('closes file & opens next available file', () => {
const f = {
...file('newOpenFile'),
url: '/newOpenFile',
@@ -88,31 +80,23 @@ describe('IDE store file actions', () => {
store.state.openFiles.push(f);
store.state.entries[f.path] = f;
- store
+ return store
.dispatch('closeFile', localFile)
.then(Vue.nextTick)
.then(() => {
expect(router.push).toHaveBeenCalledWith(`/project${f.url}`);
-
- done();
- })
- .catch(done.fail);
+ });
});
- it('removes file if it pending', done => {
+ it('removes file if it pending', () => {
store.state.openFiles.push({
...localFile,
pending: true,
});
- store
- .dispatch('closeFile', localFile)
- .then(() => {
- expect(store.state.openFiles.length).toBe(0);
-
- done();
- })
- .catch(done.fail);
+ return store.dispatch('closeFile', localFile).then(() => {
+ expect(store.state.openFiles.length).toBe(0);
+ });
});
});
@@ -264,61 +248,48 @@ describe('IDE store file actions', () => {
);
});
- it('calls the service', done => {
- store
- .dispatch('getFileData', { path: localFile.path })
- .then(() => {
- expect(service.getFileData).toHaveBeenCalledWith(
- `${RELATIVE_URL_ROOT}/test/test/-/7297abc/${localFile.path}`,
- );
-
- done();
- })
- .catch(done.fail);
+ it('calls the service', () => {
+ return store.dispatch('getFileData', { path: localFile.path }).then(() => {
+ expect(service.getFileData).toHaveBeenCalledWith(
+ `${RELATIVE_URL_ROOT}/test/test/-/7297abc/${localFile.path}`,
+ );
+ });
});
- it('sets document title with the branchId', done => {
- store
- .dispatch('getFileData', { path: localFile.path })
- .then(() => {
- expect(document.title).toBe(`${localFile.path} · master · test/test · GitLab`);
- done();
- })
- .catch(done.fail);
+ it('sets document title with the branchId', () => {
+ return store.dispatch('getFileData', { path: localFile.path }).then(() => {
+ expect(document.title).toBe(`${localFile.path} · master · test/test · GitLab`);
+ });
});
- it('sets the file as active', done => {
- store
- .dispatch('getFileData', { path: localFile.path })
- .then(() => {
- expect(localFile.active).toBeTruthy();
-
- done();
- })
- .catch(done.fail);
+ it('sets the file as active', () => {
+ return store.dispatch('getFileData', { path: localFile.path }).then(() => {
+ expect(localFile.active).toBeTruthy();
+ });
});
- it('sets the file not as active if we pass makeFileActive false', done => {
- store
+ it('sets the file not as active if we pass makeFileActive false', () => {
+ return store
.dispatch('getFileData', { path: localFile.path, makeFileActive: false })
.then(() => {
expect(localFile.active).toBeFalsy();
-
- done();
- })
- .catch(done.fail);
+ });
});
- it('adds the file to open files', done => {
- store
- .dispatch('getFileData', { path: localFile.path })
+ it('does not update the page title with the path of the file if makeFileActive is false', () => {
+ document.title = 'dummy title';
+ return store
+ .dispatch('getFileData', { path: localFile.path, makeFileActive: false })
.then(() => {
- expect(store.state.openFiles.length).toBe(1);
- expect(store.state.openFiles[0].name).toBe(localFile.name);
+ expect(document.title).toBe(`dummy title`);
+ });
+ });
- done();
- })
- .catch(done.fail);
+ it('adds the file to open files', () => {
+ return store.dispatch('getFileData', { path: localFile.path }).then(() => {
+ expect(store.state.openFiles.length).toBe(1);
+ expect(store.state.openFiles[0].name).toBe(localFile.name);
+ });
});
});
@@ -342,15 +313,10 @@ describe('IDE store file actions', () => {
);
});
- it('sets document title considering `prevPath` on a file', done => {
- store
- .dispatch('getFileData', { path: localFile.path })
- .then(() => {
- expect(document.title).toBe(`new-shiny-file · master · test/test · GitLab`);
-
- done();
- })
- .catch(done.fail);
+ it('sets document title considering `prevPath` on a file', () => {
+ return store.dispatch('getFileData', { path: localFile.path }).then(() => {
+ expect(document.title).toBe(`new-shiny-file · master · test/test · GitLab`);
+ });
});
});
@@ -397,29 +363,19 @@ describe('IDE store file actions', () => {
mock.onGet(/(.*)/).replyOnce(200, 'raw');
});
- it('calls getRawFileData service method', done => {
- store
- .dispatch('getRawFileData', { path: tmpFile.path })
- .then(() => {
- expect(service.getRawFileData).toHaveBeenCalledWith(tmpFile);
-
- done();
- })
- .catch(done.fail);
+ it('calls getRawFileData service method', () => {
+ return store.dispatch('getRawFileData', { path: tmpFile.path }).then(() => {
+ expect(service.getRawFileData).toHaveBeenCalledWith(tmpFile);
+ });
});
- it('updates file raw data', done => {
- store
- .dispatch('getRawFileData', { path: tmpFile.path })
- .then(() => {
- expect(tmpFile.raw).toBe('raw');
-
- done();
- })
- .catch(done.fail);
+ it('updates file raw data', () => {
+ return store.dispatch('getRawFileData', { path: tmpFile.path }).then(() => {
+ expect(tmpFile.raw).toBe('raw');
+ });
});
- it('calls also getBaseRawFileData service method', done => {
+ it('calls also getBaseRawFileData service method', () => {
jest.spyOn(service, 'getBaseRawFileData').mockReturnValue(Promise.resolve('baseraw'));
store.state.currentProjectId = 'gitlab-org/gitlab-ce';
@@ -436,15 +392,58 @@ describe('IDE store file actions', () => {
tmpFile.mrChange = { new_file: false };
- store
- .dispatch('getRawFileData', { path: tmpFile.path })
- .then(() => {
- expect(service.getBaseRawFileData).toHaveBeenCalledWith(tmpFile, 'SHA');
- expect(tmpFile.baseRaw).toBe('baseraw');
+ return store.dispatch('getRawFileData', { path: tmpFile.path }).then(() => {
+ expect(service.getBaseRawFileData).toHaveBeenCalledWith(tmpFile, 'SHA');
+ expect(tmpFile.baseRaw).toBe('baseraw');
+ });
+ });
+
+ describe('sets file loading to true', () => {
+ let loadingWhenGettingRawData;
+ let loadingWhenGettingBaseRawData;
+
+ beforeEach(() => {
+ loadingWhenGettingRawData = undefined;
+ loadingWhenGettingBaseRawData = undefined;
+
+ jest.spyOn(service, 'getRawFileData').mockImplementation(f => {
+ loadingWhenGettingRawData = f.loading;
+ return Promise.resolve('raw');
+ });
+ jest.spyOn(service, 'getBaseRawFileData').mockImplementation(f => {
+ loadingWhenGettingBaseRawData = f.loading;
+ return Promise.resolve('rawBase');
+ });
+ });
- done();
- })
- .catch(done.fail);
+ it('when getting raw file data', async () => {
+ expect(tmpFile.loading).toBe(false);
+
+ await store.dispatch('getRawFileData', { path: tmpFile.path });
+
+ expect(loadingWhenGettingRawData).toBe(true);
+ expect(tmpFile.loading).toBe(false);
+ });
+
+ it('when getting base raw file data', async () => {
+ tmpFile.mrChange = { new_file: false };
+
+ expect(tmpFile.loading).toBe(false);
+
+ await store.dispatch('getRawFileData', { path: tmpFile.path });
+
+ expect(loadingWhenGettingBaseRawData).toBe(true);
+ expect(tmpFile.loading).toBe(false);
+ });
+
+ it('when file was already loading', async () => {
+ tmpFile.loading = true;
+
+ await store.dispatch('getRawFileData', { path: tmpFile.path });
+
+ expect(loadingWhenGettingRawData).toBe(true);
+ expect(tmpFile.loading).toBe(false);
+ });
});
});
@@ -453,15 +452,10 @@ describe('IDE store file actions', () => {
mock.onGet(/(.*)/).replyOnce(200, JSON.stringify({ test: '123' }));
});
- it('does not parse returned JSON', done => {
- store
- .dispatch('getRawFileData', { path: tmpFile.path })
- .then(() => {
- expect(tmpFile.raw).toEqual('{"test":"123"}');
-
- done();
- })
- .catch(done.fail);
+ it('does not parse returned JSON', () => {
+ return store.dispatch('getRawFileData', { path: tmpFile.path }).then(() => {
+ expect(tmpFile.raw).toEqual('{"test":"123"}');
+ });
});
});
@@ -489,6 +483,12 @@ describe('IDE store file actions', () => {
});
});
});
+
+ it('toggles loading off after error', async () => {
+ await expect(store.dispatch('getRawFileData', { path: tmpFile.path })).rejects.toThrow();
+
+ expect(tmpFile.loading).toBe(false);
+ });
});
});
@@ -504,32 +504,25 @@ describe('IDE store file actions', () => {
store.state.entries[tmpFile.path] = tmpFile;
});
- it('updates file content', done => {
- callAction()
- .then(() => {
- expect(tmpFile.content).toBe('content\n');
-
- done();
- })
- .catch(done.fail);
+ it('updates file content', () => {
+ return callAction().then(() => {
+ expect(tmpFile.content).toBe('content\n');
+ });
});
- it('adds file into stagedFiles array', done => {
- store
+ it('adds file into stagedFiles array', () => {
+ return store
.dispatch('changeFileContent', {
path: tmpFile.path,
content: 'content',
})
.then(() => {
expect(store.state.stagedFiles.length).toBe(1);
-
- done();
- })
- .catch(done.fail);
+ });
});
- it('adds file not more than once into stagedFiles array', done => {
- store
+ it('adds file not more than once into stagedFiles array', () => {
+ return store
.dispatch('changeFileContent', {
path: tmpFile.path,
content: 'content',
@@ -542,14 +535,11 @@ describe('IDE store file actions', () => {
)
.then(() => {
expect(store.state.stagedFiles.length).toBe(1);
-
- done();
- })
- .catch(done.fail);
+ });
});
- it('removes file from changedFiles array if not changed', done => {
- store
+ it('removes file from changedFiles array if not changed', () => {
+ return store
.dispatch('changeFileContent', {
path: tmpFile.path,
content: 'content\n',
@@ -562,10 +552,7 @@ describe('IDE store file actions', () => {
)
.then(() => {
expect(store.state.changedFiles.length).toBe(0);
-
- done();
- })
- .catch(done.fail);
+ });
});
});
@@ -723,52 +710,36 @@ describe('IDE store file actions', () => {
store.state.entries[f.path] = f;
});
- it('makes file pending in openFiles', done => {
- store
- .dispatch('openPendingTab', { file: f, keyPrefix: 'pending' })
- .then(() => {
- expect(store.state.openFiles[0].pending).toBe(true);
- })
- .then(done)
- .catch(done.fail);
+ it('makes file pending in openFiles', () => {
+ return store.dispatch('openPendingTab', { file: f, keyPrefix: 'pending' }).then(() => {
+ expect(store.state.openFiles[0].pending).toBe(true);
+ });
});
- it('returns true when opened', done => {
- store
- .dispatch('openPendingTab', { file: f, keyPrefix: 'pending' })
- .then(added => {
- expect(added).toBe(true);
- })
- .then(done)
- .catch(done.fail);
+ it('returns true when opened', () => {
+ return store.dispatch('openPendingTab', { file: f, keyPrefix: 'pending' }).then(added => {
+ expect(added).toBe(true);
+ });
});
- it('returns false when already opened', done => {
+ it('returns false when already opened', () => {
store.state.openFiles.push({
...f,
active: true,
key: `pending-${f.key}`,
});
- store
- .dispatch('openPendingTab', { file: f, keyPrefix: 'pending' })
- .then(added => {
- expect(added).toBe(false);
- })
- .then(done)
- .catch(done.fail);
+ return store.dispatch('openPendingTab', { file: f, keyPrefix: 'pending' }).then(added => {
+ expect(added).toBe(false);
+ });
});
- it('pushes router URL when added', done => {
+ it('pushes router URL when added', () => {
store.state.currentBranchId = 'master';
- store
- .dispatch('openPendingTab', { file: f, keyPrefix: 'pending' })
- .then(() => {
- expect(router.push).toHaveBeenCalledWith('/project/123/tree/master/');
- })
- .then(done)
- .catch(done.fail);
+ return store.dispatch('openPendingTab', { file: f, keyPrefix: 'pending' }).then(() => {
+ expect(router.push).toHaveBeenCalledWith('/project/123/tree/master/');
+ });
});
});
@@ -784,26 +755,18 @@ describe('IDE store file actions', () => {
};
});
- it('removes pending file from open files', done => {
+ it('removes pending file from open files', () => {
store.state.openFiles.push(f);
- store
- .dispatch('removePendingTab', f)
- .then(() => {
- expect(store.state.openFiles.length).toBe(0);
- })
- .then(done)
- .catch(done.fail);
+ return store.dispatch('removePendingTab', f).then(() => {
+ expect(store.state.openFiles.length).toBe(0);
+ });
});
- it('emits event to dispose model', done => {
- store
- .dispatch('removePendingTab', f)
- .then(() => {
- expect(eventHub.$emit).toHaveBeenCalledWith(`editor.update.model.dispose.${f.key}`);
- })
- .then(done)
- .catch(done.fail);
+ it('emits event to dispose model', () => {
+ return store.dispatch('removePendingTab', f).then(() => {
+ expect(eventHub.$emit).toHaveBeenCalledWith(`editor.update.model.dispose.${f.key}`);
+ });
});
});
@@ -812,14 +775,10 @@ describe('IDE store file actions', () => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
});
- it('emits event that files have changed', done => {
- store
- .dispatch('triggerFilesChange')
- .then(() => {
- expect(eventHub.$emit).toHaveBeenCalledWith('ide.files.change');
- })
- .then(done)
- .catch(done.fail);
+ it('emits event that files have changed', () => {
+ return store.dispatch('triggerFilesChange').then(() => {
+ expect(eventHub.$emit).toHaveBeenCalledWith('ide.files.change');
+ });
});
});
});
diff --git a/spec/frontend/ide/stores/actions/merge_request_spec.js b/spec/frontend/ide/stores/actions/merge_request_spec.js
index cb4eebd97d9..e5c4f346459 100644
--- a/spec/frontend/ide/stores/actions/merge_request_spec.js
+++ b/spec/frontend/ide/stores/actions/merge_request_spec.js
@@ -55,6 +55,7 @@ describe('IDE store merge request actions', () => {
expect(service.getProjectMergeRequests).toHaveBeenCalledWith(TEST_PROJECT, {
source_branch: 'bar',
source_project_id: TEST_PROJECT_ID,
+ state: 'opened',
order_by: 'created_at',
per_page: 1,
});
diff --git a/spec/frontend/ide/stores/actions/tree_spec.js b/spec/frontend/ide/stores/actions/tree_spec.js
index 44e2fcab436..c20941843c4 100644
--- a/spec/frontend/ide/stores/actions/tree_spec.js
+++ b/spec/frontend/ide/stores/actions/tree_spec.js
@@ -7,6 +7,7 @@ import { createStore } from '~/ide/stores';
import service from '~/ide/services';
import { createRouter } from '~/ide/ide_router';
import { file, createEntriesFromPaths } from '../../helpers';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('Multi-file store tree actions', () => {
let projectTree;
@@ -97,7 +98,7 @@ describe('Multi-file store tree actions', () => {
store.state.projects = {
'abc/def': {
- web_url: `${gl.TEST_HOST}/files`,
+ web_url: `${TEST_HOST}/files`,
branches: {
'master-testing': {
commit: {
diff --git a/spec/frontend/ide/utils_spec.js b/spec/frontend/ide/utils_spec.js
index 15baeca7f36..b6de576a0a4 100644
--- a/spec/frontend/ide/utils_spec.js
+++ b/spec/frontend/ide/utils_spec.js
@@ -1,6 +1,7 @@
import {
isTextFile,
registerLanguages,
+ registerSchemas,
trimPathComponents,
insertFinalNewline,
trimTrailingWhitespace,
@@ -158,6 +159,57 @@ describe('WebIDE utils', () => {
});
});
+ describe('registerSchemas', () => {
+ let options;
+
+ beforeEach(() => {
+ options = {
+ validate: true,
+ enableSchemaRequest: true,
+ hover: true,
+ completion: true,
+ schemas: [
+ {
+ uri: 'http://myserver/foo-schema.json',
+ fileMatch: ['*'],
+ schema: {
+ id: 'http://myserver/foo-schema.json',
+ type: 'object',
+ properties: {
+ p1: { enum: ['v1', 'v2'] },
+ p2: { $ref: 'http://myserver/bar-schema.json' },
+ },
+ },
+ },
+ {
+ uri: 'http://myserver/bar-schema.json',
+ schema: {
+ id: 'http://myserver/bar-schema.json',
+ type: 'object',
+ properties: { q1: { enum: ['x1', 'x2'] } },
+ },
+ },
+ ],
+ };
+
+ jest.spyOn(languages.json.jsonDefaults, 'setDiagnosticsOptions');
+ jest.spyOn(languages.yaml.yamlDefaults, 'setDiagnosticsOptions');
+ });
+
+ it.each`
+ language | defaultsObj
+ ${'json'} | ${languages.json.jsonDefaults}
+ ${'yaml'} | ${languages.yaml.yamlDefaults}
+ `(
+ 'registers the given schemas with monaco for lang: $language',
+ ({ language, defaultsObj }) => {
+ registerSchemas({ language, options });
+
+ expect(defaultsObj.setDiagnosticsOptions).toHaveBeenCalledWith(options);
+ },
+ );
+ });
+
describe('trimTrailingWhitespace', () => {
it.each`
input | output
diff --git a/spec/frontend/image_diff/helpers/comment_indicator_helper_spec.js b/spec/frontend/image_diff/helpers/comment_indicator_helper_spec.js
index 395bb7de362..2deb4be2b91 100644
--- a/spec/frontend/image_diff/helpers/comment_indicator_helper_spec.js
+++ b/spec/frontend/image_diff/helpers/comment_indicator_helper_spec.js
@@ -1,5 +1,6 @@
import * as commentIndicatorHelper from '~/image_diff/helpers/comment_indicator_helper';
import * as mockData from '../mock_data';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('commentIndicatorHelper', () => {
const { coordinate } = mockData;
@@ -52,7 +53,7 @@ describe('commentIndicatorHelper', () => {
beforeEach(() => {
containerEl.innerHTML = `
<div class="comment-indicator" style="left:${coordinate.x}px; top: ${coordinate.y}px;">
- <img src="${gl.TEST_HOST}/image.png">
+ <img src="${TEST_HOST}/image.png">
</div>
`;
result = commentIndicatorHelper.removeCommentIndicator(containerEl);
diff --git a/spec/frontend/image_diff/helpers/utils_helper_spec.js b/spec/frontend/image_diff/helpers/utils_helper_spec.js
index 3b6378be883..a47c681e775 100644
--- a/spec/frontend/image_diff/helpers/utils_helper_spec.js
+++ b/spec/frontend/image_diff/helpers/utils_helper_spec.js
@@ -1,6 +1,7 @@
import * as utilsHelper from '~/image_diff/helpers/utils_helper';
import ImageBadge from '~/image_diff/image_badge';
import * as mockData from '../mock_data';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('utilsHelper', () => {
const { noteId, discussionId, image, imageProperties, imageMeta } = mockData;
@@ -36,7 +37,7 @@ describe('utilsHelper', () => {
beforeEach(() => {
const imageFrameEl = document.createElement('div');
imageFrameEl.innerHTML = `
- <img src="${gl.TEST_HOST}/image.png">
+ <img src="${TEST_HOST}/image.png">
`;
discussionEl = document.createElement('div');
discussionEl.dataset.discussionId = discussionId;
diff --git a/spec/frontend/image_diff/image_diff_spec.js b/spec/frontend/image_diff/image_diff_spec.js
index c15718b5106..2b29a522193 100644
--- a/spec/frontend/image_diff/image_diff_spec.js
+++ b/spec/frontend/image_diff/image_diff_spec.js
@@ -2,6 +2,7 @@ import ImageDiff from '~/image_diff/image_diff';
import * as imageUtility from '~/lib/utils/image_utility';
import imageDiffHelper from '~/image_diff/helpers/index';
import * as mockData from './mock_data';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('ImageDiff', () => {
let element;
@@ -12,7 +13,7 @@ describe('ImageDiff', () => {
<div id="element">
<div class="diff-file">
<div class="js-image-frame">
- <img src="${gl.TEST_HOST}/image.png">
+ <img src="${TEST_HOST}/image.png">
<div class="comment-indicator"></div>
<div id="badge-1" class="badge">1</div>
<div id="badge-2" class="badge">2</div>
diff --git a/spec/frontend/image_diff/replaced_image_diff_spec.js b/spec/frontend/image_diff/replaced_image_diff_spec.js
index f2a7b7f8406..38a43bfa858 100644
--- a/spec/frontend/image_diff/replaced_image_diff_spec.js
+++ b/spec/frontend/image_diff/replaced_image_diff_spec.js
@@ -2,6 +2,7 @@ import ReplacedImageDiff from '~/image_diff/replaced_image_diff';
import ImageDiff from '~/image_diff/image_diff';
import { viewTypes } from '~/image_diff/view_types';
import imageDiffHelper from '~/image_diff/helpers/index';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('ReplacedImageDiff', () => {
let element;
@@ -12,17 +13,17 @@ describe('ReplacedImageDiff', () => {
<div id="element">
<div class="two-up">
<div class="js-image-frame">
- <img src="${gl.TEST_HOST}/image.png">
+ <img src="${TEST_HOST}/image.png">
</div>
</div>
<div class="swipe">
<div class="js-image-frame">
- <img src="${gl.TEST_HOST}/image.png">
+ <img src="${TEST_HOST}/image.png">
</div>
</div>
<div class="onion-skin">
<div class="js-image-frame">
- <img src="${gl.TEST_HOST}/image.png">
+ <img src="${TEST_HOST}/image.png">
</div>
</div>
<div class="view-modes-menu">
diff --git a/spec/frontend/import_projects/store/actions_spec.js b/spec/frontend/import_projects/store/actions_spec.js
index 1f2882a2532..fd6fbcbfce0 100644
--- a/spec/frontend/import_projects/store/actions_spec.js
+++ b/spec/frontend/import_projects/store/actions_spec.js
@@ -1,4 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
+import createFlash from '~/flash';
import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'helpers/test_constants';
import axios from '~/lib/utils/axios_utils';
@@ -22,6 +23,8 @@ import {
} from '~/import_projects/store/actions';
import state from '~/import_projects/store/state';
+jest.mock('~/flash');
+
describe('import_projects store actions', () => {
let localState;
const repos = [{ id: 1 }, { id: 2 }];
@@ -130,10 +133,28 @@ describe('import_projects store actions', () => {
);
});
- it('commits REQUEST_IMPORT and RECEIVE_IMPORT_ERROR on an unsuccessful request', () => {
+ it('commits REQUEST_IMPORT and RECEIVE_IMPORT_ERROR and shows generic error message on an unsuccessful request', async () => {
mock.onPost(`${TEST_HOST}/endpoint.json`).reply(500);
- return testAction(
+ await testAction(
+ fetchImport,
+ importPayload,
+ localState,
+ [
+ { type: REQUEST_IMPORT, payload: importPayload.repo.id },
+ { type: RECEIVE_IMPORT_ERROR, payload: importPayload.repo.id },
+ ],
+ [],
+ );
+
+ expect(createFlash).toHaveBeenCalledWith('Importing the project failed');
+ });
+
+ it('commits REQUEST_IMPORT and RECEIVE_IMPORT_ERROR and shows detailed error message on an unsuccessful request with errors fields in response', async () => {
+ const ERROR_MESSAGE = 'dummy';
+ mock.onPost(`${TEST_HOST}/endpoint.json`).reply(500, { errors: ERROR_MESSAGE });
+
+ await testAction(
fetchImport,
importPayload,
localState,
@@ -143,6 +164,8 @@ describe('import_projects store actions', () => {
],
[],
);
+
+ expect(createFlash).toHaveBeenCalledWith(`Importing the project failed: ${ERROR_MESSAGE}`);
});
});
diff --git a/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap
new file mode 100644
index 00000000000..dd3589e2951
--- /dev/null
+++ b/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap
@@ -0,0 +1,99 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Alert integration settings form default state should match the default snapshot 1`] = `
+<div>
+ <p>
+ <gl-sprintf-stub
+ message="Action to take when receiving an alert. %{docsLink}"
+ />
+ </p>
+
+ <form>
+ <gl-form-group-stub
+ class="gl-pl-0"
+ >
+ <gl-form-checkbox-stub
+ checked="true"
+ data-qa-selector="create_issue_checkbox"
+ >
+ <span>
+ Create an issue. Issues are created for each alert triggered.
+ </span>
+ </gl-form-checkbox-stub>
+ </gl-form-group-stub>
+
+ <gl-form-group-stub
+ class="col-8 col-md-9 gl-px-6"
+ label-for="alert-integration-settings-issue-template"
+ label-size="sm"
+ >
+ <label
+ class="gl-display-inline-flex"
+ for="alert-integration-settings-issue-template"
+ >
+
+ Issue template (optional)
+
+ <gl-link-stub
+ href="/help/user/project/description_templates#creating-issue-templates"
+ target="_blank"
+ >
+ <gl-icon-stub
+ name="question"
+ size="12"
+ />
+ </gl-link-stub>
+ </label>
+
+ <gl-new-dropdown-stub
+ block="true"
+ category="tertiary"
+ data-qa-selector="incident_templates_dropdown"
+ headertext=""
+ id="alert-integration-settings-issue-template"
+ size="medium"
+ text="selecte_tmpl"
+ variant="default"
+ >
+ <gl-new-dropdown-item-stub
+ avatarurl=""
+ data-qa-selector="incident_templates_item"
+ iconcolor=""
+ iconname=""
+ iconrightname=""
+ ischeckitem="true"
+ secondarytext=""
+ >
+
+ No template selected
+
+ </gl-new-dropdown-item-stub>
+ </gl-new-dropdown-stub>
+ </gl-form-group-stub>
+
+ <gl-form-group-stub
+ class="gl-pl-0 gl-mb-5"
+ >
+ <gl-form-checkbox-stub>
+ <span>
+ Send a separate email notification to Developers.
+ </span>
+ </gl-form-checkbox-stub>
+ </gl-form-group-stub>
+
+ <gl-button-stub
+ category="tertiary"
+ class="js-no-auto-disable"
+ data-qa-selector="save_changes_button"
+ icon=""
+ size="medium"
+ type="submit"
+ variant="success"
+ >
+
+ Save changes
+
+ </gl-button-stub>
+ </form>
+</div>
+`;
diff --git a/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap
new file mode 100644
index 00000000000..5f355ee8261
--- /dev/null
+++ b/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap
@@ -0,0 +1,63 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`IncidentsSettingTabs should render the component 1`] = `
+<section
+ class="settings no-animate qa-incident-management-settings"
+ data-qa-selector="incidents_settings_content"
+ id="incident-management-settings"
+>
+ <div
+ class="settings-header"
+ >
+ <h3
+ class="h4"
+ >
+
+ Incidents
+
+ </h3>
+
+ <gl-button-stub
+ category="tertiary"
+ class="js-settings-toggle"
+ icon=""
+ size="medium"
+ variant="default"
+ >
+ Expand
+ </gl-button-stub>
+
+ <p>
+
+ Set up integrations with external tools to help better manage incidents.
+
+ </p>
+ </div>
+
+ <div
+ class="settings-content"
+ >
+ <gl-tabs-stub
+ theme="indigo"
+ >
+ <gl-tab-stub
+ title="Alert integration"
+ >
+ <alertssettingsform-stub
+ class="gl-pt-3"
+ data-testid="AlertsSettingsForm-tab"
+ />
+ </gl-tab-stub>
+ <gl-tab-stub
+ title="PagerDuty integration"
+ >
+ <pagerdutysettingsform-stub
+ class="gl-pt-3"
+ data-testid="PagerDutySettingsForm-tab"
+ />
+ </gl-tab-stub>
+ <!---->
+ </gl-tabs-stub>
+ </div>
+</section>
+`;
diff --git a/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap
new file mode 100644
index 00000000000..17ada722034
--- /dev/null
+++ b/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap
@@ -0,0 +1,89 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Alert integration settings form should match the default snapshot 1`] = `
+<div>
+ <!---->
+
+ <p>
+ Setting up a webhook with PagerDuty will automatically create a GitLab issue for each PagerDuty incident.
+ </p>
+
+ <form>
+ <gl-form-group-stub
+ class="col-8 col-md-9 gl-p-0"
+ >
+ <gl-toggle-stub
+ id="active"
+ label="Active"
+ labelposition="top"
+ value="true"
+ />
+ </gl-form-group-stub>
+
+ <gl-form-group-stub
+ class="col-8 col-md-9 gl-p-0"
+ label="Webhook URL"
+ label-class="label-bold"
+ label-for="url"
+ >
+ <gl-form-input-group-stub
+ data-testid="webhook-url"
+ id="url"
+ predefinedoptions="[object Object]"
+ readonly=""
+ value="pagerduty.webhook.com"
+ />
+
+ <div
+ class="gl-text-gray-400 gl-pt-2"
+ >
+ <gl-sprintf-stub
+ message="Create a GitLab issue for each PagerDuty incident by %{docsLink}"
+ />
+ </div>
+
+ <gl-button-stub
+ category="tertiary"
+ class="gl-mt-3"
+ data-testid="webhook-reset-btn"
+ icon=""
+ role="button"
+ size="medium"
+ tabindex="0"
+ variant="default"
+ >
+
+ Reset webhook URL
+
+ </gl-button-stub>
+
+ <gl-modal-stub
+ modalclass=""
+ modalid="resetWebhookModal"
+ ok-title="Reset webhook URL"
+ ok-variant="danger"
+ size="md"
+ title="Reset webhook URL"
+ titletag="h4"
+ >
+
+ Resetting the webhook URL for this project will require updating this integration's settings in PagerDuty.
+
+ </gl-modal-stub>
+ </gl-form-group-stub>
+
+ <gl-button-stub
+ category="tertiary"
+ class="js-no-auto-disable"
+ icon=""
+ size="medium"
+ type="submit"
+ variant="success"
+ >
+
+ Save changes
+
+ </gl-button-stub>
+ </form>
+</div>
+`;
diff --git a/spec/frontend/incidents_settings/components/alerts_form_spec.js b/spec/frontend/incidents_settings/components/alerts_form_spec.js
new file mode 100644
index 00000000000..04832f31e58
--- /dev/null
+++ b/spec/frontend/incidents_settings/components/alerts_form_spec.js
@@ -0,0 +1,49 @@
+import { shallowMount } from '@vue/test-utils';
+import AlertsSettingsForm from '~/incidents_settings/components/alerts_form.vue';
+
+describe('Alert integration settings form', () => {
+ let wrapper;
+ const service = { updateSettings: jest.fn().mockResolvedValue() };
+
+ const findForm = () => wrapper.find({ ref: 'settingsForm' });
+
+ beforeEach(() => {
+ wrapper = shallowMount(AlertsSettingsForm, {
+ provide: {
+ service,
+ alertSettings: {
+ issueTemplateKey: 'selecte_tmpl',
+ createIssue: true,
+ sendEmail: false,
+ templates: [],
+ },
+ },
+ });
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ wrapper = null;
+ }
+ });
+
+ describe('default state', () => {
+ it('should match the default snapshot', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ describe('form', () => {
+ it('should call service `updateSettings` on submit', () => {
+ findForm().trigger('submit');
+ expect(service.updateSettings).toHaveBeenCalledWith(
+ expect.objectContaining({
+ create_issue: wrapper.vm.createIssueEnabled,
+ issue_template_key: wrapper.vm.issueTemplate,
+ send_email: wrapper.vm.sendEmailEnabled,
+ }),
+ );
+ });
+ });
+});
diff --git a/spec/frontend/incidents_settings/components/incidents_settings_service_spec.js b/spec/frontend/incidents_settings/components/incidents_settings_service_spec.js
new file mode 100644
index 00000000000..58f9a318808
--- /dev/null
+++ b/spec/frontend/incidents_settings/components/incidents_settings_service_spec.js
@@ -0,0 +1,55 @@
+import axios from '~/lib/utils/axios_utils';
+import AxiosMockAdapter from 'axios-mock-adapter';
+import httpStatusCodes from '~/lib/utils/http_status';
+import IncidentsSettingsService from '~/incidents_settings/incidents_settings_service';
+import { ERROR_MSG } from '~/incidents_settings/constants';
+import createFlash from '~/flash';
+import { refreshCurrentPage } from '~/lib/utils/url_utility';
+
+jest.mock('~/flash');
+jest.mock('~/lib/utils/url_utility');
+
+describe('IncidentsSettingsService', () => {
+ const settingsEndpoint = 'operations/settings';
+ const webhookUpdateEndpoint = 'webhook/update';
+ let mock;
+ let service;
+
+ beforeEach(() => {
+ mock = new AxiosMockAdapter(axios);
+ service = new IncidentsSettingsService(settingsEndpoint, webhookUpdateEndpoint);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('updateSettings', () => {
+ it('should refresh the page on successful update', () => {
+ mock.onPatch().reply(httpStatusCodes.OK);
+
+ return service.updateSettings({}).then(() => {
+ expect(refreshCurrentPage).toHaveBeenCalled();
+ });
+ });
+
+ it('should display a flash message on update error', () => {
+ mock.onPatch().reply(httpStatusCodes.BAD_REQUEST);
+
+ return service.updateSettings({}).then(() => {
+ expect(createFlash).toHaveBeenCalledWith(expect.stringContaining(ERROR_MSG), 'alert');
+ });
+ });
+ });
+
+ describe('resetWebhookUrl', () => {
+ it('should make a call for webhook update', () => {
+ jest.spyOn(axios, 'post');
+ mock.onPost().reply(httpStatusCodes.OK);
+
+ return service.resetWebhookUrl().then(() => {
+ expect(axios.post).toHaveBeenCalledWith(webhookUpdateEndpoint);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/incidents_settings/components/incidents_settings_tabs_spec.js b/spec/frontend/incidents_settings/components/incidents_settings_tabs_spec.js
new file mode 100644
index 00000000000..47e2aecc108
--- /dev/null
+++ b/spec/frontend/incidents_settings/components/incidents_settings_tabs_spec.js
@@ -0,0 +1,55 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlTab } from '@gitlab/ui';
+import IncidentsSettingTabs from '~/incidents_settings/components/incidents_settings_tabs.vue';
+
+describe('IncidentsSettingTabs', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = shallowMount(IncidentsSettingTabs, {
+ provide: { glFeatures: { pagerdutyWebhook: true } },
+ });
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ const findToggleButton = () => wrapper.find({ ref: 'toggleBtn' });
+ const findSectionHeader = () => wrapper.find({ ref: 'sectionHeader' });
+
+ const findIntegrationTabs = () => wrapper.findAll(GlTab);
+ it('renders header text', () => {
+ expect(findSectionHeader().text()).toBe('Incidents');
+ });
+
+ describe('expand/collapse button', () => {
+ it('renders as an expand button by default', () => {
+ expect(findToggleButton().text()).toBe('Expand');
+ });
+ });
+
+ it('should render the component', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('should render the tab for each active integration', () => {
+ const activeTabs = wrapper.vm.$options.tabs.filter(tab => tab.active);
+ expect(findIntegrationTabs().length).toBe(activeTabs.length);
+ activeTabs.forEach((tab, index) => {
+ expect(
+ findIntegrationTabs()
+ .at(index)
+ .attributes('title'),
+ ).toBe(tab.title);
+ expect(
+ findIntegrationTabs()
+ .at(index)
+ .find(`[data-testid="${tab.component}-tab"]`)
+ .exists(),
+ ).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/incidents_settings/components/pagerduty_form_spec.js b/spec/frontend/incidents_settings/components/pagerduty_form_spec.js
new file mode 100644
index 00000000000..521094ad54c
--- /dev/null
+++ b/spec/frontend/incidents_settings/components/pagerduty_form_spec.js
@@ -0,0 +1,67 @@
+import { shallowMount } from '@vue/test-utils';
+import waitForPromises from 'helpers/wait_for_promises';
+import PagerDutySettingsForm from '~/incidents_settings/components/pagerduty_form.vue';
+import { GlAlert, GlModal } from '@gitlab/ui';
+
+describe('Alert integration settings form', () => {
+ let wrapper;
+ const resetWebhookUrl = jest.fn();
+ const service = { updateSettings: jest.fn().mockResolvedValue(), resetWebhookUrl };
+
+ const findForm = () => wrapper.find({ ref: 'settingsForm' });
+ const findWebhookInput = () => wrapper.find('[data-testid="webhook-url"]');
+ const findModal = () => wrapper.find(GlModal);
+ const findAlert = () => wrapper.find(GlAlert);
+
+ beforeEach(() => {
+ wrapper = shallowMount(PagerDutySettingsForm, {
+ provide: {
+ service,
+ pagerDutySettings: {
+ active: true,
+ webhookUrl: 'pagerduty.webhook.com',
+ webhookUpdateEndpoint: 'webhook/update',
+ },
+ },
+ });
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ wrapper = null;
+ }
+ });
+
+ it('should match the default snapshot', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('should call service `updateSettings` on form submit', () => {
+ findForm().trigger('submit');
+ expect(service.updateSettings).toHaveBeenCalledWith(
+ expect.objectContaining({ pagerduty_active: wrapper.vm.active }),
+ );
+ });
+
+ describe('Webhook reset', () => {
+ it('should make a call for webhook reset and reset form values', async () => {
+ const newWebhookUrl = 'new.webhook.url?token=token';
+ resetWebhookUrl.mockResolvedValueOnce({
+ data: { pagerduty_webhook_url: newWebhookUrl },
+ });
+ findModal().vm.$emit('ok');
+ await waitForPromises();
+ expect(resetWebhookUrl).toHaveBeenCalled();
+ expect(findWebhookInput().attributes('value')).toBe(newWebhookUrl);
+ expect(findAlert().attributes('variant')).toBe('success');
+ });
+
+ it('should show error message and NOT reset webhook url', async () => {
+ resetWebhookUrl.mockRejectedValueOnce();
+ findModal().vm.$emit('ok');
+ await waitForPromises();
+ expect(findAlert().attributes('variant')).toBe('danger');
+ });
+ });
+});
diff --git a/spec/frontend/integrations/edit/components/active_toggle_spec.js b/spec/frontend/integrations/edit/components/active_toggle_spec.js
index 5469b45f708..228d8f5fc30 100644
--- a/spec/frontend/integrations/edit/components/active_toggle_spec.js
+++ b/spec/frontend/integrations/edit/components/active_toggle_spec.js
@@ -1,8 +1,10 @@
import { mount } from '@vue/test-utils';
-import ActiveToggle from '~/integrations/edit/components/active_toggle.vue';
import { GlToggle } from '@gitlab/ui';
+import ActiveToggle from '~/integrations/edit/components/active_toggle.vue';
+
const GL_TOGGLE_ACTIVE_CLASS = 'is-checked';
+const GL_TOGGLE_DISABLED_CLASS = 'is-disabled';
describe('ActiveToggle', () => {
let wrapper;
@@ -11,9 +13,12 @@ describe('ActiveToggle', () => {
initialActivated: true,
};
- const createComponent = props => {
+ const createComponent = (props = {}, isInheriting = false) => {
wrapper = mount(ActiveToggle, {
propsData: { ...defaultProps, ...props },
+ computed: {
+ isInheriting: () => isInheriting,
+ },
});
};
@@ -29,6 +34,15 @@ describe('ActiveToggle', () => {
const findInputInToggle = () => findGlToggle().find('input');
describe('template', () => {
+ describe('is inheriting adminSettings', () => {
+ it('renders GlToggle as disabled', () => {
+ createComponent({}, true);
+
+ expect(findGlToggle().exists()).toBe(true);
+ expect(findButtonInToggle().classes()).toContain(GL_TOGGLE_DISABLED_CLASS);
+ });
+ });
+
describe('initialActivated is false', () => {
it('renders GlToggle as inactive', () => {
createComponent({
diff --git a/spec/frontend/integrations/edit/components/dynamic_field_spec.js b/spec/frontend/integrations/edit/components/dynamic_field_spec.js
index e5710641f81..3a7a0efcab7 100644
--- a/spec/frontend/integrations/edit/components/dynamic_field_spec.js
+++ b/spec/frontend/integrations/edit/components/dynamic_field_spec.js
@@ -14,9 +14,12 @@ describe('DynamicField', () => {
value: '1',
};
- const createComponent = props => {
+ const createComponent = (props, isInheriting = false) => {
wrapper = mount(DynamicField, {
propsData: { ...defaultProps, ...props },
+ computed: {
+ isInheriting: () => isInheriting,
+ },
});
};
@@ -34,108 +37,143 @@ describe('DynamicField', () => {
const findGlFormTextarea = () => wrapper.find(GlFormTextarea);
describe('template', () => {
- describe('dynamic field', () => {
- describe('type is checkbox', () => {
- beforeEach(() => {
- createComponent({
- type: 'checkbox',
+ describe.each([[true, 'disabled', 'readonly'], [false, undefined, undefined]])(
+ 'dynamic field, when isInheriting = `%p`',
+ (isInheriting, disabled, readonly) => {
+ describe('type is checkbox', () => {
+ beforeEach(() => {
+ createComponent(
+ {
+ type: 'checkbox',
+ },
+ isInheriting,
+ );
});
- });
- it('renders GlFormCheckbox', () => {
- expect(findGlFormCheckbox().exists()).toBe(true);
- });
+ it(`renders GlFormCheckbox, which ${isInheriting ? 'is' : 'is not'} disabled`, () => {
+ expect(findGlFormCheckbox().exists()).toBe(true);
+ expect(
+ findGlFormCheckbox()
+ .find('[type=checkbox]')
+ .attributes('disabled'),
+ ).toBe(disabled);
+ });
- it('does not render other types of input', () => {
- expect(findGlFormSelect().exists()).toBe(false);
- expect(findGlFormTextarea().exists()).toBe(false);
- expect(findGlFormInput().exists()).toBe(false);
+ it('does not render other types of input', () => {
+ expect(findGlFormSelect().exists()).toBe(false);
+ expect(findGlFormTextarea().exists()).toBe(false);
+ expect(findGlFormInput().exists()).toBe(false);
+ });
});
- });
- describe('type is select', () => {
- beforeEach(() => {
- createComponent({
- type: 'select',
- choices: [['all', 'All details'], ['standard', 'Standard']],
+ describe('type is select', () => {
+ beforeEach(() => {
+ createComponent(
+ {
+ type: 'select',
+ choices: [['all', 'All details'], ['standard', 'Standard']],
+ },
+ isInheriting,
+ );
});
- });
- it('renders findGlFormSelect', () => {
- expect(findGlFormSelect().exists()).toBe(true);
- expect(findGlFormSelect().findAll('option')).toHaveLength(2);
- });
+ it(`renders GlFormSelect, which ${isInheriting ? 'is' : 'is not'} disabled`, () => {
+ expect(findGlFormSelect().exists()).toBe(true);
+ expect(findGlFormSelect().findAll('option')).toHaveLength(2);
+ expect(
+ findGlFormSelect()
+ .find('select')
+ .attributes('disabled'),
+ ).toBe(disabled);
+ });
- it('does not render other types of input', () => {
- expect(findGlFormCheckbox().exists()).toBe(false);
- expect(findGlFormTextarea().exists()).toBe(false);
- expect(findGlFormInput().exists()).toBe(false);
+ it('does not render other types of input', () => {
+ expect(findGlFormCheckbox().exists()).toBe(false);
+ expect(findGlFormTextarea().exists()).toBe(false);
+ expect(findGlFormInput().exists()).toBe(false);
+ });
});
- });
- describe('type is textarea', () => {
- beforeEach(() => {
- createComponent({
- type: 'textarea',
+ describe('type is textarea', () => {
+ beforeEach(() => {
+ createComponent(
+ {
+ type: 'textarea',
+ },
+ isInheriting,
+ );
});
- });
- it('renders findGlFormTextarea', () => {
- expect(findGlFormTextarea().exists()).toBe(true);
- });
+ it(`renders GlFormTextarea, which ${isInheriting ? 'is' : 'is not'} readonly`, () => {
+ expect(findGlFormTextarea().exists()).toBe(true);
+ expect(
+ findGlFormTextarea()
+ .find('textarea')
+ .attributes('readonly'),
+ ).toBe(readonly);
+ });
- it('does not render other types of input', () => {
- expect(findGlFormCheckbox().exists()).toBe(false);
- expect(findGlFormSelect().exists()).toBe(false);
- expect(findGlFormInput().exists()).toBe(false);
+ it('does not render other types of input', () => {
+ expect(findGlFormCheckbox().exists()).toBe(false);
+ expect(findGlFormSelect().exists()).toBe(false);
+ expect(findGlFormInput().exists()).toBe(false);
+ });
});
- });
- describe('type is password', () => {
- beforeEach(() => {
- createComponent({
- type: 'password',
+ describe('type is password', () => {
+ beforeEach(() => {
+ createComponent(
+ {
+ type: 'password',
+ },
+ isInheriting,
+ );
});
- });
- it('renders GlFormInput', () => {
- expect(findGlFormInput().exists()).toBe(true);
- expect(findGlFormInput().attributes('type')).toBe('password');
- });
+ it(`renders GlFormInput, which ${isInheriting ? 'is' : 'is not'} readonly`, () => {
+ expect(findGlFormInput().exists()).toBe(true);
+ expect(findGlFormInput().attributes('type')).toBe('password');
+ expect(findGlFormInput().attributes('readonly')).toBe(readonly);
+ });
- it('does not render other types of input', () => {
- expect(findGlFormCheckbox().exists()).toBe(false);
- expect(findGlFormSelect().exists()).toBe(false);
- expect(findGlFormTextarea().exists()).toBe(false);
+ it('does not render other types of input', () => {
+ expect(findGlFormCheckbox().exists()).toBe(false);
+ expect(findGlFormSelect().exists()).toBe(false);
+ expect(findGlFormTextarea().exists()).toBe(false);
+ });
});
- });
- describe('type is text', () => {
- beforeEach(() => {
- createComponent({
- type: 'text',
- required: true,
+ describe('type is text', () => {
+ beforeEach(() => {
+ createComponent(
+ {
+ type: 'text',
+ required: true,
+ },
+ isInheriting,
+ );
});
- });
- it('renders GlFormInput', () => {
- expect(findGlFormInput().exists()).toBe(true);
- expect(findGlFormInput().attributes()).toMatchObject({
- type: 'text',
- id: 'service_project_url',
- name: 'service[project_url]',
- placeholder: defaultProps.placeholder,
- required: 'required',
+ it(`renders GlFormInput, which ${isInheriting ? 'is' : 'is not'} readonly`, () => {
+ expect(findGlFormInput().exists()).toBe(true);
+ expect(findGlFormInput().attributes()).toMatchObject({
+ type: 'text',
+ id: 'service_project_url',
+ name: 'service[project_url]',
+ placeholder: defaultProps.placeholder,
+ required: 'required',
+ });
+ expect(findGlFormInput().attributes('readonly')).toBe(readonly);
});
- });
- it('does not render other types of input', () => {
- expect(findGlFormCheckbox().exists()).toBe(false);
- expect(findGlFormSelect().exists()).toBe(false);
- expect(findGlFormTextarea().exists()).toBe(false);
+ it('does not render other types of input', () => {
+ expect(findGlFormCheckbox().exists()).toBe(false);
+ expect(findGlFormSelect().exists()).toBe(false);
+ expect(findGlFormTextarea().exists()).toBe(false);
+ });
});
- });
- });
+ },
+ );
describe('help text', () => {
it('renders description with help text', () => {
@@ -147,6 +185,20 @@ describe('DynamicField', () => {
.text(),
).toBe(defaultProps.help);
});
+
+ it('renders description with help text as HTML', () => {
+ const helpHTML = 'The <strong>URL</strong> of the project';
+
+ createComponent({
+ help: helpHTML,
+ });
+
+ expect(
+ findGlFormGroup()
+ .find('small')
+ .html(),
+ ).toContain(helpHTML);
+ });
});
describe('label text', () => {
@@ -175,5 +227,39 @@ describe('DynamicField', () => {
});
});
});
+
+ describe('validations', () => {
+ describe('password field', () => {
+ beforeEach(() => {
+ createComponent({
+ type: 'password',
+ required: true,
+ value: null,
+ });
+
+ wrapper.vm.validated = true;
+ });
+
+ describe('without value', () => {
+ it('requires validation', () => {
+ expect(wrapper.vm.valid).toBe(false);
+ expect(findGlFormGroup().classes('is-invalid')).toBe(true);
+ expect(findGlFormInput().classes('is-invalid')).toBe(true);
+ });
+ });
+
+ describe('with value', () => {
+ beforeEach(() => {
+ wrapper.setProps({ value: 'true' });
+ });
+
+ it('does not require validation', () => {
+ expect(wrapper.vm.valid).toBe(true);
+ expect(findGlFormGroup().classes('is-valid')).toBe(true);
+ expect(findGlFormInput().classes('is-valid')).toBe(true);
+ });
+ });
+ });
+ });
});
});
diff --git a/spec/frontend/integrations/edit/components/integration_form_spec.js b/spec/frontend/integrations/edit/components/integration_form_spec.js
index b598a71cea8..482c6a439f2 100644
--- a/spec/frontend/integrations/edit/components/integration_form_spec.js
+++ b/spec/frontend/integrations/edit/components/integration_form_spec.js
@@ -1,32 +1,32 @@
import { shallowMount } from '@vue/test-utils';
+import { createStore } from '~/integrations/edit/store';
import IntegrationForm from '~/integrations/edit/components/integration_form.vue';
+import OverrideDropdown from '~/integrations/edit/components/override_dropdown.vue';
import ActiveToggle from '~/integrations/edit/components/active_toggle.vue';
import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue';
+import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
import TriggerFields from '~/integrations/edit/components/trigger_fields.vue';
import DynamicField from '~/integrations/edit/components/dynamic_field.vue';
+import { mockIntegrationProps } from 'jest/integrations/edit/mock_data';
describe('IntegrationForm', () => {
let wrapper;
- const defaultProps = {
- activeToggleProps: {
- initialActivated: true,
- },
- showActive: true,
- triggerFieldsProps: {
- initialTriggerCommit: false,
- initialTriggerMergeRequest: false,
- initialEnableComments: false,
- },
- type: '',
- };
-
- const createComponent = props => {
+ const createComponent = (customStateProps = {}, featureFlags = {}, initialState = {}) => {
wrapper = shallowMount(IntegrationForm, {
- propsData: { ...defaultProps, ...props },
+ propsData: {},
+ store: createStore({
+ customState: { ...mockIntegrationProps, ...customStateProps },
+ ...initialState,
+ }),
stubs: {
+ OverrideDropdown,
ActiveToggle,
JiraTriggerFields,
+ TriggerFields,
+ },
+ provide: {
+ glFeatures: featureFlags,
},
});
};
@@ -38,8 +38,10 @@ describe('IntegrationForm', () => {
}
});
+ const findOverrideDropdown = () => wrapper.find(OverrideDropdown);
const findActiveToggle = () => wrapper.find(ActiveToggle);
const findJiraTriggerFields = () => wrapper.find(JiraTriggerFields);
+ const findJiraIssuesFields = () => wrapper.find(JiraIssuesFields);
const findTriggerFields = () => wrapper.find(TriggerFields);
describe('template', () => {
@@ -62,23 +64,41 @@ describe('IntegrationForm', () => {
});
describe('type is "slack"', () => {
- it('does not render JiraTriggerFields', () => {
- createComponent({
- type: 'slack',
- });
+ beforeEach(() => {
+ createComponent({ type: 'slack' });
+ });
+ it('does not render JiraTriggerFields', () => {
expect(findJiraTriggerFields().exists()).toBe(false);
});
+
+ it('does not render JiraIssuesFields', () => {
+ expect(findJiraIssuesFields().exists()).toBe(false);
+ });
});
describe('type is "jira"', () => {
it('renders JiraTriggerFields', () => {
- createComponent({
- type: 'jira',
- });
+ createComponent({ type: 'jira' });
expect(findJiraTriggerFields().exists()).toBe(true);
});
+
+ describe('featureFlag jiraIssuesIntegration is false', () => {
+ it('does not render JiraIssuesFields', () => {
+ createComponent({ type: 'jira' }, { jiraIssuesIntegration: false });
+
+ expect(findJiraIssuesFields().exists()).toBe(false);
+ });
+ });
+
+ describe('featureFlag jiraIssuesIntegration is true', () => {
+ it('renders JiraIssuesFields', () => {
+ createComponent({ type: 'jira' }, { jiraIssuesIntegration: true });
+
+ expect(findJiraIssuesFields().exists()).toBe(true);
+ });
+ });
});
describe('triggerEvents is present', () => {
@@ -116,5 +136,35 @@ describe('IntegrationForm', () => {
});
});
});
+
+ describe('adminState state is null', () => {
+ it('does not render OverrideDropdown', () => {
+ createComponent(
+ {},
+ {},
+ {
+ adminState: null,
+ },
+ );
+
+ expect(findOverrideDropdown().exists()).toBe(false);
+ });
+ });
+
+ describe('adminState state is an object', () => {
+ it('renders OverrideDropdown', () => {
+ createComponent(
+ {},
+ {},
+ {
+ adminState: {
+ ...mockIntegrationProps,
+ },
+ },
+ );
+
+ expect(findOverrideDropdown().exists()).toBe(true);
+ });
+ });
});
});
diff --git a/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js b/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js
new file mode 100644
index 00000000000..f58825f6297
--- /dev/null
+++ b/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js
@@ -0,0 +1,96 @@
+import { mount } from '@vue/test-utils';
+
+import { GlFormCheckbox, GlFormInput } from '@gitlab/ui';
+
+import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
+
+describe('JiraIssuesFields', () => {
+ let wrapper;
+
+ const defaultProps = {
+ showJiraIssuesIntegration: true,
+ editProjectPath: '/edit',
+ };
+
+ const createComponent = props => {
+ wrapper = mount(JiraIssuesFields, {
+ propsData: { ...defaultProps, ...props },
+ });
+ };
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ wrapper = null;
+ }
+ });
+
+ const findEnableCheckbox = () => wrapper.find(GlFormCheckbox);
+ const findProjectKey = () => wrapper.find(GlFormInput);
+ const expectedBannerText = 'This is a Premium feature';
+
+ describe('template', () => {
+ describe('upgrade banner for non-Premium user', () => {
+ beforeEach(() => {
+ createComponent({ initialProjectKey: '', showJiraIssuesIntegration: false });
+ });
+
+ it('shows upgrade banner', () => {
+ expect(wrapper.text()).toContain(expectedBannerText);
+ });
+
+ it('does not show checkbox and input field', () => {
+ expect(findEnableCheckbox().exists()).toBe(false);
+ expect(findProjectKey().exists()).toBe(false);
+ });
+ });
+
+ describe('Enable Jira issues checkbox', () => {
+ beforeEach(() => {
+ createComponent({ initialProjectKey: '' });
+ });
+
+ it('does not show upgrade banner', () => {
+ expect(wrapper.text()).not.toContain(expectedBannerText);
+ });
+
+ // As per https://vuejs.org/v2/guide/forms.html#Checkbox-1,
+ // browsers don't include unchecked boxes in form submissions.
+ it('includes issues_enabled as false even if unchecked', () => {
+ expect(wrapper.contains('input[name="service[issues_enabled]"]')).toBe(true);
+ });
+
+ it('disables project_key input', () => {
+ expect(findProjectKey().attributes('disabled')).toBe('disabled');
+ });
+
+ it('does not require project_key', () => {
+ expect(findProjectKey().attributes('required')).toBeUndefined();
+ });
+
+ describe('on enable issues', () => {
+ it('enables project_key input', () => {
+ findEnableCheckbox().vm.$emit('input', true);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findProjectKey().attributes('disabled')).toBeUndefined();
+ });
+ });
+
+ it('requires project_key input', () => {
+ findEnableCheckbox().vm.$emit('input', true);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findProjectKey().attributes('required')).toBe('required');
+ });
+ });
+ });
+ });
+
+ it('contains link to editProjectPath', () => {
+ createComponent();
+
+ expect(wrapper.contains(`a[href="${defaultProps.editProjectPath}"]`)).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js b/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js
index e4c2a0be6a3..782930eb6a2 100644
--- a/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js
+++ b/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js
@@ -1,6 +1,6 @@
import { mount } from '@vue/test-utils';
-import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue';
import { GlFormCheckbox } from '@gitlab/ui';
+import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue';
describe('JiraTriggerFields', () => {
let wrapper;
@@ -11,9 +11,12 @@ describe('JiraTriggerFields', () => {
initialEnableComments: false,
};
- const createComponent = props => {
+ const createComponent = (props, isInheriting = false) => {
wrapper = mount(JiraTriggerFields, {
propsData: { ...defaultProps, ...props },
+ computed: {
+ isInheriting: () => isInheriting,
+ },
});
};
@@ -93,5 +96,23 @@ describe('JiraTriggerFields', () => {
expect(findCommentDetail().isVisible()).toBe(true);
});
});
+
+ it('disables checkboxes and radios if inheriting', () => {
+ createComponent(
+ {
+ initialTriggerCommit: true,
+ initialEnableComments: true,
+ },
+ true,
+ );
+
+ wrapper.findAll('[type=checkbox]').wrappers.forEach(checkbox => {
+ expect(checkbox.attributes('disabled')).toBe('disabled');
+ });
+
+ wrapper.findAll('[type=radio]').wrappers.forEach(radio => {
+ expect(radio.attributes('disabled')).toBe('disabled');
+ });
+ });
});
});
diff --git a/spec/frontend/integrations/edit/components/trigger_fields_spec.js b/spec/frontend/integrations/edit/components/trigger_fields_spec.js
index 337876c6d16..41bccb8ada0 100644
--- a/spec/frontend/integrations/edit/components/trigger_fields_spec.js
+++ b/spec/frontend/integrations/edit/components/trigger_fields_spec.js
@@ -9,9 +9,12 @@ describe('TriggerFields', () => {
type: 'slack',
};
- const createComponent = props => {
+ const createComponent = (props, isInheriting = false) => {
wrapper = mount(TriggerFields, {
propsData: { ...defaultProps, ...props },
+ computed: {
+ isInheriting: () => isInheriting,
+ },
});
};
@@ -22,10 +25,11 @@ describe('TriggerFields', () => {
}
});
+ const findAllGlFormGroups = () => wrapper.find('#trigger-fields').findAll(GlFormGroup);
const findAllGlFormCheckboxes = () => wrapper.findAll(GlFormCheckbox);
const findAllGlFormInputs = () => wrapper.findAll(GlFormInput);
- describe('template', () => {
+ describe.each([true, false])('template, isInheriting = `%p`', isInheriting => {
it('renders a label with text "Trigger"', () => {
createComponent();
@@ -51,9 +55,12 @@ describe('TriggerFields', () => {
];
beforeEach(() => {
- createComponent({
- events,
- });
+ createComponent(
+ {
+ events,
+ },
+ isInheriting,
+ );
});
it('does not render GlFormInput for each event', () => {
@@ -69,8 +76,10 @@ describe('TriggerFields', () => {
});
});
- it('renders GlFormCheckbox for each event', () => {
- const checkboxes = findAllGlFormCheckboxes();
+ it(`renders GlFormCheckbox and corresponding hidden input for each event, which ${
+ isInheriting ? 'is' : 'is not'
+ } disabled`, () => {
+ const checkboxes = findAllGlFormGroups();
const expectedResults = [
{ labelText: 'Push', inputName: 'service[push_event]' },
{ labelText: 'Merge Request', inputName: 'service[merge_requests_event]' },
@@ -78,14 +87,22 @@ describe('TriggerFields', () => {
expect(checkboxes).toHaveLength(2);
checkboxes.wrappers.forEach((checkbox, index) => {
+ const checkBox = checkbox.find(GlFormCheckbox);
+
expect(checkbox.find('label').text()).toBe(expectedResults[index].labelText);
- expect(checkbox.find('input').attributes('name')).toBe(expectedResults[index].inputName);
- expect(checkbox.vm.$attrs.checked).toBe(events[index].value);
+ expect(checkbox.find('[type=hidden]').attributes('name')).toBe(
+ expectedResults[index].inputName,
+ );
+ expect(checkbox.find('[type=hidden]').attributes('value')).toBe(
+ events[index].value.toString(),
+ );
+ expect(checkBox.vm.$attrs.disabled).toBe(isInheriting);
+ expect(checkBox.vm.$attrs.checked).toBe(events[index].value);
});
});
});
- describe('events with field property', () => {
+ describe('events with field property, isInheriting = `%p`', () => {
const events = [
{
field: {
@@ -102,16 +119,21 @@ describe('TriggerFields', () => {
];
beforeEach(() => {
- createComponent({
- events,
- });
+ createComponent(
+ {
+ events,
+ },
+ isInheriting,
+ );
});
it('renders GlFormCheckbox for each event', () => {
expect(findAllGlFormCheckboxes()).toHaveLength(2);
});
- it('renders GlFormInput for each event', () => {
+ it(`renders GlFormInput for each event, which ${
+ isInheriting ? 'is' : 'is not'
+ } readonly`, () => {
const fields = findAllGlFormInputs();
const expectedResults = [
{
@@ -128,6 +150,7 @@ describe('TriggerFields', () => {
fields.wrappers.forEach((field, index) => {
expect(field.attributes()).toMatchObject(expectedResults[index]);
+ expect(field.vm.$attrs.readonly).toBe(isInheriting);
expect(field.vm.$attrs.value).toBe(events[index].field.value);
});
});
diff --git a/spec/frontend/integrations/edit/mock_data.js b/spec/frontend/integrations/edit/mock_data.js
new file mode 100644
index 00000000000..da2758ec15c
--- /dev/null
+++ b/spec/frontend/integrations/edit/mock_data.js
@@ -0,0 +1,18 @@
+// eslint-disable-next-line import/prefer-default-export
+export const mockIntegrationProps = {
+ id: 25,
+ activeToggleProps: {
+ initialActivated: true,
+ },
+ showActive: true,
+ triggerFieldsProps: {
+ initialTriggerCommit: false,
+ initialTriggerMergeRequest: false,
+ initialEnableComments: false,
+ },
+ jiraIssuesProps: {},
+ triggerEvents: [],
+ fields: [],
+ type: '',
+ inheritFromId: 25,
+};
diff --git a/spec/frontend/integrations/edit/store/actions_spec.js b/spec/frontend/integrations/edit/store/actions_spec.js
new file mode 100644
index 00000000000..c3ce6e51a3d
--- /dev/null
+++ b/spec/frontend/integrations/edit/store/actions_spec.js
@@ -0,0 +1,19 @@
+import createState from '~/integrations/edit/store/state';
+import { setOverride } from '~/integrations/edit/store/actions';
+import * as types from '~/integrations/edit/store/mutation_types';
+
+import testAction from 'helpers/vuex_action_helper';
+
+describe('Integration form store actions', () => {
+ let state;
+
+ beforeEach(() => {
+ state = createState();
+ });
+
+ describe('setOverride', () => {
+ it('should commit override mutation', () => {
+ return testAction(setOverride, true, state, [{ type: types.SET_OVERRIDE, payload: true }]);
+ });
+ });
+});
diff --git a/spec/frontend/integrations/edit/store/getters_spec.js b/spec/frontend/integrations/edit/store/getters_spec.js
new file mode 100644
index 00000000000..700d36edaad
--- /dev/null
+++ b/spec/frontend/integrations/edit/store/getters_spec.js
@@ -0,0 +1,71 @@
+import { currentKey, isInheriting, propsSource } from '~/integrations/edit/store/getters';
+import createState from '~/integrations/edit/store/state';
+import { mockIntegrationProps } from '../mock_data';
+
+describe('Integration form store getters', () => {
+ let state;
+ const customState = { ...mockIntegrationProps, type: 'CustomState' };
+ const adminState = { ...mockIntegrationProps, type: 'AdminState' };
+
+ beforeEach(() => {
+ state = createState({ customState });
+ });
+
+ describe('isInheriting', () => {
+ describe('when adminState is null', () => {
+ it('returns false', () => {
+ expect(isInheriting(state)).toBe(false);
+ });
+ });
+
+ describe('when adminState is an object', () => {
+ beforeEach(() => {
+ state.adminState = adminState;
+ });
+
+ describe('when override is false', () => {
+ beforeEach(() => {
+ state.override = false;
+ });
+
+ it('returns false', () => {
+ expect(isInheriting(state)).toBe(true);
+ });
+ });
+
+ describe('when override is true', () => {
+ beforeEach(() => {
+ state.override = true;
+ });
+
+ it('returns true', () => {
+ expect(isInheriting(state)).toBe(false);
+ });
+ });
+ });
+ });
+
+ describe('propsSource', () => {
+ beforeEach(() => {
+ state.adminState = adminState;
+ });
+
+ it('equals adminState if inheriting', () => {
+ expect(propsSource(state, { isInheriting: true })).toEqual(adminState);
+ });
+
+ it('equals customState if not inheriting', () => {
+ expect(propsSource(state, { isInheriting: false })).toEqual(customState);
+ });
+ });
+
+ describe('currentKey', () => {
+ it('equals `admin` if inheriting', () => {
+ expect(currentKey(state, { isInheriting: true })).toEqual('admin');
+ });
+
+ it('equals `custom` if not inheriting', () => {
+ expect(currentKey(state, { isInheriting: false })).toEqual('custom');
+ });
+ });
+});
diff --git a/spec/frontend/integrations/edit/store/mutations_spec.js b/spec/frontend/integrations/edit/store/mutations_spec.js
new file mode 100644
index 00000000000..4b733726d44
--- /dev/null
+++ b/spec/frontend/integrations/edit/store/mutations_spec.js
@@ -0,0 +1,19 @@
+import mutations from '~/integrations/edit/store/mutations';
+import createState from '~/integrations/edit/store/state';
+import * as types from '~/integrations/edit/store/mutation_types';
+
+describe('Integration form store mutations', () => {
+ let state;
+
+ beforeEach(() => {
+ state = createState();
+ });
+
+ describe(`${types.SET_OVERRIDE}`, () => {
+ it('sets override', () => {
+ mutations[types.SET_OVERRIDE](state, true);
+
+ expect(state.override).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/integrations/edit/store/state_spec.js b/spec/frontend/integrations/edit/store/state_spec.js
new file mode 100644
index 00000000000..a8b431aa310
--- /dev/null
+++ b/spec/frontend/integrations/edit/store/state_spec.js
@@ -0,0 +1,26 @@
+import createState from '~/integrations/edit/store/state';
+
+describe('Integration form state factory', () => {
+ it('states default to null', () => {
+ expect(createState()).toEqual({
+ adminState: null,
+ customState: {},
+ override: false,
+ });
+ });
+
+ describe('override is initialized correctly', () => {
+ it.each([
+ [{ id: 25 }, { inheritFromId: null }, true],
+ [{ id: 25 }, { inheritFromId: 27 }, true],
+ [{ id: 25 }, { inheritFromId: 25 }, false],
+ [null, { inheritFromId: null }, false],
+ [null, { inheritFromId: 25 }, false],
+ ])(
+ 'for adminState: %p, customState: %p: override = `%p`',
+ (adminState, customState, expected) => {
+ expect(createState({ adminState, customState }).override).toEqual(expected);
+ },
+ );
+ });
+});
diff --git a/spec/frontend/issuable_suggestions/components/app_spec.js b/spec/frontend/issuable_suggestions/components/app_spec.js
index 20930be8667..d51c89807be 100644
--- a/spec/frontend/issuable_suggestions/components/app_spec.js
+++ b/spec/frontend/issuable_suggestions/components/app_spec.js
@@ -89,7 +89,7 @@ describe('Issuable suggestions app component', () => {
wrapper
.findAll('li')
.at(0)
- .is('.append-bottom-default'),
+ .is('.gl-mb-3'),
).toBe(true);
});
});
@@ -102,7 +102,7 @@ describe('Issuable suggestions app component', () => {
wrapper
.findAll('li')
.at(1)
- .is('.append-bottom-default'),
+ .is('.gl-mb-3'),
).toBe(false);
});
});
diff --git a/spec/frontend/issuable_suggestions/components/item_spec.js b/spec/frontend/issuable_suggestions/components/item_spec.js
index 6c3c30fcbb0..36799f4ee9f 100644
--- a/spec/frontend/issuable_suggestions/components/item_spec.js
+++ b/spec/frontend/issuable_suggestions/components/item_spec.js
@@ -4,6 +4,7 @@ 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';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('Issuable suggestions suggestion component', () => {
let vm;
@@ -34,7 +35,7 @@ describe('Issuable suggestions suggestion component', () => {
const link = vm.find(GlLink);
- expect(link.attributes('href')).toBe(`${gl.TEST_HOST}/test/issue/1`);
+ expect(link.attributes('href')).toBe(`${TEST_HOST}/test/issue/1`);
});
it('renders IID', () => {
@@ -100,7 +101,7 @@ describe('Issuable suggestions suggestion component', () => {
const image = vm.find(UserAvatarImage);
- expect(image.props('imgSrc')).toBe(`${gl.TEST_HOST}/avatar`);
+ expect(image.props('imgSrc')).toBe(`${TEST_HOST}/avatar`);
});
});
diff --git a/spec/frontend/issuable_suggestions/mock_data.js b/spec/frontend/issuable_suggestions/mock_data.js
index 4f0f9ef8d62..4ce35ad5196 100644
--- a/spec/frontend/issuable_suggestions/mock_data.js
+++ b/spec/frontend/issuable_suggestions/mock_data.js
@@ -1,3 +1,5 @@
+import { TEST_HOST } from 'jest/helpers/test_constants';
+
function getDate(daysMinus) {
const today = new Date();
today.setDate(today.getDate() - daysMinus);
@@ -15,12 +17,12 @@ export default () => ({
createdAt: getDate(3),
updatedAt: getDate(2),
confidential: false,
- webUrl: `${gl.TEST_HOST}/test/issue/1`,
+ webUrl: `${TEST_HOST}/test/issue/1`,
title: 'Test issue',
author: {
- avatarUrl: `${gl.TEST_HOST}/avatar`,
+ avatarUrl: `${TEST_HOST}/avatar`,
name: 'Author Name',
username: 'author.username',
- webUrl: `${gl.TEST_HOST}/author`,
+ webUrl: `${TEST_HOST}/author`,
},
});
diff --git a/spec/frontend/issuables_list/components/issuable_list_root_app_spec.js b/spec/frontend/issuables_list/components/issuable_list_root_app_spec.js
index 899010bdb0f..aee49076b5d 100644
--- a/spec/frontend/issuables_list/components/issuable_list_root_app_spec.js
+++ b/spec/frontend/issuables_list/components/issuable_list_root_app_spec.js
@@ -16,10 +16,8 @@ describe('IssuableListRootApp', () => {
const findAlertLabel = () => wrapper.find(GlAlert).find(GlLabel);
const mountComponent = ({
- isFinishedAlertShowing = false,
- isInProgressAlertShowing = false,
- isInProgress = false,
- isFinished = false,
+ shouldShowFinishedAlert = false,
+ shouldShowInProgressAlert = false,
} = {}) =>
shallowMount(IssuableListRootApp, {
propsData: {
@@ -30,12 +28,11 @@ describe('IssuableListRootApp', () => {
},
data() {
return {
- isFinishedAlertShowing,
- isInProgressAlertShowing,
jiraImport: {
- isInProgress,
- isFinished,
+ importedIssuesCount: 1,
label,
+ shouldShowFinishedAlert,
+ shouldShowInProgressAlert,
},
};
},
@@ -57,8 +54,7 @@ describe('IssuableListRootApp', () => {
describe('when Jira import is in progress', () => {
it('shows an alert that tells the user a Jira import is in progress', () => {
wrapper = mountComponent({
- isInProgressAlertShowing: true,
- isInProgress: true,
+ shouldShowInProgressAlert: true,
});
expect(findAlert().text()).toBe(
@@ -70,14 +66,13 @@ describe('IssuableListRootApp', () => {
describe('when Jira import has finished', () => {
beforeEach(() => {
wrapper = mountComponent({
- isFinishedAlertShowing: true,
- isFinished: true,
+ shouldShowFinishedAlert: true,
});
});
describe('shows an alert', () => {
it('tells the user the Jira import has finished', () => {
- expect(findAlert().text()).toBe('Issues successfully imported with the label');
+ expect(findAlert().text()).toBe('1 issue successfully imported with the label');
});
it('contains the label title associated with the Jira import', () => {
@@ -105,8 +100,7 @@ describe('IssuableListRootApp', () => {
describe('alert message', () => {
it('is hidden when dismissed', () => {
wrapper = mountComponent({
- isInProgressAlertShowing: true,
- isInProgress: true,
+ shouldShowInProgressAlert: true,
});
expect(wrapper.contains(GlAlert)).toBe(true);
diff --git a/spec/frontend/issuables_list/components/issuable_spec.js b/spec/frontend/issuables_list/components/issuable_spec.js
index 834d18246a5..87868b7eeff 100644
--- a/spec/frontend/issuables_list/components/issuable_spec.js
+++ b/spec/frontend/issuables_list/components/issuable_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import { GlSprintf } from '@gitlab/ui';
+import { GlSprintf, GlLabel, GlIcon } from '@gitlab/ui';
import { TEST_HOST } from 'helpers/test_constants';
import { trimText } from 'helpers/text_helper';
import initUserPopovers from '~/user_popovers';
@@ -8,6 +8,7 @@ import { mergeUrlParams } from '~/lib/utils/url_utility';
import Issuable from '~/issuables_list/components/issuable.vue';
import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
import { simpleIssue, testAssignees, testLabels } from '../issuable_list_test_data';
+import { isScopedLabel } from '~/lib/utils/common_utils';
jest.mock('~/user_popovers');
@@ -37,13 +38,18 @@ describe('Issuable component', () => {
let DateOrig;
let wrapper;
- const factory = (props = {}) => {
+ const factory = (props = {}, scopedLabels = false) => {
wrapper = shallowMount(Issuable, {
propsData: {
issuable: simpleIssue,
baseUrl: TEST_BASE_URL,
...props,
},
+ provide: {
+ glFeatures: {
+ scopedLabels,
+ },
+ },
stubs: {
'gl-sprintf': GlSprintf,
'gl-link': '<a><slot></slot></a>',
@@ -57,6 +63,7 @@ describe('Issuable component', () => {
afterEach(() => {
wrapper.destroy();
+ wrapper = null;
});
beforeAll(() => {
@@ -68,14 +75,16 @@ describe('Issuable component', () => {
window.Date = DateOrig;
});
- const findConfidentialIcon = () => wrapper.find('.fa-eye-slash');
+ const checkExists = findFn => () => findFn().exists();
+ const hasConfidentialIcon = () =>
+ wrapper.findAll(GlIcon).wrappers.some(iconWrapper => iconWrapper.props('name') === 'eye-slash');
const findTaskStatus = () => wrapper.find('.task-status');
const findOpenedAgoContainer = () => wrapper.find('[data-testid="openedByMessage"]');
+ const findAuthor = () => wrapper.find({ ref: 'openedAgoByContainer' });
const findMilestone = () => wrapper.find('.js-milestone');
const findMilestoneTooltip = () => findMilestone().attributes('title');
const findDueDate = () => wrapper.find('.js-due-date');
- const findLabelContainer = () => wrapper.find('.js-labels');
- const findLabelLinks = () => findLabelContainer().findAll('a');
+ const findLabels = () => wrapper.findAll(GlLabel);
const findWeight = () => wrapper.find('.js-weight');
const findAssignees = () => wrapper.find(IssueAssignees);
const findMergeRequestsCount = () => wrapper.find('.js-merge-requests');
@@ -83,6 +92,11 @@ describe('Issuable component', () => {
const findDownvotes = () => wrapper.find('.js-downvotes');
const findNotes = () => wrapper.find('.js-notes');
const findBulkCheckbox = () => wrapper.find('input.selected-issuable');
+ const findScopedLabels = () => findLabels().filter(w => isScopedLabel({ title: w.text() }));
+ const findUnscopedLabels = () => findLabels().filter(w => !isScopedLabel({ title: w.text() }));
+ const findIssuableTitle = () => wrapper.find('[data-testid="issuable-title"]');
+ const findIssuableStatus = () => wrapper.find('[data-testid="issuable-status"]');
+ const containsJiraLogo = () => wrapper.contains('[data-testid="jira-logo"]');
describe('when mounted', () => {
it('initializes user popovers', () => {
@@ -94,6 +108,54 @@ describe('Issuable component', () => {
});
});
+ describe('when scopedLabels feature is available', () => {
+ beforeEach(() => {
+ issuable.labels = [...testLabels];
+
+ factory({ issuable }, true);
+ });
+
+ describe('when label is scoped', () => {
+ it('returns label with correct props', () => {
+ const scopedLabel = findScopedLabels().at(0);
+
+ expect(scopedLabel.props('scoped')).toBe(true);
+ });
+ });
+
+ describe('when label is not scoped', () => {
+ it('returns label with correct props', () => {
+ const notScopedLabel = findUnscopedLabels().at(0);
+
+ expect(notScopedLabel.props('scoped')).toBe(false);
+ });
+ });
+ });
+
+ describe('when scopedLabels feature is not available', () => {
+ beforeEach(() => {
+ issuable.labels = [...testLabels];
+
+ factory({ issuable });
+ });
+
+ describe('when label is scoped', () => {
+ it('label scoped props is false', () => {
+ const scopedLabel = findScopedLabels().at(0);
+
+ expect(scopedLabel.props('scoped')).toBe(false);
+ });
+ });
+
+ describe('when label is not scoped', () => {
+ it('label scoped props is false', () => {
+ const notScopedLabel = findUnscopedLabels().at(0);
+
+ expect(notScopedLabel.props('scoped')).toBe(false);
+ });
+ });
+ });
+
describe('with simple issuable', () => {
beforeEach(() => {
Object.assign(issuable, {
@@ -111,19 +173,19 @@ describe('Issuable component', () => {
});
it.each`
- desc | finder
- ${'bulk editing checkbox'} | ${findBulkCheckbox}
- ${'confidential icon'} | ${findConfidentialIcon}
- ${'task status'} | ${findTaskStatus}
- ${'milestone'} | ${findMilestone}
- ${'due date'} | ${findDueDate}
- ${'labels'} | ${findLabelContainer}
- ${'weight'} | ${findWeight}
- ${'merge request count'} | ${findMergeRequestsCount}
- ${'upvotes'} | ${findUpvotes}
- ${'downvotes'} | ${findDownvotes}
- `('does not render $desc', ({ finder }) => {
- expect(finder().exists()).toBe(false);
+ desc | check
+ ${'bulk editing checkbox'} | ${checkExists(findBulkCheckbox)}
+ ${'confidential icon'} | ${hasConfidentialIcon}
+ ${'task status'} | ${checkExists(findTaskStatus)}
+ ${'milestone'} | ${checkExists(findMilestone)}
+ ${'due date'} | ${checkExists(findDueDate)}
+ ${'labels'} | ${checkExists(findLabels)}
+ ${'weight'} | ${checkExists(findWeight)}
+ ${'merge request count'} | ${checkExists(findMergeRequestsCount)}
+ ${'upvotes'} | ${checkExists(findUpvotes)}
+ ${'downvotes'} | ${checkExists(findDownvotes)}
+ `('does not render $desc', ({ check }) => {
+ expect(check()).toBe(false);
});
it('show relative reference path', () => {
@@ -157,7 +219,41 @@ describe('Issuable component', () => {
});
it('renders the confidential icon', () => {
- expect(findConfidentialIcon().exists()).toBe(true);
+ expect(hasConfidentialIcon()).toBe(true);
+ });
+ });
+
+ describe('with Jira issuable', () => {
+ beforeEach(() => {
+ issuable.external_tracker = 'jira';
+
+ factory({ issuable });
+ });
+
+ it('renders the Jira icon', () => {
+ expect(containsJiraLogo()).toBe(true);
+ });
+
+ it('opens issuable in a new tab', () => {
+ expect(findIssuableTitle().props('target')).toBe('_blank');
+ });
+
+ it('opens author in a new tab', () => {
+ expect(findAuthor().props('target')).toBe('_blank');
+ });
+
+ describe('with Jira status', () => {
+ const expectedStatus = 'In Progress';
+
+ beforeEach(() => {
+ issuable.status = expectedStatus;
+
+ factory({ issuable });
+ });
+
+ it('renders the Jira status', () => {
+ expect(findIssuableStatus().text()).toBe(expectedStatus);
+ });
});
});
@@ -243,10 +339,10 @@ describe('Issuable component', () => {
it('renders labels', () => {
factory({ issuable });
- const labels = findLabelLinks().wrappers.map(label => ({
- href: label.attributes('href'),
+ const labels = findLabels().wrappers.map(label => ({
+ href: label.props('target'),
text: label.text(),
- tooltip: label.find('span').attributes('title'),
+ tooltip: label.attributes('description'),
}));
const expected = testLabels.map(label => ({
@@ -259,6 +355,33 @@ describe('Issuable component', () => {
});
});
+ describe('with labels for Jira issuable', () => {
+ beforeEach(() => {
+ issuable.labels = [...testLabels];
+ issuable.external_tracker = 'jira';
+
+ factory({ issuable });
+ });
+
+ it('renders labels', () => {
+ factory({ issuable });
+
+ const labels = findLabels().wrappers.map(label => ({
+ href: label.props('target'),
+ text: label.text(),
+ tooltip: label.attributes('description'),
+ }));
+
+ const expected = testLabels.map(label => ({
+ href: mergeUrlParams({ 'labels[]': label.name }, TEST_BASE_URL),
+ text: label.name,
+ tooltip: label.description,
+ }));
+
+ expect(labels).toEqual(expected);
+ });
+ });
+
describe.each`
weight
${0}
diff --git a/spec/frontend/issuables_list/components/issuables_list_app_spec.js b/spec/frontend/issuables_list/components/issuables_list_app_spec.js
index 6b680af354e..9f4995a54ee 100644
--- a/spec/frontend/issuables_list/components/issuables_list_app_spec.js
+++ b/spec/frontend/issuables_list/components/issuables_list_app_spec.js
@@ -7,6 +7,7 @@ import { TEST_HOST } from 'helpers/test_constants';
import flash from '~/flash';
import IssuablesListApp from '~/issuables_list/components/issuables_list_app.vue';
import Issuable from '~/issuables_list/components/issuable.vue';
+import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import issueablesEventBus from '~/issuables_list/eventhub';
import { PAGE_SIZE, PAGE_SIZE_MANUAL, RELATIVE_POSITION } from '~/issuables_list/constants';
@@ -59,6 +60,7 @@ describe('Issuables list component', () => {
const findLoading = () => wrapper.find(GlSkeletonLoading);
const findIssuables = () => wrapper.findAll(Issuable);
+ const findFilteredSearchBar = () => wrapper.find(FilteredSearchBar);
const findFirstIssuable = () => findIssuables().wrappers[0];
const findEmptyState = () => wrapper.find(GlEmptyState);
@@ -75,6 +77,7 @@ describe('Issuables list component', () => {
afterEach(() => {
wrapper.destroy();
+ wrapper = null;
mockAxios.restore();
window.location = oldLocation;
});
@@ -131,6 +134,7 @@ describe('Issuables list component', () => {
});
it('does not call API until mounted', () => {
+ factory();
expect(apiSpy).not.toHaveBeenCalled();
});
@@ -173,6 +177,12 @@ describe('Issuables list component', () => {
expect(wrapper.find(GlPagination).exists()).toBe(true);
});
});
+
+ it('does not render FilteredSearchBar', () => {
+ factory();
+
+ expect(findFilteredSearchBar().exists()).toBe(false);
+ });
});
describe('with bulk editing enabled', () => {
@@ -293,7 +303,7 @@ describe('Issuables list component', () => {
describe('when page is not present in params', () => {
const query =
- '?assignee_username=root&author_username=root&confidential=yes&label_name%5B%5D=Aquapod&label_name%5B%5D=Astro&milestone_title=v3.0&my_reaction_emoji=airplane&scope=all&sort=priority&state=opened&utf8=%E2%9C%93&weight=0';
+ '?assignee_username=root&author_username=root&confidential=yes&label_name%5B%5D=Aquapod&label_name%5B%5D=Astro&milestone_title=v3.0&my_reaction_emoji=airplane&scope=all&sort=priority&state=opened&utf8=%E2%9C%93&weight=0&not[label_name][]=Afterpod&not[milestone_title][]=13';
beforeEach(() => {
setUrl(query);
@@ -310,7 +320,11 @@ describe('Issuables list component', () => {
it('applies filters and sorts', () => {
expect(wrapper.vm.hasFilters).toBe(true);
- expect(wrapper.vm.filters).toEqual(expectedFilters);
+ expect(wrapper.vm.filters).toEqual({
+ ...expectedFilters,
+ 'not[milestone]': ['13'],
+ 'not[labels]': ['Afterpod'],
+ });
expect(apiSpy).toHaveBeenCalledWith(
expect.objectContaining({
@@ -319,6 +333,8 @@ describe('Issuables list component', () => {
with_labels_details: true,
page: 1,
per_page: PAGE_SIZE,
+ 'not[milestone]': ['13'],
+ 'not[labels]': ['Afterpod'],
},
}),
);
@@ -454,43 +470,117 @@ describe('Issuables list component', () => {
describe('when paginates', () => {
const newPage = 3;
- beforeEach(() => {
- window.history.pushState = jest.fn();
- setupApiMock(() => [
- 200,
- MOCK_ISSUES.slice(0, PAGE_SIZE),
- {
- 'x-total': 100,
- 'x-page': 2,
- },
- ]);
+ describe('when total-items is defined in response headers', () => {
+ beforeEach(() => {
+ window.history.pushState = jest.fn();
+ setupApiMock(() => [
+ 200,
+ MOCK_ISSUES.slice(0, PAGE_SIZE),
+ {
+ 'x-total': 100,
+ 'x-page': 2,
+ },
+ ]);
- factory();
+ factory();
- return waitForPromises();
+ return waitForPromises();
+ });
+
+ afterEach(() => {
+ // reset to original value
+ window.history.pushState.mockRestore();
+ });
+
+ it('calls window.history.pushState one time', () => {
+ // Trigger pagination
+ wrapper.find(GlPagination).vm.$emit('input', newPage);
+
+ expect(window.history.pushState).toHaveBeenCalledTimes(1);
+ });
+
+ it('sets params in the url', () => {
+ // Trigger pagination
+ wrapper.find(GlPagination).vm.$emit('input', newPage);
+
+ expect(window.history.pushState).toHaveBeenCalledWith(
+ {},
+ '',
+ `${TEST_LOCATION}?state=opened&order_by=priority&sort=asc&page=${newPage}`,
+ );
+ });
});
- afterEach(() => {
- // reset to original value
- window.history.pushState.mockRestore();
+ describe('when total-items is not defined in the headers', () => {
+ const page = 2;
+ const prevPage = page - 1;
+ const nextPage = page + 1;
+
+ beforeEach(() => {
+ setupApiMock(() => [
+ 200,
+ MOCK_ISSUES.slice(0, PAGE_SIZE),
+ {
+ 'x-page': page,
+ },
+ ]);
+
+ factory();
+
+ return waitForPromises();
+ });
+
+ it('finds the correct props applied to GlPagination', () => {
+ expect(wrapper.find(GlPagination).props()).toMatchObject({
+ nextPage,
+ prevPage,
+ value: page,
+ });
+ });
});
+ });
- it('calls window.history.pushState one time', () => {
- // Trigger pagination
- wrapper.find(GlPagination).vm.$emit('input', newPage);
+ describe('when type is "jira"', () => {
+ it('renders FilteredSearchBar', () => {
+ factory({ type: 'jira' });
- expect(window.history.pushState).toHaveBeenCalledTimes(1);
+ expect(findFilteredSearchBar().exists()).toBe(true);
});
- it('sets params in the url', () => {
- // Trigger pagination
- wrapper.find(GlPagination).vm.$emit('input', newPage);
+ describe('initialSortBy', () => {
+ const query = '?sort=updated_asc';
- expect(window.history.pushState).toHaveBeenCalledWith(
- {},
- '',
- `${TEST_LOCATION}?state=opened&order_by=priority&sort=asc&page=${newPage}`,
- );
+ it('sets default value', () => {
+ factory({ type: 'jira' });
+
+ expect(findFilteredSearchBar().props('initialSortBy')).toBe('created_desc');
+ });
+
+ it('sets value according to query', () => {
+ setUrl(query);
+
+ factory({ type: 'jira' });
+
+ expect(findFilteredSearchBar().props('initialSortBy')).toBe('updated_asc');
+ });
+ });
+
+ describe('initialFilterValue', () => {
+ it('does not set value when no query', () => {
+ factory({ type: 'jira' });
+
+ expect(findFilteredSearchBar().props('initialFilterValue')).toEqual([]);
+ });
+
+ it('sets value according to query', () => {
+ const query = '?search=free+text';
+
+ setUrl(query);
+
+ factory({ type: 'jira' });
+
+ expect(findFilteredSearchBar().props('initialFilterValue')).toEqual(['free text']);
+ });
});
});
});
diff --git a/spec/frontend/issue_show/components/issuable_header_warnings_spec.js b/spec/frontend/issue_show/components/issuable_header_warnings_spec.js
new file mode 100644
index 00000000000..5a166812d84
--- /dev/null
+++ b/spec/frontend/issue_show/components/issuable_header_warnings_spec.js
@@ -0,0 +1,79 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import IssuableHeaderWarnings from '~/issue_show/components/issuable_header_warnings.vue';
+import createStore from '~/notes/stores';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('IssuableHeaderWarnings', () => {
+ let wrapper;
+ let store;
+
+ const findConfidential = () => wrapper.find('[data-testid="confidential"]');
+ const findLocked = () => wrapper.find('[data-testid="locked"]');
+ const confidentialIconName = () => findConfidential().attributes('name');
+ const lockedIconName = () => findLocked().attributes('name');
+
+ const createComponent = () => {
+ wrapper = shallowMount(IssuableHeaderWarnings, { store, localVue });
+ };
+
+ beforeEach(() => {
+ store = createStore();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ store = null;
+ });
+
+ describe('when confidential is on', () => {
+ beforeEach(() => {
+ store.state.noteableData.confidential = true;
+
+ createComponent();
+ });
+
+ it('renders the confidential icon', () => {
+ expect(confidentialIconName()).toBe('eye-slash');
+ });
+ });
+
+ describe('when confidential is off', () => {
+ beforeEach(() => {
+ store.state.noteableData.confidential = false;
+
+ createComponent();
+ });
+
+ it('does not find the component', () => {
+ expect(findConfidential().exists()).toBe(false);
+ });
+ });
+
+ describe('when discussion locked is on', () => {
+ beforeEach(() => {
+ store.state.noteableData.discussion_locked = true;
+
+ createComponent();
+ });
+
+ it('renders the locked icon', () => {
+ expect(lockedIconName()).toBe('lock');
+ });
+ });
+
+ describe('when discussion locked is off', () => {
+ beforeEach(() => {
+ store.state.noteableData.discussion_locked = false;
+
+ createComponent();
+ });
+
+ it('does not find the component', () => {
+ expect(findLocked().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/issue_show/components/pinned_links_spec.js b/spec/frontend/issue_show/components/pinned_links_spec.js
index 007ad4c9a1b..bb67feee601 100644
--- a/spec/frontend/issue_show/components/pinned_links_spec.js
+++ b/spec/frontend/issue_show/components/pinned_links_spec.js
@@ -1,6 +1,7 @@
import { shallowMount } from '@vue/test-utils';
-import { GlLink } from '@gitlab/ui';
+import { GlButton } from '@gitlab/ui';
import PinnedLinks from '~/issue_show/components/pinned_links.vue';
+import { STATUS_PAGE_PUBLISHED, JOIN_ZOOM_MEETING } from '~/issue_show/constants';
const plainZoomUrl = 'https://zoom.us/j/123456789';
const plainStatusUrl = 'https://status.com';
@@ -8,7 +9,7 @@ const plainStatusUrl = 'https://status.com';
describe('PinnedLinks', () => {
let wrapper;
- const findLinks = () => wrapper.findAll(GlLink);
+ const findButtons = () => wrapper.findAll(GlButton);
const createComponent = props => {
wrapper = shallowMount(PinnedLinks, {
@@ -26,10 +27,10 @@ describe('PinnedLinks', () => {
});
expect(
- findLinks()
+ findButtons()
.at(0)
.text(),
- ).toBe('Join Zoom meeting');
+ ).toBe(JOIN_ZOOM_MEETING);
});
it('displays Status link', () => {
@@ -38,10 +39,10 @@ describe('PinnedLinks', () => {
});
expect(
- findLinks()
+ findButtons()
.at(0)
.text(),
- ).toBe('Published on status page');
+ ).toBe(STATUS_PAGE_PUBLISHED);
});
it('does not render if there are no links', () => {
@@ -50,6 +51,6 @@ describe('PinnedLinks', () => {
publishedIncidentUrl: '',
});
- expect(wrapper.find(GlLink).exists()).toBe(false);
+ expect(findButtons()).toHaveLength(0);
});
});
diff --git a/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap b/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap
new file mode 100644
index 00000000000..975c31bb59c
--- /dev/null
+++ b/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap
@@ -0,0 +1,277 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`JiraImportForm table body shows correct information in each cell 1`] = `
+<table
+ aria-busy="false"
+ aria-colcount="3"
+ class="table b-table gl-table b-table-fixed"
+ role="table"
+>
+ <!---->
+ <!---->
+ <thead
+ class=""
+ role="rowgroup"
+ >
+ <!---->
+ <tr
+ class=""
+ role="row"
+ >
+ <th
+ aria-colindex="1"
+ class=""
+ role="columnheader"
+ scope="col"
+ >
+ Jira display name
+ </th>
+ <th
+ aria-colindex="2"
+ aria-label="Arrow"
+ class=""
+ role="columnheader"
+ scope="col"
+ />
+ <th
+ aria-colindex="3"
+ class=""
+ role="columnheader"
+ scope="col"
+ >
+ GitLab username
+ </th>
+ </tr>
+ </thead>
+ <tbody
+ role="rowgroup"
+ >
+ <!---->
+ <tr
+ class=""
+ role="row"
+ >
+ <td
+ aria-colindex="1"
+ class=""
+ role="cell"
+ >
+ Jane Doe
+ </td>
+ <td
+ aria-colindex="2"
+ class=""
+ role="cell"
+ >
+ <svg
+ aria-label="Will be mapped to"
+ class="gl-icon s16"
+ data-testid="arrow-right-icon"
+ >
+ <use
+ href="#arrow-right"
+ />
+ </svg>
+ </td>
+ <td
+ aria-colindex="3"
+ class=""
+ role="cell"
+ >
+ <div
+ aria-label="The GitLab user to which the Jira user Jane Doe will be mapped"
+ class="dropdown b-dropdown gl-new-dropdown w-100 btn-group"
+ >
+ <!---->
+ <button
+ aria-expanded="false"
+ aria-haspopup="true"
+ class="btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle"
+ type="button"
+ >
+ <!---->
+
+ <span
+ class="gl-new-dropdown-button-text"
+ >
+ janedoe
+ </span>
+
+ <svg
+ class="dropdown-chevron gl-icon s16"
+ data-testid="chevron-down-icon"
+ >
+ <use
+ href="#chevron-down"
+ />
+ </svg>
+ </button>
+ <ul
+ class="dropdown-menu"
+ role="menu"
+ tabindex="-1"
+ >
+ <!---->
+
+ <div
+ class="gl-search-box-by-type m-2"
+ >
+ <svg
+ class="gl-search-box-by-type-search-icon gl-icon s16"
+ data-testid="search-icon"
+ >
+ <use
+ href="#search"
+ />
+ </svg>
+
+ <input
+ aria-label="Search"
+ class="gl-form-input gl-search-box-by-type-input form-control"
+ placeholder="Search"
+ type="text"
+ />
+
+ <div
+ class="gl-search-box-by-type-right-icons"
+ >
+ <!---->
+
+ <!---->
+ </div>
+ </div>
+
+ <li
+ class="gl-new-dropdown-text text-secondary"
+ role="presentation"
+ >
+ <p
+ class="b-dropdown-text"
+ >
+
+ No matches found
+
+ </p>
+ </li>
+
+ </ul>
+ </div>
+ </td>
+ </tr>
+ <tr
+ class=""
+ role="row"
+ >
+ <td
+ aria-colindex="1"
+ class=""
+ role="cell"
+ >
+ Fred Chopin
+ </td>
+ <td
+ aria-colindex="2"
+ class=""
+ role="cell"
+ >
+ <svg
+ aria-label="Will be mapped to"
+ class="gl-icon s16"
+ data-testid="arrow-right-icon"
+ >
+ <use
+ href="#arrow-right"
+ />
+ </svg>
+ </td>
+ <td
+ aria-colindex="3"
+ class=""
+ role="cell"
+ >
+ <div
+ aria-label="The GitLab user to which the Jira user Fred Chopin will be mapped"
+ class="dropdown b-dropdown gl-new-dropdown w-100 btn-group"
+ >
+ <!---->
+ <button
+ aria-expanded="false"
+ aria-haspopup="true"
+ class="btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle"
+ type="button"
+ >
+ <!---->
+
+ <span
+ class="gl-new-dropdown-button-text"
+ >
+ mrgitlab
+ </span>
+
+ <svg
+ class="dropdown-chevron gl-icon s16"
+ data-testid="chevron-down-icon"
+ >
+ <use
+ href="#chevron-down"
+ />
+ </svg>
+ </button>
+ <ul
+ class="dropdown-menu"
+ role="menu"
+ tabindex="-1"
+ >
+ <!---->
+
+ <div
+ class="gl-search-box-by-type m-2"
+ >
+ <svg
+ class="gl-search-box-by-type-search-icon gl-icon s16"
+ data-testid="search-icon"
+ >
+ <use
+ href="#search"
+ />
+ </svg>
+
+ <input
+ aria-label="Search"
+ class="gl-form-input gl-search-box-by-type-input form-control"
+ placeholder="Search"
+ type="text"
+ />
+
+ <div
+ class="gl-search-box-by-type-right-icons"
+ >
+ <!---->
+
+ <!---->
+ </div>
+ </div>
+
+ <li
+ class="gl-new-dropdown-text text-secondary"
+ role="presentation"
+ >
+ <p
+ class="b-dropdown-text"
+ >
+
+ No matches found
+
+ </p>
+ </li>
+
+ </ul>
+ </div>
+ </td>
+ </tr>
+ <!---->
+ <!---->
+ </tbody>
+ <!---->
+</table>
+`;
diff --git a/spec/frontend/jira_import/components/jira_import_app_spec.js b/spec/frontend/jira_import/components/jira_import_app_spec.js
index a21b89f6517..074f9842512 100644
--- a/spec/frontend/jira_import/components/jira_import_app_spec.js
+++ b/spec/frontend/jira_import/components/jira_import_app_spec.js
@@ -1,88 +1,19 @@
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
+import AxiosMockAdapter from 'axios-mock-adapter';
import Vue from 'vue';
+import axios from '~/lib/utils/axios_utils';
import JiraImportApp from '~/jira_import/components/jira_import_app.vue';
import JiraImportForm from '~/jira_import/components/jira_import_form.vue';
import JiraImportProgress from '~/jira_import/components/jira_import_progress.vue';
import JiraImportSetup from '~/jira_import/components/jira_import_setup.vue';
import initiateJiraImportMutation from '~/jira_import/queries/initiate_jira_import.mutation.graphql';
-
-const mountComponent = ({
- isJiraConfigured = true,
- errorMessage = '',
- selectedProject = 'MTG',
- showAlert = false,
- isInProgress = false,
- loading = false,
- mutate = jest.fn(() => Promise.resolve()),
- mountType,
-} = {}) => {
- const mountFunction = mountType === 'mount' ? mount : shallowMount;
-
- return mountFunction(JiraImportApp, {
- propsData: {
- inProgressIllustration: 'in-progress-illustration.svg',
- isJiraConfigured,
- issuesPath: 'gitlab-org/gitlab-test/-/issues',
- jiraIntegrationPath: 'gitlab-org/gitlab-test/-/services/jira/edit',
- projectPath: 'gitlab-org/gitlab-test',
- setupIllustration: 'setup-illustration.svg',
- },
- data() {
- return {
- errorMessage,
- showAlert,
- selectedProject,
- jiraImportDetails: {
- isInProgress,
- imports: [
- {
- jiraProjectKey: 'MTG',
- scheduledAt: '2020-04-08T10:11:12+00:00',
- scheduledBy: {
- name: 'John Doe',
- },
- },
- {
- jiraProjectKey: 'MSJP',
- scheduledAt: '2020-04-09T13:14:15+00:00',
- scheduledBy: {
- name: 'Jimmy Doe',
- },
- },
- {
- jiraProjectKey: 'MTG',
- scheduledAt: '2020-04-09T16:17:18+00:00',
- scheduledBy: {
- name: 'Jane Doe',
- },
- },
- ],
- mostRecentImport: {
- jiraProjectKey: 'MTG',
- scheduledAt: '2020-04-09T16:17:18+00:00',
- scheduledBy: {
- name: 'Jane Doe',
- },
- },
- projects: [
- { text: 'My Jira Project (MJP)', value: 'MJP' },
- { text: 'My Second Jira Project (MSJP)', value: 'MSJP' },
- { text: 'Migrate to GitLab (MTG)', value: 'MTG' },
- ],
- },
- };
- },
- mocks: {
- $apollo: {
- loading,
- mutate,
- },
- },
- });
-};
+import getJiraUserMappingMutation from '~/jira_import/queries/get_jira_user_mapping.mutation.graphql';
+import { imports, issuesPath, jiraIntegrationPath, jiraProjects, userMappings } from '../mock_data';
describe('JiraImportApp', () => {
+ let axiosMock;
+ let mutateSpy;
let wrapper;
const getFormComponent = () => wrapper.find(JiraImportForm);
@@ -95,7 +26,64 @@ describe('JiraImportApp', () => {
const getLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const mountComponent = ({
+ isJiraConfigured = true,
+ errorMessage = '',
+ selectedProject = 'MTG',
+ showAlert = false,
+ isInProgress = false,
+ loading = false,
+ mutate = mutateSpy,
+ mountFunction = shallowMount,
+ } = {}) =>
+ mountFunction(JiraImportApp, {
+ propsData: {
+ inProgressIllustration: 'in-progress-illustration.svg',
+ isJiraConfigured,
+ issuesPath,
+ jiraIntegrationPath,
+ projectId: '5',
+ projectPath: 'gitlab-org/gitlab-test',
+ setupIllustration: 'setup-illustration.svg',
+ },
+ data() {
+ return {
+ isSubmitting: false,
+ selectedProject,
+ userMappings,
+ errorMessage,
+ showAlert,
+ jiraImportDetails: {
+ isInProgress,
+ imports,
+ mostRecentImport: imports[imports.length - 1],
+ projects: jiraProjects,
+ },
+ };
+ },
+ mocks: {
+ $apollo: {
+ loading,
+ mutate,
+ },
+ },
+ });
+
+ beforeEach(() => {
+ axiosMock = new AxiosMockAdapter(axios);
+ mutateSpy = jest.fn(() =>
+ Promise.resolve({
+ data: {
+ jiraImportStart: { errors: [] },
+ jiraImportUsers: { jiraUsers: [], errors: [] },
+ },
+ }),
+ );
+ });
+
afterEach(() => {
+ axiosMock.restore();
+ mutateSpy.mockRestore();
wrapper.destroy();
wrapper = null;
});
@@ -223,7 +211,7 @@ describe('JiraImportApp', () => {
});
it('shows warning alert to explain project MTG has been imported 2 times before', () => {
- wrapper = mountComponent({ mountType: 'mount' });
+ wrapper = mountComponent({ mountFunction: mount });
expect(getAlert().text()).toBe(
'You have imported from this project 2 times before. Each new import will create duplicate issues.',
@@ -248,9 +236,7 @@ describe('JiraImportApp', () => {
describe('initiating a Jira import', () => {
it('calls the mutation with the expected arguments', () => {
- const mutate = jest.fn(() => Promise.resolve());
-
- wrapper = mountComponent({ mutate });
+ wrapper = mountComponent();
const mutationArguments = {
mutation: initiateJiraImportMutation,
@@ -258,13 +244,23 @@ describe('JiraImportApp', () => {
input: {
jiraProjectKey: 'MTG',
projectPath: 'gitlab-org/gitlab-test',
+ usersMapping: [
+ {
+ jiraAccountId: 'aei23f98f-q23fj98qfj',
+ gitlabId: 15,
+ },
+ {
+ jiraAccountId: 'fu39y8t34w-rq3u289t3h4i',
+ gitlabId: undefined,
+ },
+ ],
},
},
};
getFormComponent().vm.$emit('initiateJiraImport', 'MTG');
- expect(mutate).toHaveBeenCalledWith(expect.objectContaining(mutationArguments));
+ expect(mutateSpy).toHaveBeenCalledWith(expect.objectContaining(mutationArguments));
});
it('shows alert message with error message on error', () => {
@@ -283,19 +279,53 @@ describe('JiraImportApp', () => {
});
});
- it('can dismiss alert message', () => {
- wrapper = mountComponent({
- errorMessage: 'There was an error importing the Jira project.',
- showAlert: true,
- selectedProject: null,
+ describe('alert', () => {
+ it('can be dismissed', () => {
+ wrapper = mountComponent({
+ errorMessage: 'There was an error importing the Jira project.',
+ showAlert: true,
+ selectedProject: null,
+ });
+
+ expect(getAlert().exists()).toBe(true);
+
+ getAlert().vm.$emit('dismiss');
+
+ return Vue.nextTick().then(() => {
+ expect(getAlert().exists()).toBe(false);
+ });
});
+ });
- expect(getAlert().exists()).toBe(true);
+ describe('on mount', () => {
+ it('makes a GraphQL mutation call to get user mappings', () => {
+ wrapper = mountComponent();
- getAlert().vm.$emit('dismiss');
+ const mutationArguments = {
+ mutation: getJiraUserMappingMutation,
+ variables: {
+ input: {
+ projectPath: 'gitlab-org/gitlab-test',
+ startAt: 1,
+ },
+ },
+ };
+
+ expect(mutateSpy).toHaveBeenCalledWith(expect.objectContaining(mutationArguments));
+ });
+
+ it('does not make a GraphQL mutation call to get user mappings when Jira is not configured', () => {
+ wrapper = mountComponent({ isJiraConfigured: false });
+
+ expect(mutateSpy).not.toHaveBeenCalled();
+ });
+
+ it('shows error message when there is an error with the GraphQL mutation call', () => {
+ const mutate = jest.fn(() => Promise.reject());
+
+ wrapper = mountComponent({ mutate });
- return Vue.nextTick().then(() => {
- expect(getAlert().exists()).toBe(false);
+ expect(getAlert().exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/jira_import/components/jira_import_form_spec.js b/spec/frontend/jira_import/components/jira_import_form_spec.js
index dea94e7bf1f..685b0288e92 100644
--- a/spec/frontend/jira_import/components/jira_import_form_spec.js
+++ b/spec/frontend/jira_import/components/jira_import_form_spec.js
@@ -1,44 +1,51 @@
-import { GlAvatar, GlButton, GlFormSelect, GlLabel } from '@gitlab/ui';
+import { GlButton, GlFormSelect, GlLabel, GlTable } from '@gitlab/ui';
+import { getByRole } from '@testing-library/dom';
import { mount, shallowMount } from '@vue/test-utils';
+import AxiosMockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import JiraImportForm from '~/jira_import/components/jira_import_form.vue';
-
-const importLabel = 'jira-import::MTG-1';
-const value = 'MTG';
-
-const mountComponent = ({ mountType } = {}) => {
- const mountFunction = mountType === 'mount' ? mount : shallowMount;
-
- return mountFunction(JiraImportForm, {
- propsData: {
- importLabel,
- issuesPath: 'gitlab-org/gitlab-test/-/issues',
- jiraProjects: [
- {
- text: 'My Jira Project',
- value: 'MJP',
- },
- {
- text: 'My Second Jira Project',
- value: 'MSJP',
- },
- {
- text: 'Migrate to GitLab',
- value: 'MTG',
- },
- ],
- value,
- },
- });
-};
+import { issuesPath, jiraProjects, userMappings } from '../mock_data';
describe('JiraImportForm', () => {
+ let axiosMock;
let wrapper;
+ const currentUsername = 'mrgitlab';
+ const importLabel = 'jira-import::MTG-1';
+ const value = 'MTG';
+
const getSelectDropdown = () => wrapper.find(GlFormSelect);
const getCancelButton = () => wrapper.findAll(GlButton).at(1);
+ const getHeader = name => getByRole(wrapper.element, 'columnheader', { name });
+
+ const mountComponent = ({ isSubmitting = false, mountFunction = shallowMount } = {}) =>
+ mountFunction(JiraImportForm, {
+ propsData: {
+ importLabel,
+ isSubmitting,
+ issuesPath,
+ jiraProjects,
+ projectId: '5',
+ userMappings,
+ value,
+ },
+ data: () => ({
+ isFetching: false,
+ searchTerm: '',
+ selectState: null,
+ users: [],
+ }),
+ currentUsername,
+ });
+
+ beforeEach(() => {
+ axiosMock = new AxiosMockAdapter(axios);
+ });
+
afterEach(() => {
+ axiosMock.restore();
wrapper.destroy();
wrapper = null;
});
@@ -51,16 +58,22 @@ describe('JiraImportForm', () => {
});
it('contains a list of Jira projects to select from', () => {
- wrapper = mountComponent({ mountType: 'mount' });
-
- const optionItems = ['My Jira Project', 'My Second Jira Project', 'Migrate to GitLab'];
+ wrapper = mountComponent({ mountFunction: mount });
getSelectDropdown()
.findAll('option')
.wrappers.forEach((optionEl, index) => {
- expect(optionEl.text()).toBe(optionItems[index]);
+ expect(optionEl.text()).toBe(jiraProjects[index].text);
});
});
+
+ it('emits an "input" event when the input select value changes', () => {
+ wrapper = mountComponent();
+
+ getSelectDropdown().vm.$emit('change', value);
+
+ expect(wrapper.emitted('input')[0]).toEqual([value]);
+ });
});
describe('form information', () => {
@@ -72,64 +85,90 @@ describe('JiraImportForm', () => {
expect(wrapper.find(GlLabel).props('title')).toBe(importLabel);
});
+ it('shows a heading for the user mapping section', () => {
+ expect(
+ getByRole(wrapper.element, 'heading', { name: 'Jira-GitLab user mapping template' }),
+ ).toBeTruthy();
+ });
+
it('shows information to the user', () => {
expect(wrapper.find('p').text()).toBe(
- "For each Jira issue successfully imported, we'll create a new GitLab issue with the following data:",
+ 'Jira users have been matched with similar GitLab users. This can be overwritten by selecting a GitLab user from the dropdown in the "GitLab username" column. If it wasn\'t possible to match a Jira user with a GitLab user, the dropdown defaults to the user conducting the import.',
);
});
+ });
- it('shows jira.issue.summary for the Title', () => {
- expect(wrapper.find('[id="jira-project-title"]').text()).toBe('jira.issue.summary');
- });
+ describe('table', () => {
+ describe('headers', () => {
+ beforeEach(() => {
+ wrapper = mountComponent({ mountFunction: mount });
+ });
- it('shows an avatar for the Reporter', () => {
- expect(wrapper.contains(GlAvatar)).toBe(true);
- });
+ it('has a "Jira display name" column', () => {
+ expect(getHeader('Jira display name')).toBeTruthy();
+ });
- it('shows jira.issue.description.content for the Description', () => {
- expect(wrapper.find('[id="jira-project-description"]').text()).toBe(
- 'jira.issue.description.content',
- );
- });
- });
+ it('has an "arrow" column', () => {
+ expect(getHeader('Arrow')).toBeTruthy();
+ });
- describe('Next button', () => {
- beforeEach(() => {
- wrapper = mountComponent();
+ it('has a "GitLab username" column', () => {
+ expect(getHeader('GitLab username')).toBeTruthy();
+ });
});
- it('is shown', () => {
- expect(wrapper.find(GlButton).text()).toBe('Next');
+ describe('body', () => {
+ it('shows all user mappings', () => {
+ wrapper = mountComponent({ mountFunction: mount });
+
+ expect(wrapper.find(GlTable).findAll('tbody tr').length).toBe(userMappings.length);
+ });
+
+ it('shows correct information in each cell', () => {
+ wrapper = mountComponent({ mountFunction: mount });
+
+ expect(wrapper.find(GlTable).element).toMatchSnapshot();
+ });
});
});
- describe('Cancel button', () => {
- beforeEach(() => {
- wrapper = mountComponent();
- });
+ describe('buttons', () => {
+ describe('"Continue" button', () => {
+ it('is shown', () => {
+ wrapper = mountComponent();
- it('is shown', () => {
- expect(getCancelButton().text()).toBe('Cancel');
- });
+ expect(wrapper.find(GlButton).text()).toBe('Continue');
+ });
+
+ it('is in loading state when the form is submitting', async () => {
+ wrapper = mountComponent({ isSubmitting: true });
- it('links to the Issues page', () => {
- expect(getCancelButton().attributes('href')).toBe('gitlab-org/gitlab-test/-/issues');
+ expect(wrapper.find(GlButton).props('loading')).toBe(true);
+ });
});
- });
- it('emits an "input" event when the input select value changes', () => {
- wrapper = mountComponent({ mountType: 'mount' });
+ describe('"Cancel" button', () => {
+ beforeEach(() => {
+ wrapper = mountComponent();
+ });
- getSelectDropdown().vm.$emit('change', value);
+ it('is shown', () => {
+ expect(getCancelButton().text()).toBe('Cancel');
+ });
- expect(wrapper.emitted('input')[0]).toEqual([value]);
+ it('links to the Issues page', () => {
+ expect(getCancelButton().attributes('href')).toBe(issuesPath);
+ });
+ });
});
- it('emits an "initiateJiraImport" event with the selected dropdown value when submitted', () => {
- wrapper = mountComponent();
+ describe('form', () => {
+ it('emits an "initiateJiraImport" event with the selected dropdown value when submitted', () => {
+ wrapper = mountComponent();
- wrapper.find('form').trigger('submit');
+ wrapper.find('form').trigger('submit');
- expect(wrapper.emitted('initiateJiraImport')[0]).toEqual([value]);
+ expect(wrapper.emitted('initiateJiraImport')[0]).toEqual([value]);
+ });
});
});
diff --git a/spec/frontend/jira_import/components/jira_import_progress_spec.js b/spec/frontend/jira_import/components/jira_import_progress_spec.js
index 3ccf14554e1..ed7e1824fa3 100644
--- a/spec/frontend/jira_import/components/jira_import_progress_spec.js
+++ b/spec/frontend/jira_import/components/jira_import_progress_spec.js
@@ -1,14 +1,13 @@
import { GlEmptyState } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import JiraImportProgress from '~/jira_import/components/jira_import_progress.vue';
-
-const illustration = 'illustration.svg';
-const importProject = 'JIRAPROJECT';
-const issuesPath = 'gitlab-org/gitlab-test/-/issues';
+import { illustration, issuesPath } from '../mock_data';
describe('JiraImportProgress', () => {
let wrapper;
+ const importProject = 'JIRAPROJECT';
+
const getGlEmptyStateProp = attribute => wrapper.find(GlEmptyState).props(attribute);
const getParagraphText = () => wrapper.find('p').text();
diff --git a/spec/frontend/jira_import/components/jira_import_setup_spec.js b/spec/frontend/jira_import/components/jira_import_setup_spec.js
index aa94dc4f503..7c84d4a166a 100644
--- a/spec/frontend/jira_import/components/jira_import_setup_spec.js
+++ b/spec/frontend/jira_import/components/jira_import_setup_spec.js
@@ -1,9 +1,7 @@
import { GlEmptyState } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import JiraImportSetup from '~/jira_import/components/jira_import_setup.vue';
-
-const illustration = 'illustration.svg';
-const jiraIntegrationPath = 'gitlab-org/gitlab-test/-/services/jira/edit';
+import { illustration, jiraIntegrationPath } from '../mock_data';
describe('JiraImportSetup', () => {
let wrapper;
diff --git a/spec/frontend/jira_import/mock_data.js b/spec/frontend/jira_import/mock_data.js
index e82ab53cb6f..a7447221b15 100644
--- a/spec/frontend/jira_import/mock_data.js
+++ b/spec/frontend/jira_import/mock_data.js
@@ -70,3 +70,56 @@ export const jiraImportMutationResponse = {
__typename: 'JiraImportStartPayload',
},
};
+
+export const issuesPath = 'gitlab-org/gitlab-test/-/issues';
+
+export const jiraIntegrationPath = 'gitlab-org/gitlab-test/-/services/jira/edit';
+
+export const illustration = 'illustration.svg';
+
+export const jiraProjects = [
+ { text: 'My Jira Project (MJP)', value: 'MJP' },
+ { text: 'My Second Jira Project (MSJP)', value: 'MSJP' },
+ { text: 'Migrate to GitLab (MTG)', value: 'MTG' },
+];
+
+export const imports = [
+ {
+ jiraProjectKey: 'MTG',
+ scheduledAt: '2020-04-08T10:11:12+00:00',
+ scheduledBy: {
+ name: 'John Doe',
+ },
+ },
+ {
+ jiraProjectKey: 'MSJP',
+ scheduledAt: '2020-04-09T13:14:15+00:00',
+ scheduledBy: {
+ name: 'Jimmy Doe',
+ },
+ },
+ {
+ jiraProjectKey: 'MTG',
+ scheduledAt: '2020-04-09T16:17:18+00:00',
+ scheduledBy: {
+ name: 'Jane Doe',
+ },
+ },
+];
+
+export const userMappings = [
+ {
+ jiraAccountId: 'aei23f98f-q23fj98qfj',
+ jiraDisplayName: 'Jane Doe',
+ jiraEmail: 'janedoe@example.com',
+ gitlabId: 15,
+ gitlabUsername: 'janedoe',
+ },
+ {
+ jiraAccountId: 'fu39y8t34w-rq3u289t3h4i',
+ jiraDisplayName: 'Fred Chopin',
+ jiraEmail: 'fredchopin@example.com',
+ gitlabId: undefined,
+ gitlabUsername: undefined,
+ },
+];
diff --git a/spec/frontend/jira_import/utils/jira_import_utils_spec.js b/spec/frontend/jira_import/utils/jira_import_utils_spec.js
index 504d399217a..8ae1fc3535a 100644
--- a/spec/frontend/jira_import/utils/jira_import_utils_spec.js
+++ b/spec/frontend/jira_import/utils/jira_import_utils_spec.js
@@ -1,10 +1,16 @@
+import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import {
calculateJiraImportLabel,
extractJiraProjectsOptions,
IMPORT_STATE,
isFinished,
isInProgress,
+ setFinishedAlertHideMap,
+ shouldShowFinishedAlert,
} from '~/jira_import/utils/jira_import_utils';
+import { JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY } from '~/issuables_list/constants';
+
+useLocalStorageSpy();
describe('isInProgress', () => {
it.each`
@@ -89,3 +95,56 @@ describe('calculateJiraImportLabel', () => {
expect(label.color).toBe('#333');
});
});
+
+describe('shouldShowFinishedAlert', () => {
+ const labelTitle = 'jira-import::JCP-1';
+
+ afterEach(() => {
+ localStorage.clear();
+ });
+
+ it('checks localStorage value', () => {
+ jest.spyOn(localStorage, 'getItem').mockReturnValue(JSON.stringify({}));
+
+ shouldShowFinishedAlert(labelTitle, IMPORT_STATE.FINISHED);
+
+ expect(localStorage.getItem).toHaveBeenCalledWith(JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY);
+ });
+
+ it('returns true when an import has finished', () => {
+ jest.spyOn(localStorage, 'getItem').mockReturnValue(JSON.stringify({}));
+
+ expect(shouldShowFinishedAlert(labelTitle, IMPORT_STATE.FINISHED)).toBe(true);
+ });
+
+ it('returns false when an import has finished but the user chose to hide the alert', () => {
+ jest.spyOn(localStorage, 'getItem').mockReturnValue(JSON.stringify({ [labelTitle]: true }));
+
+ expect(shouldShowFinishedAlert(labelTitle, IMPORT_STATE.FINISHED)).toBe(false);
+ });
+
+ it('returns false when an import has not finished', () => {
+ jest.spyOn(localStorage, 'getItem').mockReturnValue(JSON.stringify({}));
+
+ expect(shouldShowFinishedAlert(labelTitle, IMPORT_STATE.SCHEDULED)).toBe(false);
+ });
+});
+
+describe('setFinishedAlertHideMap', () => {
+ const labelTitle = 'jira-import::ABC-1';
+ const newLabelTitle = 'jira-import::JCP-1';
+
+ it('sets item to localStorage correctly', () => {
+ jest.spyOn(localStorage, 'getItem').mockReturnValue(JSON.stringify({ [labelTitle]: true }));
+
+ setFinishedAlertHideMap(newLabelTitle);
+
+ expect(localStorage.setItem).toHaveBeenCalledWith(
+ JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY,
+ JSON.stringify({
+ [labelTitle]: true,
+ [newLabelTitle]: true,
+ }),
+ );
+ });
+});
diff --git a/spec/frontend/jobs/components/job_app_spec.js b/spec/frontend/jobs/components/job_app_spec.js
index 8fa289bbe4d..d0b3d4f6247 100644
--- a/spec/frontend/jobs/components/job_app_spec.js
+++ b/spec/frontend/jobs/components/job_app_spec.js
@@ -6,6 +6,7 @@ import axios from '~/lib/utils/axios_utils';
import JobApp from '~/jobs/components/job_app.vue';
import createStore from '~/jobs/store';
import job from '../mock_data';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('Job App', () => {
const localVue = createLocalVue();
@@ -18,8 +19,8 @@ describe('Job App', () => {
let mock;
const initSettings = {
- endpoint: `${gl.TEST_HOST}jobs/123.json`,
- pagePath: `${gl.TEST_HOST}jobs/123`,
+ endpoint: `${TEST_HOST}jobs/123.json`,
+ pagePath: `${TEST_HOST}jobs/123`,
logState:
'eyJvZmZzZXQiOjE3NDUxLCJuX29wZW5fdGFncyI6MCwiZmdfY29sb3IiOm51bGwsImJnX2NvbG9yIjpudWxsLCJzdHlsZV9tYXNrIjowfQ%3D%3D',
};
@@ -397,132 +398,31 @@ describe('Job App', () => {
});
});
- describe('trace output', () => {
- describe('with append flag', () => {
- it('appends the log content to the existing one', () =>
- setupAndMount({
- traceData: {
- html: '<span>More<span>',
- status: 'running',
- state: 'newstate',
- append: true,
- complete: true,
- },
- })
- .then(() => {
- store.state.trace = 'Update';
-
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(
- wrapper
- .find('.js-build-trace')
- .text()
- .trim(),
- ).toEqual('Update');
- }));
+ describe('trace controls', () => {
+ beforeEach(() =>
+ setupAndMount({
+ traceData: {
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 50,
+ total: 100,
+ complete: true,
+ },
+ }),
+ );
+
+ it('should render scroll buttons', () => {
+ expect(wrapper.find('.js-scroll-top').exists()).toBe(true);
+ expect(wrapper.find('.js-scroll-bottom').exists()).toBe(true);
});
- describe('without append flag', () => {
- it('replaces the trace', () =>
- setupAndMount({
- traceData: {
- html: '<span>Different<span>',
- status: 'running',
- append: false,
- complete: true,
- },
- }).then(() => {
- expect(
- wrapper
- .find('.js-build-trace')
- .text()
- .trim(),
- ).toEqual('Different');
- }));
- });
-
- describe('truncated information', () => {
- describe('when size is less than total', () => {
- it('shows information about truncated log', () => {
- mock.onGet(`${props.pagePath}/trace.json`).reply(200, {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 50,
- total: 100,
- complete: true,
- });
-
- return setupAndMount({
- traceData: {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 50,
- total: 100,
- complete: true,
- },
- }).then(() => {
- expect(
- wrapper
- .find('.js-truncated-info')
- .text()
- .trim(),
- ).toContain('Showing last 50 bytes');
- });
- });
- });
-
- describe('when size is equal than total', () => {
- it('does not show the truncated information', () =>
- setupAndMount({
- traceData: {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 100,
- total: 100,
- complete: true,
- },
- }).then(() => {
- expect(
- wrapper
- .find('.js-truncated-info')
- .text()
- .trim(),
- ).toEqual('');
- }));
- });
+ it('should render link to raw ouput', () => {
+ expect(wrapper.find('.js-raw-link-controller').exists()).toBe(true);
});
- describe('trace controls', () => {
- beforeEach(() =>
- setupAndMount({
- traceData: {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 50,
- total: 100,
- complete: true,
- },
- }),
- );
-
- it('should render scroll buttons', () => {
- expect(wrapper.find('.js-scroll-top').exists()).toBe(true);
- expect(wrapper.find('.js-scroll-bottom').exists()).toBe(true);
- });
-
- it('should render link to raw ouput', () => {
- expect(wrapper.find('.js-raw-link-controller').exists()).toBe(true);
- });
-
- it('should render link to erase job', () => {
- expect(wrapper.find('.js-erase-link').exists()).toBe(true);
- });
+ it('should render link to erase job', () => {
+ expect(wrapper.find('.js-erase-link').exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/jobs/components/job_log_spec.js b/spec/frontend/jobs/components/job_log_spec.js
deleted file mode 100644
index a167fe8a134..00000000000
--- a/spec/frontend/jobs/components/job_log_spec.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import Vue from 'vue';
-import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
-import component from '~/jobs/components/job_log.vue';
-import createStore from '~/jobs/store';
-import { resetStore } from '../store/helpers';
-
-describe('Job Log', () => {
- const Component = Vue.extend(component);
- let store;
- let vm;
-
- const trace =
- '<span>Running with gitlab-runner 12.1.0 (de7731dd)<br/></span><span> on docker-auto-scale-com d5ae8d25<br/></span><div class="gl-mr-3" data-timestamp="1565502765" data-section="prepare-executor" role="button"></div><span class="section section-header js-s-prepare-executor">Using Docker executor with image ruby:2.6 ...<br/></span>';
-
- beforeEach(() => {
- store = createStore();
- });
-
- afterEach(() => {
- resetStore(store);
- vm.$destroy();
- });
-
- it('renders provided trace', () => {
- vm = mountComponentWithStore(Component, {
- props: {
- trace,
- isComplete: true,
- },
- store,
- });
-
- expect(vm.$el.querySelector('code').textContent).toContain(
- 'Running with gitlab-runner 12.1.0 (de7731dd)',
- );
- });
-
- describe('while receiving trace', () => {
- it('renders animation', () => {
- vm = mountComponentWithStore(Component, {
- props: {
- trace,
- isComplete: false,
- },
- store,
- });
-
- expect(vm.$el.querySelector('.js-log-animation')).not.toBeNull();
- });
- });
-
- describe('when build trace has finishes', () => {
- it('does not render animation', () => {
- vm = mountComponentWithStore(Component, {
- props: {
- trace,
- isComplete: true,
- },
- store,
- });
-
- expect(vm.$el.querySelector('.js-log-animation')).toBeNull();
- });
- });
-});
diff --git a/spec/frontend/jobs/components/log/collapsible_section_spec.js b/spec/frontend/jobs/components/log/collapsible_section_spec.js
index 3a16521a986..bf2f8c05806 100644
--- a/spec/frontend/jobs/components/log/collapsible_section_spec.js
+++ b/spec/frontend/jobs/components/log/collapsible_section_spec.js
@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
-import CollpasibleSection from '~/jobs/components/log/collapsible_section.vue';
+import CollapsibleSection from '~/jobs/components/log/collapsible_section.vue';
import { collapsibleSectionClosed, collapsibleSectionOpened } from './mock_data';
describe('Job Log Collapsible Section', () => {
@@ -11,7 +11,7 @@ describe('Job Log Collapsible Section', () => {
const findCollapsibleLineSvg = () => wrapper.find('.collapsible-line svg');
const createComponent = (props = {}) => {
- wrapper = mount(CollpasibleSection, {
+ wrapper = mount(CollapsibleSection, {
propsData: {
...props,
},
diff --git a/spec/frontend/jobs/store/mutations_spec.js b/spec/frontend/jobs/store/mutations_spec.js
index 3557d3b94b6..608abc8f7c4 100644
--- a/spec/frontend/jobs/store/mutations_spec.js
+++ b/spec/frontend/jobs/store/mutations_spec.js
@@ -76,28 +76,15 @@ describe('Jobs Store Mutations', () => {
lines: [],
});
- expect(stateCopy.trace).toEqual(html);
expect(stateCopy.traceSize).toEqual(511846);
expect(stateCopy.isTraceComplete).toEqual(true);
});
describe('with new job log', () => {
- let stateWithNewLog;
- beforeEach(() => {
- gon.features = gon.features || {};
- gon.features.jobLogJson = true;
-
- stateWithNewLog = state();
- });
-
- afterEach(() => {
- gon.features.jobLogJson = false;
- });
-
describe('log.lines', () => {
describe('when append is true', () => {
it('sets the parsed log ', () => {
- mutations[types.RECEIVE_TRACE_SUCCESS](stateWithNewLog, {
+ mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, {
append: true,
size: 511846,
complete: true,
@@ -109,7 +96,7 @@ describe('Jobs Store Mutations', () => {
],
});
- expect(stateWithNewLog.trace).toEqual([
+ expect(stateCopy.trace).toEqual([
{
offset: 1,
content: [{ text: 'Running with gitlab-runner 11.12.1 (5a147c92)' }],
@@ -121,7 +108,7 @@ describe('Jobs Store Mutations', () => {
describe('when it is defined', () => {
it('sets the parsed log ', () => {
- mutations[types.RECEIVE_TRACE_SUCCESS](stateWithNewLog, {
+ mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, {
append: false,
size: 511846,
complete: true,
@@ -130,7 +117,7 @@ describe('Jobs Store Mutations', () => {
],
});
- expect(stateWithNewLog.trace).toEqual([
+ expect(stateCopy.trace).toEqual([
{
offset: 0,
content: [{ text: 'Running with gitlab-runner 11.11.1 (5a147c92)' }],
@@ -142,7 +129,7 @@ describe('Jobs Store Mutations', () => {
describe('when it is null', () => {
it('sets the default value', () => {
- mutations[types.RECEIVE_TRACE_SUCCESS](stateWithNewLog, {
+ mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, {
append: true,
html,
size: 511846,
@@ -150,7 +137,7 @@ describe('Jobs Store Mutations', () => {
lines: null,
});
- expect(stateWithNewLog.trace).toEqual([]);
+ expect(stateCopy.trace).toEqual([]);
});
});
});
diff --git a/spec/frontend/jobs/store/utils_spec.js b/spec/frontend/jobs/store/utils_spec.js
index 8819f39dee0..294f88bbc74 100644
--- a/spec/frontend/jobs/store/utils_spec.js
+++ b/spec/frontend/jobs/store/utils_spec.js
@@ -181,7 +181,7 @@ describe('Jobs Store Utils', () => {
});
});
- describe('collpasible section', () => {
+ describe('collapsible section', () => {
it('adds a `isClosed` property', () => {
expect(result[1].isClosed).toEqual(false);
});
@@ -190,7 +190,7 @@ describe('Jobs Store Utils', () => {
expect(result[1].isHeader).toEqual(true);
});
- it('creates a lines array property with the content of the collpasible section', () => {
+ it('creates a lines array property with the content of the collapsible section', () => {
expect(result[1].lines.length).toEqual(2);
expect(result[1].lines[0].content).toEqual(utilsMockData[2].content);
expect(result[1].lines[1].content).toEqual(utilsMockData[3].content);
diff --git a/spec/frontend/lib/utils/common_utils_spec.js b/spec/frontend/lib/utils/common_utils_spec.js
index f597255538c..585f0de9cc3 100644
--- a/spec/frontend/lib/utils/common_utils_spec.js
+++ b/spec/frontend/lib/utils/common_utils_spec.js
@@ -330,32 +330,6 @@ describe('common_utils', () => {
});
});
- describe('normalizeCRLFHeaders', () => {
- const testContext = {};
- beforeEach(() => {
- testContext.CLRFHeaders =
- 'a-header: a-value\nAnother-Header: ANOTHER-VALUE\nLaSt-HeAdEr: last-VALUE';
- jest.spyOn(String.prototype, 'split');
- testContext.normalizeCRLFHeaders = commonUtils.normalizeCRLFHeaders(testContext.CLRFHeaders);
- });
-
- it('should split by newline', () => {
- expect(String.prototype.split).toHaveBeenCalledWith('\n');
- });
-
- it('should split by colon+space for each header', () => {
- expect(String.prototype.split.mock.calls.filter(args => args[0] === ': ').length).toBe(3);
- });
-
- it('should return a normalized headers object', () => {
- expect(testContext.normalizeCRLFHeaders).toEqual({
- 'A-HEADER': 'a-value',
- 'ANOTHER-HEADER': 'ANOTHER-VALUE',
- 'LAST-HEADER': 'last-VALUE',
- });
- });
- });
-
describe('parseIntPagination', () => {
it('should parse to integers all string values and return pagination object', () => {
const pagination = {
@@ -510,27 +484,6 @@ describe('common_utils', () => {
});
});
- describe('setFavicon', () => {
- beforeEach(() => {
- const favicon = document.createElement('link');
- favicon.setAttribute('id', 'favicon');
- favicon.setAttribute('href', 'default/favicon');
- favicon.setAttribute('data-default-href', 'default/favicon');
- document.body.appendChild(favicon);
- });
-
- afterEach(() => {
- document.body.removeChild(document.getElementById('favicon'));
- });
-
- it('should set page favicon to provided favicon', () => {
- const faviconPath = '//custom_favicon';
- commonUtils.setFavicon(faviconPath);
-
- expect(document.getElementById('favicon').getAttribute('href')).toEqual(faviconPath);
- });
- });
-
describe('resetFavicon', () => {
beforeEach(() => {
const favicon = document.createElement('link');
diff --git a/spec/frontend/lib/utils/datetime_utility_spec.js b/spec/frontend/lib/utils/datetime_utility_spec.js
index f6878c7c920..adf5c312149 100644
--- a/spec/frontend/lib/utils/datetime_utility_spec.js
+++ b/spec/frontend/lib/utils/datetime_utility_spec.js
@@ -1,5 +1,6 @@
import { __, s__ } from '~/locale';
import $ from 'jquery';
+import timezoneMock from 'timezone-mock';
import '~/commons/bootstrap';
import * as datetimeUtility from '~/lib/utils/datetime_utility';
@@ -86,6 +87,31 @@ describe('Date time utils', () => {
datetimeUtility.formatDate('2016-07-23 00:00:00 UTC');
}).toThrow(new Error('Invalid date'));
});
+
+ describe('convert local timezone to UTC with utc parameter', () => {
+ const midnightUTC = '2020-07-09';
+ const format = 'mmm d, yyyy';
+
+ beforeEach(() => {
+ timezoneMock.register('US/Pacific');
+ });
+
+ afterEach(() => {
+ timezoneMock.unregister();
+ });
+
+ it('defaults to false', () => {
+ const formattedDate = datetimeUtility.formatDate(midnightUTC, format);
+
+ expect(formattedDate).toBe('Jul 8, 2020');
+ });
+
+ it('converts local time to UTC if utc flag is true', () => {
+ const formattedDate = datetimeUtility.formatDate(midnightUTC, format, true);
+
+ expect(formattedDate).toBe('Jul 9, 2020');
+ });
+ });
});
describe('get day difference', () => {
diff --git a/spec/frontend/lib/utils/dom_utils_spec.js b/spec/frontend/lib/utils/dom_utils_spec.js
index 10b4a10a8ff..d918016a5f4 100644
--- a/spec/frontend/lib/utils/dom_utils_spec.js
+++ b/spec/frontend/lib/utils/dom_utils_spec.js
@@ -1,4 +1,9 @@
-import { addClassIfElementExists, canScrollUp, canScrollDown } from '~/lib/utils/dom_utils';
+import {
+ addClassIfElementExists,
+ canScrollUp,
+ canScrollDown,
+ parseBooleanDataAttributes,
+} from '~/lib/utils/dom_utils';
const TEST_MARGIN = 5;
@@ -112,4 +117,47 @@ describe('DOM Utils', () => {
expect(canScrollDown(element, TEST_MARGIN)).toBe(false);
});
});
+
+ describe('parseBooleanDataAttributes', () => {
+ let element;
+
+ beforeEach(() => {
+ setFixtures('<div data-foo-bar data-baz data-qux="">');
+ element = document.querySelector('[data-foo-bar]');
+ });
+
+ it('throws if not given an element', () => {
+ expect(() => parseBooleanDataAttributes(null, ['baz'])).toThrow();
+ });
+
+ it('throws if not given an array of dataset names', () => {
+ expect(() => parseBooleanDataAttributes(element)).toThrow();
+ });
+
+ it('returns an empty object if given an empty array of names', () => {
+ expect(parseBooleanDataAttributes(element, [])).toEqual({});
+ });
+
+ it('correctly parses boolean-like data attributes', () => {
+ expect(
+ parseBooleanDataAttributes(element, [
+ 'fooBar',
+ 'foobar',
+ 'baz',
+ 'qux',
+ 'doesNotExist',
+ 'toString',
+ ]),
+ ).toEqual({
+ fooBar: true,
+ foobar: false,
+ baz: true,
+ qux: true,
+ doesNotExist: false,
+
+ // Ensure prototype properties aren't false positives
+ toString: false,
+ });
+ });
+ });
});
diff --git a/spec/frontend/lib/utils/grammar_spec.js b/spec/frontend/lib/utils/grammar_spec.js
index 377b2ffb48c..7f2431af7ed 100644
--- a/spec/frontend/lib/utils/grammar_spec.js
+++ b/spec/frontend/lib/utils/grammar_spec.js
@@ -7,27 +7,27 @@ describe('utils/grammar', () => {
});
it('with single item returns item', () => {
- const items = ['Lorem Ipsum'];
+ const items = ['Lorem & Ipsum'];
expect(grammar.toNounSeriesText(items)).toBe(items[0]);
});
it('with 2 items returns item1 and item2', () => {
- const items = ['Dolar', 'Sit Amit'];
+ const items = ['Dolar', 'Sit & Amit'];
expect(grammar.toNounSeriesText(items)).toBe(`${items[0]} and ${items[1]}`);
});
it('with 3 items returns comma separated series', () => {
- const items = ['Lorem', 'Ipsum', 'dolar'];
- const expected = 'Lorem, Ipsum, and dolar';
+ const items = ['Lorem', 'Ipsum', 'Sit & Amit'];
+ const expected = 'Lorem, Ipsum, and Sit & Amit';
expect(grammar.toNounSeriesText(items)).toBe(expected);
});
it('with 6 items returns comma separated series', () => {
- const items = ['Lorem', 'ipsum', 'dolar', 'sit', 'amit', 'consectetur'];
- const expected = 'Lorem, ipsum, dolar, sit, amit, and consectetur';
+ const items = ['Lorem', 'ipsum', 'dolar', 'sit', 'amit', 'consectetur & adipiscing'];
+ const expected = 'Lorem, ipsum, dolar, sit, amit, and consectetur & adipiscing';
expect(grammar.toNounSeriesText(items)).toBe(expected);
});
diff --git a/spec/frontend/lib/utils/text_markdown_spec.js b/spec/frontend/lib/utils/text_markdown_spec.js
index aca299aea0f..2e52958a828 100644
--- a/spec/frontend/lib/utils/text_markdown_spec.js
+++ b/spec/frontend/lib/utils/text_markdown_spec.js
@@ -232,19 +232,17 @@ describe('init markdown', () => {
beforeEach(() => {
editor = {
- getSelectionRange: () => ({
+ getSelectionRange: jest.fn().mockReturnValue({
start: 0,
end: 0,
}),
- getValue: () => 'this is text \n in two lines',
- insert: () => {},
- navigateLeft: () => {},
+ getValue: jest.fn().mockReturnValue('this is text \n in two lines'),
+ insert: jest.fn(),
+ navigateLeft: jest.fn(),
};
});
it('uses ace editor insert text when editor is passed in', () => {
- jest.spyOn(editor, 'insert').mockReturnValue();
-
insertMarkdownText({
text: editor.getValue,
tag: '*',
@@ -258,8 +256,6 @@ describe('init markdown', () => {
});
it('adds block tags on line above and below selection', () => {
- jest.spyOn(editor, 'insert').mockReturnValue();
-
const selected = 'this text \n is multiple \n lines';
const text = `before \n ${selected} \n after`;
@@ -276,8 +272,6 @@ describe('init markdown', () => {
});
it('uses ace editor to navigate back tag length when nothing is selected', () => {
- jest.spyOn(editor, 'navigateLeft').mockReturnValue();
-
insertMarkdownText({
text: editor.getValue,
tag: '*',
@@ -291,8 +285,6 @@ describe('init markdown', () => {
});
it('ace editor does not navigate back when there is selected text', () => {
- jest.spyOn(editor, 'navigateLeft').mockReturnValue();
-
insertMarkdownText({
text: editor.getValue,
tag: '*',
@@ -305,4 +297,96 @@ describe('init markdown', () => {
expect(editor.navigateLeft).not.toHaveBeenCalled();
});
});
+
+ describe('Editor Lite', () => {
+ let editor;
+ let origGon;
+
+ beforeEach(() => {
+ origGon = window.gon;
+ window.gon = {
+ features: {
+ monacoBlobs: true,
+ },
+ };
+ editor = {
+ getSelection: jest.fn().mockReturnValue({
+ startLineNumber: 1,
+ startColumn: 1,
+ endLineNumber: 2,
+ endColumn: 2,
+ }),
+ getValue: jest.fn().mockReturnValue('this is text \n in two lines'),
+ selectWithinSelection: jest.fn(),
+ replaceSelectedText: jest.fn(),
+ moveCursor: jest.fn(),
+ };
+ });
+
+ afterEach(() => {
+ window.gon = origGon;
+ });
+
+ it('replaces selected text', () => {
+ insertMarkdownText({
+ text: editor.getValue,
+ tag: '*',
+ blockTag: null,
+ selected: '',
+ wrap: false,
+ editor,
+ });
+
+ expect(editor.replaceSelectedText).toHaveBeenCalled();
+ });
+
+ it('adds block tags on line above and below selection', () => {
+ const selected = 'this text \n is multiple \n lines';
+ const text = `before \n ${selected} \n after`;
+
+ insertMarkdownText({
+ text,
+ tag: '',
+ blockTag: '***',
+ selected,
+ wrap: true,
+ editor,
+ });
+
+ expect(editor.replaceSelectedText).toHaveBeenCalledWith(`***\n${selected}\n***\n`, undefined);
+ });
+
+ it('uses ace editor to navigate back tag length when nothing is selected', () => {
+ editor.getSelection = jest.fn().mockReturnValue({
+ startLineNumber: 1,
+ startColumn: 1,
+ endLineNumber: 1,
+ endColumn: 1,
+ });
+
+ insertMarkdownText({
+ text: editor.getValue,
+ tag: '*',
+ blockTag: null,
+ selected: '',
+ wrap: true,
+ editor,
+ });
+
+ expect(editor.moveCursor).toHaveBeenCalledWith(-1);
+ });
+
+ it('ace editor does not navigate back when there is selected text', () => {
+ insertMarkdownText({
+ text: editor.getValue,
+ tag: '*',
+ blockTag: null,
+ selected: 'foobar',
+ wrap: true,
+ editor,
+ });
+
+ expect(editor.selectWithinSelection).not.toHaveBeenCalled();
+ });
+ });
});
diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js
index 76e0e435860..285f7d04c3b 100644
--- a/spec/frontend/lib/utils/text_utility_spec.js
+++ b/spec/frontend/lib/utils/text_utility_spec.js
@@ -145,6 +145,56 @@ describe('text_utility', () => {
});
});
+ describe('truncate', () => {
+ it('returns the original string when str length is less than maxLength', () => {
+ const str = 'less than 20 chars';
+ expect(textUtils.truncate(str, 20)).toEqual(str);
+ });
+
+ it('returns truncated string when str length is more than maxLength', () => {
+ const str = 'more than 10 chars';
+ expect(textUtils.truncate(str, 10)).toEqual(`${str.substring(0, 10 - 1)}…`);
+ });
+
+ it('returns the original string when rendered width is exactly equal to maxWidth', () => {
+ const str = 'Exactly 16 chars';
+ expect(textUtils.truncate(str, 16)).toEqual(str);
+ });
+ });
+
+ describe('truncateWidth', () => {
+ const clientWidthDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, 'clientWidth');
+
+ beforeAll(() => {
+ // Mock measured width of ' ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
+ Object.defineProperty(Element.prototype, 'clientWidth', {
+ value: 431,
+ writable: false,
+ });
+ });
+
+ afterAll(() => {
+ Object.defineProperty(Element.prototype, 'clientWidth', clientWidthDescriptor);
+ });
+
+ it('returns the original string when rendered width is less than maxWidth', () => {
+ const str = '< 80px';
+ expect(textUtils.truncateWidth(str)).toEqual(str);
+ });
+
+ it('returns truncated string when rendered width is more than maxWidth', () => {
+ const str = 'This is wider than 80px';
+ expect(textUtils.truncateWidth(str)).toEqual(`${str.substring(0, 10)}…`);
+ });
+
+ it('returns the original string when rendered width is exactly equal to maxWidth', () => {
+ const str = 'Exactly 159.62962962962965px';
+ expect(textUtils.truncateWidth(str, { maxWidth: 159.62962962962965, fontSize: 10 })).toEqual(
+ str,
+ );
+ });
+ });
+
describe('truncateSha', () => {
it('shortens SHAs to 8 characters', () => {
expect(textUtils.truncateSha('verylongsha')).toBe('verylong');
diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js
index 85e680fe216..e769580b587 100644
--- a/spec/frontend/lib/utils/url_utility_spec.js
+++ b/spec/frontend/lib/utils/url_utility_spec.js
@@ -595,6 +595,14 @@ describe('URL utility', () => {
);
});
+ it('handles arrays properly when railsArraySyntax=true', () => {
+ const url = 'https://gitlab.com/test';
+
+ expect(urlUtils.setUrlParams({ labels: ['foo', 'bar'] }, url, false, true)).toEqual(
+ 'https://gitlab.com/test?labels%5B%5D=foo&labels%5B%5D=bar',
+ );
+ });
+
it('removes all existing URL params and sets a new param when cleanParams=true', () => {
const url = 'https://gitlab.com/test?group_id=gitlab-org&project_id=my-project';
diff --git a/spec/frontend/logs/components/environment_logs_spec.js b/spec/frontend/logs/components/environment_logs_spec.js
index 62f3e8a755d..dee62709d81 100644
--- a/spec/frontend/logs/components/environment_logs_spec.js
+++ b/spec/frontend/logs/components/environment_logs_spec.js
@@ -12,6 +12,7 @@ import {
mockTrace,
mockEnvironmentsEndpoint,
mockDocumentationPath,
+ mockManagedAppsEndpoint,
} from '../mock_data';
jest.mock('~/lib/utils/scroll_utils');
@@ -34,6 +35,7 @@ describe('EnvironmentLogs', () => {
environmentName: mockEnvName,
environmentsPath: mockEnvironmentsEndpoint,
clusterApplicationsDocumentationPath: mockDocumentationPath,
+ clustersPath: mockManagedAppsEndpoint,
};
const updateControlBtnsMock = jest.fn();
diff --git a/spec/frontend/logs/components/log_control_buttons_spec.js b/spec/frontend/logs/components/log_control_buttons_spec.js
index 85fc5a040d6..38e568f569f 100644
--- a/spec/frontend/logs/components/log_control_buttons_spec.js
+++ b/spec/frontend/logs/components/log_control_buttons_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import { GlDeprecatedButton } from '@gitlab/ui';
+import { GlButton } from '@gitlab/ui';
import LogControlButtons from '~/logs/components/log_control_buttons.vue';
describe('LogControlButtons', () => {
@@ -31,9 +31,9 @@ describe('LogControlButtons', () => {
expect(wrapper.isVueInstance()).toBe(true);
expect(wrapper.isEmpty()).toBe(false);
- expect(findScrollToTop().is(GlDeprecatedButton)).toBe(true);
- expect(findScrollToBottom().is(GlDeprecatedButton)).toBe(true);
- expect(findRefreshBtn().is(GlDeprecatedButton)).toBe(true);
+ expect(findScrollToTop().is(GlButton)).toBe(true);
+ expect(findScrollToBottom().is(GlButton)).toBe(true);
+ expect(findRefreshBtn().is(GlButton)).toBe(true);
});
it('emits a `refresh` event on click on `refresh` button', () => {
diff --git a/spec/frontend/logs/mock_data.js b/spec/frontend/logs/mock_data.js
index 14c8f7a2ba2..f9b3508e01c 100644
--- a/spec/frontend/logs/mock_data.js
+++ b/spec/frontend/logs/mock_data.js
@@ -7,6 +7,8 @@ export const mockDocumentationPath = '/documentation.md';
export const mockLogsEndpoint = '/dummy_logs_path.json';
export const mockCursor = 'MOCK_CURSOR';
export const mockNextCursor = 'MOCK_NEXT_CURSOR';
+export const mockManagedAppName = 'kubernetes-cluster-1';
+export const mockManagedAppsEndpoint = `${mockProjectPath}/clusters.json`;
const makeMockEnvironment = (id, name, advancedQuerying) => ({
id,
@@ -23,6 +25,19 @@ export const mockEnvironments = [
makeMockEnvironment(102, 'review/a-feature', false),
];
+export const mockManagedApps = [
+ {
+ cluster_type: 'project_type',
+ enabled: true,
+ environment_scope: '*',
+ name: 'kubernetes-cluster-1',
+ provider_type: 'user',
+ status: 'connected',
+ path: '/root/autodevops-deploy/-/clusters/15',
+ gitlab_managed_apps_logs_path: '/root/autodevops-deploy/-/logs?cluster_id=15',
+ },
+];
+
export const mockPodName = 'production-764c58d697-aaaaa';
export const mockPods = [
mockPodName,
diff --git a/spec/frontend/logs/stores/actions_spec.js b/spec/frontend/logs/stores/actions_spec.js
index e2e3c3d23c6..acd9536a682 100644
--- a/spec/frontend/logs/stores/actions_spec.js
+++ b/spec/frontend/logs/stores/actions_spec.js
@@ -11,6 +11,7 @@ import {
fetchEnvironments,
fetchLogs,
fetchMoreLogsPrepend,
+ fetchManagedApps,
} from '~/logs/stores/actions';
import { defaultTimeRange } from '~/vue_shared/constants';
@@ -30,6 +31,8 @@ import {
mockResponse,
mockCursor,
mockNextCursor,
+ mockManagedApps,
+ mockManagedAppsEndpoint,
} from '../mock_data';
import { TOKEN_TYPE_POD_NAME } from '~/logs/constants';
@@ -217,6 +220,30 @@ describe('Logs Store actions', () => {
});
});
+ describe('fetchManagedApps', () => {
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ it('should commit RECEIVE_MANAGED_APPS_DATA_SUCCESS mutation on succesful fetch', () => {
+ mock.onGet(mockManagedAppsEndpoint).replyOnce(200, { clusters: mockManagedApps });
+ return testAction(fetchManagedApps, mockManagedAppsEndpoint, state, [
+ { type: types.RECEIVE_MANAGED_APPS_DATA_SUCCESS, payload: mockManagedApps },
+ ]);
+ });
+
+ it('should commit RECEIVE_MANAGED_APPS_DATA_ERROR on wrong data', () => {
+ mock.onGet(mockManagedAppsEndpoint).replyOnce(500);
+ return testAction(
+ fetchManagedApps,
+ mockManagedAppsEndpoint,
+ state,
+ [{ type: types.RECEIVE_MANAGED_APPS_DATA_ERROR }],
+ [],
+ );
+ });
+ });
+
describe('when the backend responds succesfully', () => {
let expectedMutations;
let expectedActions;
diff --git a/spec/frontend/logs/stores/mutations_spec.js b/spec/frontend/logs/stores/mutations_spec.js
index 46561055a4a..137533f02d7 100644
--- a/spec/frontend/logs/stores/mutations_spec.js
+++ b/spec/frontend/logs/stores/mutations_spec.js
@@ -11,6 +11,8 @@ import {
mockSearch,
mockCursor,
mockNextCursor,
+ mockManagedApps,
+ mockManagedAppName,
} from '../mock_data';
describe('Logs Store Mutations', () => {
@@ -30,6 +32,15 @@ describe('Logs Store Mutations', () => {
it('sets the environment', () => {
mutations[types.SET_PROJECT_ENVIRONMENT](state, mockEnvName);
expect(state.environments.current).toEqual(mockEnvName);
+ expect(state.managedApps.current).toBe(null);
+ });
+ });
+
+ describe('SET_MANAGED_APP', () => {
+ it('sets the managed app', () => {
+ mutations[types.SET_MANAGED_APP](state, mockManagedAppName);
+ expect(state.managedApps.current).toBe(mockManagedAppName);
+ expect(state.environments.current).toBe(null);
});
});
@@ -254,4 +265,28 @@ describe('Logs Store Mutations', () => {
);
});
});
+
+ describe('RECEIVE_MANAGED_APPS_DATA_SUCCESS', () => {
+ it('receives managed apps data success', () => {
+ expect(state.managedApps.options).toEqual([]);
+
+ mutations[types.RECEIVE_MANAGED_APPS_DATA_SUCCESS](state, mockManagedApps);
+
+ expect(state.managedApps.options).toEqual(mockManagedApps);
+ expect(state.managedApps.isLoading).toBe(false);
+ });
+ });
+
+ describe('RECEIVE_MANAGED_APPS_DATA_ERROR', () => {
+ it('received managed apps data error', () => {
+ mutations[types.RECEIVE_MANAGED_APPS_DATA_ERROR](state);
+
+ expect(state.managedApps).toEqual({
+ options: [],
+ isLoading: false,
+ current: null,
+ fetchError: true,
+ });
+ });
+ });
});
diff --git a/spec/frontend/merge_request_tabs_spec.js b/spec/frontend/merge_request_tabs_spec.js
index 3d3be647d12..ad373d04ec0 100644
--- a/spec/frontend/merge_request_tabs_spec.js
+++ b/spec/frontend/merge_request_tabs_spec.js
@@ -5,7 +5,7 @@ import MergeRequestTabs from '~/merge_request_tabs';
import '~/commit/pipelines/pipelines_bundle';
import '~/lib/utils/common_utils';
import 'vendor/jquery.scrollTo';
-import initMrPage from '../javascripts/helpers/init_vue_mr_page_helper';
+import initMrPage from 'helpers/init_vue_mr_page_helper';
jest.mock('~/lib/utils/webpack', () => ({
resetServiceWorkersPublicPath: jest.fn(),
diff --git a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
index 4b08163f30a..e7c51d82cd2 100644
--- a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
+++ b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
@@ -4,22 +4,32 @@ exports[`Dashboard template matches the default snapshot 1`] = `
<div
class="prometheus-graphs"
data-qa-selector="prometheus_graphs"
+ environmentstate="available"
+ metricsdashboardbasepath="/monitoring/monitor-project/-/environments/1/metrics"
+ metricsendpoint="/monitoring/monitor-project/-/environments/1/additional_metrics.json"
+ prometheusstatus=""
>
<div
class="prometheus-graphs-header d-sm-flex flex-sm-wrap pt-2 pr-1 pb-0 pl-2 border-bottom bg-gray-light"
>
<div
- class="mb-2 pr-2 d-flex d-sm-block"
+ class="mb-2 mr-2 d-flex d-sm-block"
>
<dashboards-dropdown-stub
class="flex-grow-1"
data-qa-selector="dashboards_filter_dropdown"
defaultbranch="master"
id="monitor-dashboards-dropdown"
+ modalid="duplicateDashboard"
toggle-class="dropdown-menu-toggle"
/>
</div>
+ <span
+ aria-hidden="true"
+ class="gl-pl-3 border-left gl-mb-3 d-none d-sm-block"
+ />
+
<div
class="mb-2 pr-2 d-flex d-sm-block"
>
@@ -80,17 +90,7 @@ exports[`Dashboard template matches the default snapshot 1`] = `
<div
class="mb-2 pr-2 d-flex d-sm-block"
>
- <gl-deprecated-button-stub
- class="flex-grow-1"
- size="md"
- title="Refresh dashboard"
- variant="default"
- >
- <icon-stub
- name="retry"
- size="16"
- />
- </gl-deprecated-button-stub>
+ <refresh-button-stub />
</div>
<div
@@ -127,23 +127,30 @@ exports[`Dashboard template matches the default snapshot 1`] = `
<!---->
<!---->
+
+ <!---->
+
+ <!---->
+
+ <!---->
</div>
+
+ <duplicate-dashboard-modal-stub
+ defaultbranch="master"
+ modalid="duplicateDashboard"
+ />
</div>
- <!---->
-
- <!---->
-
<empty-state-stub
- clusterspath="/path/to/clusters"
- documentationpath="/path/to/docs"
- emptygettingstartedsvgpath="/path/to/getting-started.svg"
- emptyloadingsvgpath="/path/to/loading.svg"
- emptynodatasmallsvgpath="/path/to/no-data-small.svg"
- emptynodatasvgpath="/path/to/no-data.svg"
- emptyunabletoconnectsvgpath="/path/to/unable-to-connect.svg"
+ clusterspath="/monitoring/monitor-project/-/clusters"
+ documentationpath="/help/administration/monitoring/prometheus/index.md"
+ emptygettingstartedsvgpath="/images/illustrations/monitoring/getting_started.svg"
+ emptyloadingsvgpath="/images/illustrations/monitoring/loading.svg"
+ emptynodatasmallsvgpath="/images/illustrations/chart-empty-state-small.svg"
+ emptynodatasvgpath="/images/illustrations/monitoring/no_data.svg"
+ emptyunabletoconnectsvgpath="/images/illustrations/monitoring/unable_to_connect.svg"
selectedstate="gettingStarted"
- settingspath="/path/to/settings"
+ settingspath="/monitoring/monitor-project/-/services/prometheus/edit"
/>
</div>
`;
diff --git a/spec/frontend/monitoring/components/__snapshots__/empty_state_spec.js.snap b/spec/frontend/monitoring/components/__snapshots__/empty_state_spec.js.snap
index 31b3ad1bd76..4f8a82692b8 100644
--- a/spec/frontend/monitoring/components/__snapshots__/empty_state_spec.js.snap
+++ b/spec/frontend/monitoring/components/__snapshots__/empty_state_spec.js.snap
@@ -1,37 +1,49 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EmptyState shows gettingStarted state 1`] = `
-<gl-empty-state-stub
- description="Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments."
- primarybuttonlink="/clustersPath"
- primarybuttontext="Install on clusters"
- secondarybuttonlink="/settingsPath"
- secondarybuttontext="Configure existing installation"
- svgpath="/path/to/getting-started.svg"
- title="Get started with performance monitoring"
-/>
+<div>
+ <!---->
+
+ <gl-empty-state-stub
+ description="Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments."
+ primarybuttonlink="/clustersPath"
+ primarybuttontext="Install on clusters"
+ secondarybuttonlink="/settingsPath"
+ secondarybuttontext="Configure existing installation"
+ svgpath="/path/to/getting-started.svg"
+ title="Get started with performance monitoring"
+ />
+</div>
`;
-exports[`EmptyState shows loading state 1`] = `
-<gl-empty-state-stub
- description="Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available."
- primarybuttonlink="/documentationPath"
- primarybuttontext="View documentation"
- secondarybuttonlink=""
- secondarybuttontext=""
- svgpath="/path/to/loading.svg"
- title="Waiting for performance data"
-/>
+exports[`EmptyState shows noData state 1`] = `
+<div>
+ <!---->
+
+ <gl-empty-state-stub
+ description="You are connected to the Prometheus server, but there is currently no data to display."
+ primarybuttonlink="/settingsPath"
+ primarybuttontext="Configure Prometheus"
+ secondarybuttonlink=""
+ secondarybuttontext=""
+ svgpath="/path/to/no-data.svg"
+ title="No data found"
+ />
+</div>
`;
exports[`EmptyState shows unableToConnect state 1`] = `
-<gl-empty-state-stub
- description="Ensure connectivity is available from the GitLab server to the Prometheus server"
- primarybuttonlink="/documentationPath"
- primarybuttontext="View documentation"
- secondarybuttonlink="/settingsPath"
- secondarybuttontext="Configure Prometheus"
- svgpath="/path/to/unable-to-connect.svg"
- title="Unable to connect to Prometheus server"
-/>
+<div>
+ <!---->
+
+ <gl-empty-state-stub
+ description="Ensure connectivity is available from the GitLab server to the Prometheus server"
+ primarybuttonlink="/documentationPath"
+ primarybuttontext="View documentation"
+ secondarybuttonlink="/settingsPath"
+ secondarybuttontext="Configure Prometheus"
+ svgpath="/path/to/unable-to-connect.svg"
+ title="Unable to connect to Prometheus server"
+ />
+</div>
`;
diff --git a/spec/frontend/monitoring/components/charts/anomaly_spec.js b/spec/frontend/monitoring/components/charts/anomaly_spec.js
index 4178d3f0d2d..15a52d03bcd 100644
--- a/spec/frontend/monitoring/components/charts/anomaly_spec.js
+++ b/spec/frontend/monitoring/components/charts/anomaly_spec.js
@@ -3,28 +3,14 @@ import { TEST_HOST } from 'helpers/test_constants';
import Anomaly from '~/monitoring/components/charts/anomaly.vue';
import { colorValues } from '~/monitoring/constants';
-import {
- anomalyDeploymentData,
- mockProjectDir,
- anomalyMockGraphData,
- anomalyMockResultValues,
-} from '../../mock_data';
+import { anomalyDeploymentData, mockProjectDir } from '../../mock_data';
+import { anomalyGraphData } from '../../graph_data';
import MonitorTimeSeriesChart from '~/monitoring/components/charts/time_series.vue';
const mockProjectPath = `${TEST_HOST}${mockProjectDir}`;
-const makeAnomalyGraphData = (datasetName, template = anomalyMockGraphData) => {
- const metrics = anomalyMockResultValues[datasetName].map((values, index) => ({
- ...template.metrics[index],
- result: [
- {
- metrics: {},
- values,
- },
- ],
- }));
- return { ...template, metrics };
-};
+const TEST_UPPER = 11;
+const TEST_LOWER = 9;
describe('Anomaly chart component', () => {
let wrapper;
@@ -38,13 +24,22 @@ describe('Anomaly chart component', () => {
const getTimeSeriesProps = () => findTimeSeries().props();
describe('wrapped monitor-time-series-chart component', () => {
- const dataSetName = 'noAnomaly';
- const dataSet = anomalyMockResultValues[dataSetName];
+ const mockValues = ['10', '10', '10'];
+
+ const mockGraphData = anomalyGraphData(
+ {},
+ {
+ upper: mockValues.map(() => String(TEST_UPPER)),
+ values: mockValues,
+ lower: mockValues.map(() => String(TEST_LOWER)),
+ },
+ );
+
const inputThresholds = ['some threshold'];
beforeEach(() => {
setupAnomalyChart({
- graphData: makeAnomalyGraphData(dataSetName),
+ graphData: mockGraphData,
deploymentData: anomalyDeploymentData,
thresholds: inputThresholds,
projectPath: mockProjectPath,
@@ -65,21 +60,21 @@ describe('Anomaly chart component', () => {
it('receives "metric" with all data', () => {
const { graphData } = getTimeSeriesProps();
- const query = graphData.metrics[0];
- const expectedQuery = makeAnomalyGraphData(dataSetName).metrics[0];
- expect(query).toEqual(expectedQuery);
+ const metric = graphData.metrics[0];
+ const expectedMetric = mockGraphData.metrics[0];
+ expect(metric).toEqual(expectedMetric);
});
it('receives the "metric" results', () => {
const { graphData } = getTimeSeriesProps();
const { result } = graphData.metrics[0];
const { values } = result[0];
- const [metricDataset] = dataSet;
- expect(values).toEqual(expect.any(Array));
- values.forEach(([, y], index) => {
- expect(y).toBeCloseTo(metricDataset[index][1]);
- });
+ expect(values).toEqual([
+ [expect.any(String), 10],
+ [expect.any(String), 10],
+ [expect.any(String), 10],
+ ]);
});
});
@@ -108,14 +103,13 @@ describe('Anomaly chart component', () => {
it('upper boundary values are stacked on top of lower boundary', () => {
const [lowerSeries, upperSeries] = series;
- const [, upperDataset, lowerDataset] = dataSet;
- lowerSeries.data.forEach(([, y], i) => {
- expect(y).toBeCloseTo(lowerDataset[i][1]);
+ lowerSeries.data.forEach(([, y]) => {
+ expect(y).toBeCloseTo(TEST_LOWER);
});
- upperSeries.data.forEach(([, y], i) => {
- expect(y).toBeCloseTo(upperDataset[i][1] - lowerDataset[i][1]);
+ upperSeries.data.forEach(([, y]) => {
+ expect(y).toBeCloseTo(TEST_UPPER - TEST_LOWER);
});
});
});
@@ -140,11 +134,10 @@ describe('Anomaly chart component', () => {
}),
);
});
+
it('does not display anomalies', () => {
const { symbolSize, itemStyle } = seriesConfig;
- const [metricDataset] = dataSet;
-
- metricDataset.forEach((v, dataIndex) => {
+ mockValues.forEach((v, dataIndex) => {
const size = symbolSize(null, { dataIndex });
const color = itemStyle.color({ dataIndex });
@@ -155,9 +148,10 @@ describe('Anomaly chart component', () => {
});
it('can format y values (to use in tooltips)', () => {
- expect(parseFloat(wrapper.vm.yValueFormatted(0, 0))).toEqual(dataSet[0][0][1]);
- expect(parseFloat(wrapper.vm.yValueFormatted(1, 0))).toEqual(dataSet[1][0][1]);
- expect(parseFloat(wrapper.vm.yValueFormatted(2, 0))).toEqual(dataSet[2][0][1]);
+ mockValues.forEach((v, dataIndex) => {
+ const formatted = wrapper.vm.yValueFormatted(0, dataIndex);
+ expect(parseFloat(formatted)).toEqual(parseFloat(v));
+ });
});
});
@@ -179,12 +173,18 @@ describe('Anomaly chart component', () => {
});
describe('with no boundary data', () => {
- const dataSetName = 'noBoundary';
- const dataSet = anomalyMockResultValues[dataSetName];
+ const noBoundaryData = anomalyGraphData(
+ {},
+ {
+ upper: [],
+ values: ['10', '10', '10'],
+ lower: [],
+ },
+ );
beforeEach(() => {
setupAnomalyChart({
- graphData: makeAnomalyGraphData(dataSetName),
+ graphData: noBoundaryData,
deploymentData: anomalyDeploymentData,
});
});
@@ -204,7 +204,7 @@ describe('Anomaly chart component', () => {
});
it('can format y values (to use in tooltips)', () => {
- expect(parseFloat(wrapper.vm.yValueFormatted(0, 0))).toEqual(dataSet[0][0][1]);
+ expect(parseFloat(wrapper.vm.yValueFormatted(0, 0))).toEqual(10);
expect(wrapper.vm.yValueFormatted(1, 0)).toBe(''); // missing boundary
expect(wrapper.vm.yValueFormatted(2, 0)).toBe(''); // missing boundary
});
@@ -212,12 +212,20 @@ describe('Anomaly chart component', () => {
});
describe('with one anomaly', () => {
- const dataSetName = 'oneAnomaly';
- const dataSet = anomalyMockResultValues[dataSetName];
+ const mockValues = ['10', '20', '10'];
+
+ const oneAnomalyData = anomalyGraphData(
+ {},
+ {
+ upper: mockValues.map(() => TEST_UPPER),
+ values: mockValues,
+ lower: mockValues.map(() => TEST_LOWER),
+ },
+ );
beforeEach(() => {
setupAnomalyChart({
- graphData: makeAnomalyGraphData(dataSetName),
+ graphData: oneAnomalyData,
deploymentData: anomalyDeploymentData,
});
});
@@ -226,13 +234,12 @@ describe('Anomaly chart component', () => {
it('displays one anomaly', () => {
const { seriesConfig } = getTimeSeriesProps();
const { symbolSize, itemStyle } = seriesConfig;
- const [metricDataset] = dataSet;
- const bigDots = metricDataset.filter((v, dataIndex) => {
+ const bigDots = mockValues.filter((v, dataIndex) => {
const size = symbolSize(null, { dataIndex });
return size > 0.1;
});
- const redDots = metricDataset.filter((v, dataIndex) => {
+ const redDots = mockValues.filter((v, dataIndex) => {
const color = itemStyle.color({ dataIndex });
return color === colorValues.anomalySymbol;
});
@@ -244,13 +251,21 @@ describe('Anomaly chart component', () => {
});
describe('with offset', () => {
- const dataSetName = 'negativeBoundary';
- const dataSet = anomalyMockResultValues[dataSetName];
- const expectedOffset = 4; // Lowst point in mock data is -3.70, it gets rounded
+ const mockValues = ['10', '11', '12'];
+ const mockUpper = ['20', '20', '20'];
+ const mockLower = ['-1', '-2', '-3.70'];
+ const expectedOffset = 4; // Lowest point in mock data is -3.70, it gets rounded
beforeEach(() => {
setupAnomalyChart({
- graphData: makeAnomalyGraphData(dataSetName),
+ graphData: anomalyGraphData(
+ {},
+ {
+ upper: mockUpper,
+ values: mockValues,
+ lower: mockLower,
+ },
+ ),
deploymentData: anomalyDeploymentData,
});
});
@@ -266,11 +281,11 @@ describe('Anomaly chart component', () => {
const { graphData } = getTimeSeriesProps();
const { result } = graphData.metrics[0];
const { values } = result[0];
- const [metricDataset] = dataSet;
+
expect(values).toEqual(expect.any(Array));
values.forEach(([, y], index) => {
- expect(y).toBeCloseTo(metricDataset[index][1] + expectedOffset);
+ expect(y).toBeCloseTo(parseFloat(mockValues[index]) + expectedOffset);
});
});
});
@@ -281,14 +296,12 @@ describe('Anomaly chart component', () => {
const { option } = getTimeSeriesProps();
const { series } = option;
const [lowerSeries, upperSeries] = series;
- const [, upperDataset, lowerDataset] = dataSet;
-
lowerSeries.data.forEach(([, y], i) => {
- expect(y).toBeCloseTo(lowerDataset[i][1] + expectedOffset);
+ expect(y).toBeCloseTo(parseFloat(mockLower[i]) + expectedOffset);
});
upperSeries.data.forEach(([, y], i) => {
- expect(y).toBeCloseTo(upperDataset[i][1] - lowerDataset[i][1]);
+ expect(y).toBeCloseTo(parseFloat(mockUpper[i] - mockLower[i]));
});
});
});
diff --git a/spec/frontend/monitoring/components/charts/column_spec.js b/spec/frontend/monitoring/components/charts/column_spec.js
index 89739a7485d..a2056d96dcf 100644
--- a/spec/frontend/monitoring/components/charts/column_spec.js
+++ b/spec/frontend/monitoring/components/charts/column_spec.js
@@ -63,8 +63,8 @@ describe('Column component', () => {
return formatter(date);
};
- it('x-axis is formatted correctly in AM/PM format', () => {
- expect(useXAxisFormatter(mockDate)).toEqual('8:00 PM');
+ it('x-axis is formatted correctly in m/d h:MM TT format', () => {
+ expect(useXAxisFormatter(mockDate)).toEqual('5/26 8:00 PM');
});
describe('when in PT timezone', () => {
@@ -78,17 +78,17 @@ describe('Column component', () => {
it('by default, values are formatted in PT', () => {
createWrapper();
- expect(useXAxisFormatter(mockDate)).toEqual('1:00 PM');
+ expect(useXAxisFormatter(mockDate)).toEqual('5/26 1:00 PM');
});
it('when the chart uses local timezone, y-axis is formatted in PT', () => {
createWrapper({ timezone: 'LOCAL' });
- expect(useXAxisFormatter(mockDate)).toEqual('1:00 PM');
+ expect(useXAxisFormatter(mockDate)).toEqual('5/26 1:00 PM');
});
it('when the chart uses UTC, y-axis is formatted in UTC', () => {
createWrapper({ timezone: 'UTC' });
- expect(useXAxisFormatter(mockDate)).toEqual('8:00 PM');
+ expect(useXAxisFormatter(mockDate)).toEqual('5/26 8:00 PM');
});
});
});
diff --git a/spec/frontend/monitoring/components/charts/single_stat_spec.js b/spec/frontend/monitoring/components/charts/single_stat_spec.js
index 9cc5970da82..3783b1eebd2 100644
--- a/spec/frontend/monitoring/components/charts/single_stat_spec.js
+++ b/spec/frontend/monitoring/components/charts/single_stat_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import SingleStatChart from '~/monitoring/components/charts/single_stat.vue';
-import { singleStatMetricsResult } from '../../mock_data';
+import { singleStatGraphData } from '../../graph_data';
describe('Single Stat Chart component', () => {
let singleStatChart;
@@ -8,7 +8,7 @@ describe('Single Stat Chart component', () => {
beforeEach(() => {
singleStatChart = shallowMount(SingleStatChart, {
propsData: {
- graphData: singleStatMetricsResult,
+ graphData: singleStatGraphData({}, { unit: 'MB' }),
},
});
});
@@ -20,15 +20,12 @@ describe('Single Stat Chart component', () => {
describe('computed', () => {
describe('statValue', () => {
it('should interpolate the value and unit props', () => {
- expect(singleStatChart.vm.statValue).toBe('91.00MB');
+ expect(singleStatChart.vm.statValue).toBe('1.00MB');
});
it('should change the value representation to a percentile one', () => {
singleStatChart.setProps({
- graphData: {
- ...singleStatMetricsResult,
- maxValue: 120,
- },
+ graphData: singleStatGraphData({ max_value: 120 }, { value: 91 }),
});
expect(singleStatChart.vm.statValue).toContain('75.83%');
@@ -36,10 +33,7 @@ describe('Single Stat Chart component', () => {
it('should display NaN for non numeric maxValue values', () => {
singleStatChart.setProps({
- graphData: {
- ...singleStatMetricsResult,
- maxValue: 'not a number',
- },
+ graphData: singleStatGraphData({ max_value: 'not a number' }),
});
expect(singleStatChart.vm.statValue).toContain('NaN');
@@ -47,25 +41,33 @@ describe('Single Stat Chart component', () => {
it('should display NaN for missing query values', () => {
singleStatChart.setProps({
- graphData: {
- ...singleStatMetricsResult,
- metrics: [
- {
- ...singleStatMetricsResult.metrics[0],
- result: [
- {
- ...singleStatMetricsResult.metrics[0].result[0],
- value: [''],
- },
- ],
- },
- ],
- maxValue: 120,
- },
+ graphData: singleStatGraphData({ max_value: 120 }, { value: 'NaN' }),
});
expect(singleStatChart.vm.statValue).toContain('NaN');
});
+
+ describe('field attribute', () => {
+ it('displays a label value instead of metric value when field attribute is used', () => {
+ singleStatChart.setProps({
+ graphData: singleStatGraphData({ field: 'job' }, { isVector: true }),
+ });
+
+ return singleStatChart.vm.$nextTick(() => {
+ expect(singleStatChart.vm.statValue).toContain('prometheus');
+ });
+ });
+
+ it('displays No data to display if field attribute is not present', () => {
+ singleStatChart.setProps({
+ graphData: singleStatGraphData({ field: 'this-does-not-exist' }),
+ });
+
+ return singleStatChart.vm.$nextTick(() => {
+ expect(singleStatChart.vm.statValue).toContain('No data to display');
+ });
+ });
+ });
});
});
});
diff --git a/spec/frontend/monitoring/components/charts/time_series_spec.js b/spec/frontend/monitoring/components/charts/time_series_spec.js
index 50d2c9c80b2..97386be9e32 100644
--- a/spec/frontend/monitoring/components/charts/time_series_spec.js
+++ b/spec/frontend/monitoring/components/charts/time_series_spec.js
@@ -9,18 +9,12 @@ import {
GlChartSeriesLabel,
GlChartLegend,
} from '@gitlab/ui/dist/charts';
-import { cloneDeep } from 'lodash';
import { shallowWrapperContainsSlotText } from 'helpers/vue_test_utils_helper';
-import { createStore } from '~/monitoring/stores';
import { panelTypes, chartHeight } from '~/monitoring/constants';
import TimeSeries from '~/monitoring/components/charts/time_series.vue';
-import * as types from '~/monitoring/stores/mutation_types';
import { deploymentData, mockProjectDir, annotationsData } from '../../mock_data';
-import {
- metricsDashboardPayload,
- metricsDashboardViewModel,
- metricResultStatus,
-} from '../../fixture_data';
+
+import { timeSeriesGraphData } from '../../graph_data';
jest.mock('lodash/throttle', () =>
// this throttle mock executes immediately
@@ -35,23 +29,21 @@ jest.mock('~/lib/utils/icon_utils', () => ({
}));
describe('Time series component', () => {
- let mockGraphData;
- let store;
+ const defaultGraphData = timeSeriesGraphData();
let wrapper;
const createWrapper = (
- { graphData = mockGraphData, ...props } = {},
+ { graphData = defaultGraphData, ...props } = {},
mountingMethod = shallowMount,
) => {
wrapper = mountingMethod(TimeSeries, {
propsData: {
graphData,
- deploymentData: store.state.monitoringDashboard.deploymentData,
- annotations: store.state.monitoringDashboard.annotations,
+ deploymentData,
+ annotations: annotationsData,
projectPath: `${TEST_HOST}${mockProjectDir}`,
...props,
},
- store,
stubs: {
GlPopover: true,
},
@@ -59,27 +51,15 @@ describe('Time series component', () => {
});
};
- describe('With a single time series', () => {
- beforeEach(() => {
- setTestTimeout(1000);
-
- store = createStore();
-
- store.commit(
- `monitoringDashboard/${types.RECEIVE_METRICS_DASHBOARD_SUCCESS}`,
- metricsDashboardPayload,
- );
-
- store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
+ beforeEach(() => {
+ setTestTimeout(1000);
+ });
- store.commit(
- `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`,
- metricResultStatus,
- );
- // dashboard is a dynamically generated fixture and stored at environment_metrics_dashboard.json
- [mockGraphData] = store.state.monitoringDashboard.dashboard.panelGroups[1].panels;
- });
+ afterEach(() => {
+ wrapper.destroy();
+ });
+ describe('With a single time series', () => {
describe('general functions', () => {
const findChart = () => wrapper.find({ ref: 'chart' });
@@ -88,10 +68,6 @@ describe('Time series component', () => {
return wrapper.vm.$nextTick();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('allows user to override legend label texts using props', () => {
const legendRelatedProps = {
legendMinText: 'legendMinText',
@@ -231,19 +207,20 @@ describe('Time series component', () => {
});
it('formats tooltip content', () => {
- const name = 'Status Code';
+ const name = 'Metric 1';
const value = '5.556';
const dataIndex = 0;
const seriesLabel = wrapper.find(GlChartSeriesLabel);
expect(seriesLabel.vm.color).toBe('');
+
expect(shallowWrapperContainsSlotText(seriesLabel, 'default', name)).toBe(true);
expect(wrapper.vm.tooltip.content).toEqual([
{ name, value, dataIndex, color: undefined },
]);
expect(
- shallowWrapperContainsSlotText(wrapper.find(GlAreaChart), 'tooltipContent', value),
+ shallowWrapperContainsSlotText(wrapper.find(GlLineChart), 'tooltipContent', value),
).toBe(true);
});
@@ -385,10 +362,8 @@ describe('Time series component', () => {
});
it('utilizes all data points', () => {
- const { values } = mockGraphData.metrics[0].result[0];
-
expect(chartData.length).toBe(1);
- expect(seriesData().data.length).toBe(values.length);
+ expect(seriesData().data.length).toBe(3);
});
it('creates valid data', () => {
@@ -552,8 +527,8 @@ describe('Time series component', () => {
return formatter(date);
};
- it('x-axis is formatted correctly in AM/PM format', () => {
- expect(useXAxisFormatter(mockDate)).toEqual('8:00 PM');
+ it('x-axis is formatted correctly in m/d h:MM TT format', () => {
+ expect(useXAxisFormatter(mockDate)).toEqual('5/26 8:00 PM');
});
describe('when in PT timezone', () => {
@@ -567,17 +542,17 @@ describe('Time series component', () => {
it('by default, values are formatted in PT', () => {
createWrapper();
- expect(useXAxisFormatter(mockDate)).toEqual('1:00 PM');
+ expect(useXAxisFormatter(mockDate)).toEqual('5/26 1:00 PM');
});
it('when the chart uses local timezone, y-axis is formatted in PT', () => {
createWrapper({ timezone: 'LOCAL' });
- expect(useXAxisFormatter(mockDate)).toEqual('1:00 PM');
+ expect(useXAxisFormatter(mockDate)).toEqual('5/26 1:00 PM');
});
it('when the chart uses UTC, y-axis is formatted in UTC', () => {
createWrapper({ timezone: 'UTC' });
- expect(useXAxisFormatter(mockDate)).toEqual('8:00 PM');
+ expect(useXAxisFormatter(mockDate)).toEqual('5/26 8:00 PM');
});
});
});
@@ -602,14 +577,10 @@ describe('Time series component', () => {
it('constructs a label for the chart y-axis', () => {
const { yAxis } = getChartOptions();
- expect(yAxis[0].name).toBe('Requests / Sec');
+ expect(yAxis[0].name).toBe('Y Axis');
});
});
});
-
- afterEach(() => {
- wrapper.destroy();
- });
});
describe('wrapped components', () => {
@@ -630,7 +601,7 @@ describe('Time series component', () => {
beforeEach(() => {
createWrapper(
- { graphData: { ...mockGraphData, type: dynamicComponent.chartType } },
+ { graphData: timeSeriesGraphData({ type: dynamicComponent.chartType }) },
mount,
);
return wrapper.vm.$nextTick();
@@ -700,20 +671,12 @@ describe('Time series component', () => {
describe('with multiple time series', () => {
describe('General functions', () => {
beforeEach(() => {
- store = createStore();
- const graphData = cloneDeep(metricsDashboardViewModel.panelGroups[0].panels[3]);
- graphData.metrics.forEach(metric =>
- Object.assign(metric, { result: metricResultStatus.result }),
- );
+ const graphData = timeSeriesGraphData({ type: panelTypes.AREA_CHART, multiMetric: true });
- createWrapper({ graphData: { ...graphData, type: 'area-chart' } }, mount);
+ createWrapper({ graphData }, mount);
return wrapper.vm.$nextTick();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Color match', () => {
let lineColors;
@@ -754,14 +717,10 @@ describe('Time series component', () => {
const findLegend = () => wrapper.find(GlChartLegend);
beforeEach(() => {
- createWrapper(mockGraphData, mount);
+ createWrapper({}, mount);
return wrapper.vm.$nextTick();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render a tabular legend layout by default', () => {
expect(findLegend().props('layout')).toBe('table');
});
diff --git a/spec/frontend/monitoring/components/create_dashboard_modal_spec.js b/spec/frontend/monitoring/components/create_dashboard_modal_spec.js
new file mode 100644
index 00000000000..d1028445638
--- /dev/null
+++ b/spec/frontend/monitoring/components/create_dashboard_modal_spec.js
@@ -0,0 +1,48 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlModal } from '@gitlab/ui';
+import CreateDashboardModal from '~/monitoring/components/create_dashboard_modal.vue';
+
+describe('Create dashboard modal', () => {
+ let wrapper;
+
+ const defaultProps = {
+ modalId: 'id',
+ projectPath: 'https://localhost/',
+ addDashboardDocumentationPath: 'https://link/to/docs',
+ };
+
+ const findDocsButton = () => wrapper.find('[data-testid="create-dashboard-modal-docs-button"]');
+ const findRepoButton = () => wrapper.find('[data-testid="create-dashboard-modal-repo-button"]');
+
+ const createWrapper = (props = {}, options = {}) => {
+ wrapper = shallowMount(CreateDashboardModal, {
+ propsData: { ...defaultProps, ...props },
+ stubs: {
+ GlModal,
+ },
+ ...options,
+ });
+ };
+
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('has button that links to the project url', () => {
+ findRepoButton().trigger('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findRepoButton().exists()).toBe(true);
+ expect(findRepoButton().attributes('href')).toBe(defaultProps.projectPath);
+ });
+ });
+
+ it('has button that links to the docs', () => {
+ expect(findDocsButton().exists()).toBe(true);
+ expect(findDocsButton().attributes('href')).toBe(defaultProps.addDashboardDocumentationPath);
+ });
+});
diff --git a/spec/frontend/monitoring/components/dashboard_header_spec.js b/spec/frontend/monitoring/components/dashboard_header_spec.js
new file mode 100644
index 00000000000..5a1a615c703
--- /dev/null
+++ b/spec/frontend/monitoring/components/dashboard_header_spec.js
@@ -0,0 +1,232 @@
+import { shallowMount } from '@vue/test-utils';
+import { createStore } from '~/monitoring/stores';
+import DashboardHeader from '~/monitoring/components/dashboard_header.vue';
+import DuplicateDashboardModal from '~/monitoring/components/duplicate_dashboard_modal.vue';
+import CreateDashboardModal from '~/monitoring/components/create_dashboard_modal.vue';
+import { setupAllDashboards } from '../store_utils';
+import {
+ dashboardGitResponse,
+ selfMonitoringDashboardGitResponse,
+ dashboardHeaderProps,
+} from '../mock_data';
+import { redirectTo } from '~/lib/utils/url_utility';
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ redirectTo: jest.fn(),
+ queryToObject: jest.fn(),
+ mergeUrlParams: jest.requireActual('~/lib/utils/url_utility').mergeUrlParams,
+}));
+
+describe('Dashboard header', () => {
+ let store;
+ let wrapper;
+
+ const findActionsMenu = () => wrapper.find('[data-testid="actions-menu"]');
+ const findCreateDashboardMenuItem = () =>
+ findActionsMenu().find('[data-testid="action-create-dashboard"]');
+ const findCreateDashboardDuplicateItem = () =>
+ findActionsMenu().find('[data-testid="action-duplicate-dashboard"]');
+ const findDuplicateDashboardModal = () => wrapper.find(DuplicateDashboardModal);
+ const findCreateDashboardModal = () => wrapper.find('[data-testid="create-dashboard-modal"]');
+
+ const createShallowWrapper = (props = {}, options = {}) => {
+ wrapper = shallowMount(DashboardHeader, {
+ propsData: { ...dashboardHeaderProps, ...props },
+ store,
+ ...options,
+ });
+ };
+
+ beforeEach(() => {
+ store = createStore();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when a dashboard has been duplicated in the duplicate dashboard modal', () => {
+ beforeEach(() => {
+ store.state.monitoringDashboard.projectPath = 'root/sandbox';
+ });
+ /**
+ * The duplicate dashboard modal gets called both by a menu item from the
+ * dashboards dropdown and by an item from the actions menu.
+ *
+ * This spec is context agnostic, so it addresses all cases where the
+ * duplicate dashboard modal gets called.
+ */
+ it('redirects to the newly created dashboard', () => {
+ delete window.location;
+ window.location = new URL('https://localhost');
+
+ const newDashboard = dashboardGitResponse[1];
+
+ createShallowWrapper();
+
+ const newDashboardUrl = 'root/sandbox/-/metrics/dashboard.yml';
+ findDuplicateDashboardModal().vm.$emit('dashboardDuplicated', newDashboard);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(redirectTo).toHaveBeenCalled();
+ expect(redirectTo).toHaveBeenCalledWith(newDashboardUrl);
+ });
+ });
+ });
+
+ describe('actions menu', () => {
+ beforeEach(() => {
+ store.state.monitoringDashboard.projectPath = '';
+ createShallowWrapper();
+ });
+
+ it('is rendered if projectPath is set in store', () => {
+ store.state.monitoringDashboard.projectPath = 'https://path/to/project';
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findActionsMenu().exists()).toBe(true);
+ });
+ });
+
+ it('is not rendered if projectPath is not set in store', () => {
+ expect(findActionsMenu().exists()).toBe(false);
+ });
+
+ it('contains a modal', () => {
+ store.state.monitoringDashboard.projectPath = 'https://path/to/project';
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findActionsMenu().contains(CreateDashboardModal)).toBe(true);
+ });
+ });
+
+ const duplicableCases = [
+ null, // When no path is specified, it uses the default dashboard path.
+ dashboardGitResponse[0].path,
+ dashboardGitResponse[2].path,
+ selfMonitoringDashboardGitResponse[0].path,
+ ];
+
+ describe.each(duplicableCases)(
+ 'when the selected dashboard can be duplicated',
+ dashboardPath => {
+ it('contains a "Create New" menu item and a "Duplicate Dashboard" menu item', () => {
+ store.state.monitoringDashboard.projectPath = 'https://path/to/project';
+ setupAllDashboards(store, dashboardPath);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findCreateDashboardMenuItem().exists()).toBe(true);
+ expect(findCreateDashboardDuplicateItem().exists()).toBe(true);
+ });
+ });
+ },
+ );
+
+ const nonDuplicableCases = [
+ dashboardGitResponse[1].path,
+ selfMonitoringDashboardGitResponse[1].path,
+ ];
+
+ describe.each(nonDuplicableCases)(
+ 'when the selected dashboard cannot be duplicated',
+ dashboardPath => {
+ it('contains a "Create New" menu item and no "Duplicate Dashboard" menu item', () => {
+ store.state.monitoringDashboard.projectPath = 'https://path/to/project';
+ setupAllDashboards(store, dashboardPath);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findCreateDashboardMenuItem().exists()).toBe(true);
+ expect(findCreateDashboardDuplicateItem().exists()).toBe(false);
+ });
+ });
+ },
+ );
+ });
+
+ describe('actions menu modals', () => {
+ const url = 'https://path/to/project';
+
+ beforeEach(() => {
+ store.state.monitoringDashboard.projectPath = url;
+ setupAllDashboards(store);
+
+ createShallowWrapper();
+ });
+
+ it('Clicking on "Create New" opens up a modal', () => {
+ const modalId = 'createDashboard';
+ const modalTrigger = findCreateDashboardMenuItem();
+ const rootEmit = jest.spyOn(wrapper.vm.$root, '$emit');
+
+ modalTrigger.trigger('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(rootEmit.mock.calls[0]).toContainEqual(modalId);
+ });
+ });
+
+ it('"Create new dashboard" modal contains correct buttons', () => {
+ expect(findCreateDashboardModal().props('projectPath')).toBe(url);
+ });
+
+ it('"Duplicate Dashboard" opens up a modal', () => {
+ const modalId = 'duplicateDashboard';
+ const modalTrigger = findCreateDashboardDuplicateItem();
+ const rootEmit = jest.spyOn(wrapper.vm.$root, '$emit');
+
+ modalTrigger.trigger('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(rootEmit.mock.calls[0]).toContainEqual(modalId);
+ });
+ });
+ });
+
+ describe('metrics settings button', () => {
+ const findSettingsButton = () => wrapper.find('[data-testid="metrics-settings-button"]');
+ const url = 'https://path/to/project/settings';
+
+ beforeEach(() => {
+ createShallowWrapper();
+
+ store.state.monitoringDashboard.canAccessOperationsSettings = false;
+ store.state.monitoringDashboard.operationsSettingsPath = '';
+ });
+
+ it('is rendered when the user can access the project settings and path to settings is available', () => {
+ store.state.monitoringDashboard.canAccessOperationsSettings = true;
+ store.state.monitoringDashboard.operationsSettingsPath = url;
+
+ return wrapper.vm.$nextTick(() => {
+ expect(findSettingsButton().exists()).toBe(true);
+ });
+ });
+
+ it('is not rendered when the user can not access the project settings', () => {
+ store.state.monitoringDashboard.canAccessOperationsSettings = false;
+ store.state.monitoringDashboard.operationsSettingsPath = url;
+
+ return wrapper.vm.$nextTick(() => {
+ expect(findSettingsButton().exists()).toBe(false);
+ });
+ });
+
+ it('is not rendered when the path to settings is unavailable', () => {
+ store.state.monitoringDashboard.canAccessOperationsSettings = false;
+ store.state.monitoringDashboard.operationsSettingsPath = '';
+
+ return wrapper.vm.$nextTick(() => {
+ expect(findSettingsButton().exists()).toBe(false);
+ });
+ });
+
+ it('leads to the project settings page', () => {
+ store.state.monitoringDashboard.canAccessOperationsSettings = true;
+ store.state.monitoringDashboard.operationsSettingsPath = url;
+
+ return wrapper.vm.$nextTick(() => {
+ expect(findSettingsButton().attributes('href')).toBe(url);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/monitoring/components/dashboard_panel_spec.js b/spec/frontend/monitoring/components/dashboard_panel_spec.js
index 0ad6e04588f..693818aa55a 100644
--- a/spec/frontend/monitoring/components/dashboard_panel_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_panel_spec.js
@@ -9,17 +9,16 @@ import AlertWidget from '~/monitoring/components/alert_widget.vue';
import DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
import {
- anomalyMockGraphData,
mockLogsHref,
mockLogsPath,
mockNamespace,
mockNamespacedData,
mockTimeRange,
- singleStatMetricsResult,
graphDataPrometheusQueryRangeMultiTrack,
barMockData,
- propsData,
} from '../mock_data';
+import { dashboardProps, graphData, graphDataEmpty } from '../fixture_data';
+import { anomalyGraphData, singleStatGraphData } from '../graph_data';
import { panelTypes } from '~/monitoring/constants';
@@ -32,7 +31,6 @@ import MonitorColumnChart from '~/monitoring/components/charts/column.vue';
import MonitorBarChart from '~/monitoring/components/charts/bar.vue';
import MonitorStackedColumnChart from '~/monitoring/components/charts/stacked_column.vue';
-import { graphData, graphDataEmpty } from '../fixture_data';
import { createStore, monitoringDashboard } from '~/monitoring/stores';
import { createStore as createEmbedGroupStore } from '~/monitoring/stores/embed_group';
@@ -63,7 +61,7 @@ describe('Dashboard Panel', () => {
wrapper = shallowMount(DashboardPanel, {
propsData: {
graphData,
- settingsPath: propsData.settingsPath,
+ settingsPath: dashboardProps.settingsPath,
...props,
},
store,
@@ -137,10 +135,6 @@ describe('Dashboard Panel', () => {
expect(wrapper.find(MonitorEmptyChart).exists()).toBe(true);
expect(wrapper.find(MonitorEmptyChart).isVueInstance()).toBe(true);
});
-
- it('does not contain a tabindex attribute', () => {
- expect(wrapper.find(MonitorEmptyChart).contains('[tabindex]')).toBe(false);
- });
});
describe('When graphData is null', () => {
@@ -233,23 +227,32 @@ describe('Dashboard Panel', () => {
expect(wrapper.find(MonitorTimeSeriesChart).isVueInstance()).toBe(true);
});
- it.each`
- data | component
- ${dataWithType(panelTypes.AREA_CHART)} | ${MonitorTimeSeriesChart}
- ${dataWithType(panelTypes.LINE_CHART)} | ${MonitorTimeSeriesChart}
- ${anomalyMockGraphData} | ${MonitorAnomalyChart}
- ${dataWithType(panelTypes.COLUMN)} | ${MonitorColumnChart}
- ${dataWithType(panelTypes.STACKED_COLUMN)} | ${MonitorStackedColumnChart}
- ${singleStatMetricsResult} | ${MonitorSingleStatChart}
- ${graphDataPrometheusQueryRangeMultiTrack} | ${MonitorHeatmapChart}
- ${barMockData} | ${MonitorBarChart}
- `('wrapps a $data.type component binding attributes', ({ data, component }) => {
+ describe.each`
+ data | component | hasCtxMenu
+ ${dataWithType(panelTypes.AREA_CHART)} | ${MonitorTimeSeriesChart} | ${true}
+ ${dataWithType(panelTypes.LINE_CHART)} | ${MonitorTimeSeriesChart} | ${true}
+ ${singleStatGraphData()} | ${MonitorSingleStatChart} | ${true}
+ ${anomalyGraphData()} | ${MonitorAnomalyChart} | ${false}
+ ${dataWithType(panelTypes.COLUMN)} | ${MonitorColumnChart} | ${false}
+ ${dataWithType(panelTypes.STACKED_COLUMN)} | ${MonitorStackedColumnChart} | ${false}
+ ${graphDataPrometheusQueryRangeMultiTrack} | ${MonitorHeatmapChart} | ${false}
+ ${barMockData} | ${MonitorBarChart} | ${false}
+ `('when $data.type data is provided', ({ data, component, hasCtxMenu }) => {
const attrs = { attr1: 'attr1Value', attr2: 'attr2Value' };
- createWrapper({ graphData: data }, { attrs });
- expect(wrapper.find(component).exists()).toBe(true);
- expect(wrapper.find(component).isVueInstance()).toBe(true);
- expect(wrapper.find(component).attributes()).toMatchObject(attrs);
+ beforeEach(() => {
+ createWrapper({ graphData: data }, { attrs });
+ });
+
+ it(`renders the chart component and binds attributes`, () => {
+ expect(wrapper.find(component).exists()).toBe(true);
+ expect(wrapper.find(component).isVueInstance()).toBe(true);
+ expect(wrapper.find(component).attributes()).toMatchObject(attrs);
+ });
+
+ it(`contextual menu is ${hasCtxMenu ? '' : 'not '}shown`, () => {
+ expect(findCtxMenu().exists()).toBe(hasCtxMenu);
+ });
});
});
});
@@ -307,7 +310,7 @@ describe('Dashboard Panel', () => {
return wrapper.vm.$nextTick(() => {
expect(findEditCustomMetricLink().text()).toBe('Edit metrics');
- expect(findEditCustomMetricLink().attributes('href')).toBe(propsData.settingsPath);
+ expect(findEditCustomMetricLink().attributes('href')).toBe(dashboardProps.settingsPath);
});
});
});
@@ -361,7 +364,7 @@ describe('Dashboard Panel', () => {
});
});
- it('it is overriden when a datazoom event is received', () => {
+ it('it is overridden when a datazoom event is received', () => {
state.logsPath = mockLogsPath;
state.timeRange = mockTimeRange;
@@ -424,7 +427,7 @@ describe('Dashboard Panel', () => {
wrapper = shallowMount(DashboardPanel, {
propsData: {
clipboardText: exampleText,
- settingsPath: propsData.settingsPath,
+ settingsPath: dashboardProps.settingsPath,
graphData: {
y_label: 'metric',
...graphData,
@@ -474,7 +477,7 @@ describe('Dashboard Panel', () => {
wrapper = shallowMount(DashboardPanel, {
propsData: {
graphData,
- settingsPath: propsData.settingsPath,
+ settingsPath: dashboardProps.settingsPath,
namespace: mockNamespace,
},
store,
diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js
index 7bb4c68b4cd..4b7f7a9ddb3 100644
--- a/spec/frontend/monitoring/components/dashboard_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_spec.js
@@ -6,16 +6,18 @@ import { objectToQuery } from '~/lib/utils/url_utility';
import VueDraggable from 'vuedraggable';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
-import { metricStates } from '~/monitoring/constants';
+import { dashboardEmptyStates, metricStates } from '~/monitoring/constants';
import Dashboard from '~/monitoring/components/dashboard.vue';
import DashboardHeader from '~/monitoring/components/dashboard_header.vue';
+import RefreshButton from '~/monitoring/components/refresh_button.vue';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue';
import DashboardsDropdown from '~/monitoring/components/dashboards_dropdown.vue';
import EmptyState from '~/monitoring/components/empty_state.vue';
import GroupEmptyState from '~/monitoring/components/group_empty_state.vue';
import DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
+import GraphGroup from '~/monitoring/components/graph_group.vue';
import LinksSection from '~/monitoring/components/links_section.vue';
import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
@@ -24,12 +26,17 @@ import {
setupStoreWithDashboard,
setMetricResult,
setupStoreWithData,
- setupStoreWithVariable,
+ setupStoreWithDataForPanelCount,
setupStoreWithLinks,
} from '../store_utils';
-import { environmentData, dashboardGitResponse, propsData } from '../mock_data';
-import { metricsDashboardViewModel, metricsDashboardPanelCount } from '../fixture_data';
+import { environmentData, dashboardGitResponse, storeVariables } from '../mock_data';
+import {
+ metricsDashboardViewModel,
+ metricsDashboardPanelCount,
+ dashboardProps,
+} from '../fixture_data';
import createFlash from '~/flash';
+import { TEST_HOST } from 'helpers/test_constants';
jest.mock('~/flash');
@@ -48,7 +55,7 @@ describe('Dashboard', () => {
const createShallowWrapper = (props = {}, options = {}) => {
wrapper = shallowMount(Dashboard, {
- propsData: { ...propsData, ...props },
+ propsData: { ...dashboardProps, ...props },
store,
stubs: {
DashboardHeader,
@@ -59,7 +66,7 @@ describe('Dashboard', () => {
const createMountedWrapper = (props = {}, options = {}) => {
wrapper = mount(Dashboard, {
- propsData: { ...propsData, ...props },
+ propsData: { ...dashboardProps, ...props },
store,
stubs: {
'graph-group': true,
@@ -120,13 +127,13 @@ describe('Dashboard', () => {
});
it('shows up a loading state', () => {
- store.state.monitoringDashboard.emptyState = 'loading';
+ store.state.monitoringDashboard.emptyState = dashboardEmptyStates.LOADING;
createShallowWrapper({ hasMetrics: true });
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(EmptyState).exists()).toBe(true);
- expect(wrapper.find(EmptyState).props('selectedState')).toBe('loading');
+ expect(wrapper.find(EmptyState).props('selectedState')).toBe(dashboardEmptyStates.LOADING);
});
});
@@ -136,7 +143,7 @@ describe('Dashboard', () => {
setupStoreWithData(store);
return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.showEmptyState).toEqual(false);
+ expect(wrapper.vm.emptyState).toBeNull();
expect(wrapper.findAll('.prometheus-panel')).toHaveLength(0);
});
});
@@ -157,6 +164,103 @@ describe('Dashboard', () => {
});
});
+ describe('panel containers layout', () => {
+ const findPanelLayoutWrapperAt = index => {
+ return wrapper
+ .find(GraphGroup)
+ .findAll('[data-testid="dashboard-panel-layout-wrapper"]')
+ .at(index);
+ };
+
+ beforeEach(() => {
+ createMountedWrapper({ hasMetrics: true });
+
+ return wrapper.vm.$nextTick();
+ });
+
+ describe('when the graph group has an even number of panels', () => {
+ it('2 panels - all panel wrappers take half width of their parent', () => {
+ setupStoreWithDataForPanelCount(store, 2);
+
+ wrapper.vm.$nextTick(() => {
+ expect(findPanelLayoutWrapperAt(0).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(1).classes('col-lg-6')).toBe(true);
+ });
+ });
+
+ it('4 panels - all panel wrappers take half width of their parent', () => {
+ setupStoreWithDataForPanelCount(store, 4);
+
+ wrapper.vm.$nextTick(() => {
+ expect(findPanelLayoutWrapperAt(0).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(1).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(2).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(3).classes('col-lg-6')).toBe(true);
+ });
+ });
+ });
+
+ describe('when the graph group has an odd number of panels', () => {
+ it('1 panel - panel wrapper does not take half width of its parent', () => {
+ setupStoreWithDataForPanelCount(store, 1);
+
+ wrapper.vm.$nextTick(() => {
+ expect(findPanelLayoutWrapperAt(0).classes('col-lg-6')).toBe(false);
+ });
+ });
+
+ it('3 panels - all panels but last take half width of their parents', () => {
+ setupStoreWithDataForPanelCount(store, 3);
+
+ wrapper.vm.$nextTick(() => {
+ expect(findPanelLayoutWrapperAt(0).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(1).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(2).classes('col-lg-6')).toBe(false);
+ });
+ });
+
+ it('5 panels - all panels but last take half width of their parents', () => {
+ setupStoreWithDataForPanelCount(store, 5);
+
+ wrapper.vm.$nextTick(() => {
+ expect(findPanelLayoutWrapperAt(0).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(1).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(2).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(3).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(4).classes('col-lg-6')).toBe(false);
+ });
+ });
+ });
+ });
+
+ describe('dashboard validation warning', () => {
+ it('displays a warning if there are validation warnings', () => {
+ createMountedWrapper({ hasMetrics: true });
+
+ store.commit(
+ `monitoringDashboard/${types.RECEIVE_DASHBOARD_VALIDATION_WARNINGS_SUCCESS}`,
+ true,
+ );
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(createFlash).toHaveBeenCalled();
+ });
+ });
+
+ it('does not display a warning if there are no validation warnings', () => {
+ createMountedWrapper({ hasMetrics: true });
+
+ store.commit(
+ `monitoringDashboard/${types.RECEIVE_DASHBOARD_VALIDATION_WARNINGS_SUCCESS}`,
+ false,
+ );
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(createFlash).not.toHaveBeenCalled();
+ });
+ });
+ });
+
describe('when the URL contains a reference to a panel', () => {
let location;
@@ -323,12 +427,72 @@ describe('Dashboard', () => {
);
});
});
+
+ describe('when custom dashboard is selected', () => {
+ const windowLocation = window.location;
+ const findDashboardDropdown = () => wrapper.find(DashboardHeader).find(DashboardsDropdown);
+
+ beforeEach(() => {
+ store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
+ projectPath: TEST_HOST,
+ });
+
+ delete window.location;
+ window.location = { ...windowLocation, assign: jest.fn() };
+ createMountedWrapper();
+
+ return wrapper.vm.$nextTick();
+ });
+
+ afterEach(() => {
+ window.location = windowLocation;
+ });
+
+ it('encodes dashboard param', () => {
+ findDashboardDropdown().vm.$emit('selectDashboard', {
+ path: '.gitlab/dashboards/dashboard&copy.yml',
+ display_name: 'dashboard&copy.yml',
+ });
+ expect(window.location.assign).toHaveBeenCalledWith(
+ `${TEST_HOST}/-/metrics/dashboard%26copy.yml`,
+ );
+ });
+ });
+ });
+
+ describe('when all panels in the first group are loading', () => {
+ const findGroupAt = i => wrapper.findAll(GraphGroup).at(i);
+
+ beforeEach(() => {
+ setupStoreWithDashboard(store);
+
+ const { panels } = store.state.monitoringDashboard.dashboard.panelGroups[0];
+ panels.forEach(({ metrics }) => {
+ store.commit(`monitoringDashboard/${types.REQUEST_METRIC_RESULT}`, {
+ metricId: metrics[0].metricId,
+ });
+ });
+
+ createShallowWrapper();
+
+ return wrapper.vm.$nextTick();
+ });
+
+ it('a loading icon appears in the first group', () => {
+ expect(findGroupAt(0).props('isLoading')).toBe(true);
+ });
+
+ it('a loading icon does not appear in the second group', () => {
+ expect(findGroupAt(1).props('isLoading')).toBe(false);
+ });
});
describe('when all requests have been commited by the store', () => {
beforeEach(() => {
store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
currentEnvironmentName: 'production',
+ currentDashboard: dashboardGitResponse[0].path,
+ projectPath: TEST_HOST,
});
createMountedWrapper({ hasMetrics: true });
setupStoreWithData(store);
@@ -341,13 +505,26 @@ describe('Dashboard', () => {
findAllEnvironmentsDropdownItems().wrappers.forEach((itemWrapper, index) => {
const anchorEl = itemWrapper.find('a');
- if (anchorEl.exists() && environmentData[index].metrics_path) {
+ if (anchorEl.exists()) {
const href = anchorEl.attributes('href');
- expect(href).toBe(environmentData[index].metrics_path);
+ const currentDashboard = encodeURIComponent(dashboardGitResponse[0].path);
+ const environmentId = encodeURIComponent(environmentData[index].id);
+ const url = `${TEST_HOST}/-/metrics/${currentDashboard}?environment=${environmentId}`;
+ expect(href).toBe(url);
}
});
});
+ it('it does not show loading icons in any group', () => {
+ setupStoreWithData(store);
+
+ wrapper.vm.$nextTick(() => {
+ wrapper.findAll(GraphGroup).wrappers.forEach(groupWrapper => {
+ expect(groupWrapper.props('isLoading')).toBe(false);
+ });
+ });
+ });
+
// Note: This test is not working, .active does not show the active environment
// eslint-disable-next-line jest/no-disabled-tests
it.skip('renders the environments dropdown with a single active element', () => {
@@ -464,10 +641,9 @@ describe('Dashboard', () => {
setupStoreWithData(store);
return wrapper.vm.$nextTick().then(() => {
- const refreshBtn = wrapper.find(DashboardHeader).findAll({ ref: 'refreshDashboardBtn' });
+ const refreshBtn = wrapper.find(DashboardHeader).find(RefreshButton);
- expect(refreshBtn).toHaveLength(1);
- expect(refreshBtn.is(GlDeprecatedButton)).toBe(true);
+ expect(refreshBtn.exists()).toBe(true);
});
});
@@ -475,8 +651,7 @@ describe('Dashboard', () => {
beforeEach(() => {
createShallowWrapper({ hasMetrics: true });
setupStoreWithData(store);
- setupStoreWithVariable(store);
-
+ store.state.monitoringDashboard.variables = storeVariables;
return wrapper.vm.$nextTick();
});
@@ -1041,6 +1216,34 @@ describe('Dashboard', () => {
});
});
+ describe('keyboard shortcuts', () => {
+ const currentDashboard = dashboardGitResponse[1].path;
+ const panelRef = 'dashboard-panel-response-metrics-aws-elb-4-1'; // skip expanded panel
+
+ // While the recommendation in the documentation is to test
+ // with a data-testid attribute, I want to make sure that
+ // the dashboard panels have a ref attribute set.
+ const getDashboardPanel = () => wrapper.find({ ref: panelRef });
+
+ beforeEach(() => {
+ setupStoreWithData(store);
+ store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
+ currentDashboard,
+ });
+ createShallowWrapper({ hasMetrics: true });
+
+ wrapper.setData({ hoveredPanel: panelRef });
+
+ return wrapper.vm.$nextTick();
+ });
+
+ it('contains a ref attribute inside a DashboardPanel component', () => {
+ const dashboardPanel = getDashboardPanel();
+
+ expect(dashboardPanel.exists()).toBe(true);
+ });
+ });
+
describe('add custom metrics', () => {
const findAddMetricButton = () => wrapper.find(DashboardHeader).find({ ref: 'addMetricBtn' });
@@ -1082,7 +1285,7 @@ describe('Dashboard', () => {
it('uses modal for custom metrics form', () => {
expect(wrapper.find(GlModal).exists()).toBe(true);
- expect(wrapper.find(GlModal).attributes().modalid).toBe('add-metric');
+ expect(wrapper.find(GlModal).attributes().modalid).toBe('addMetric');
});
it('adding new metric is tracked', done => {
const submitButton = wrapper
diff --git a/spec/frontend/monitoring/components/dashboard_template_spec.js b/spec/frontend/monitoring/components/dashboard_template_spec.js
index a1a450d4abe..8941e57c4ce 100644
--- a/spec/frontend/monitoring/components/dashboard_template_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_template_spec.js
@@ -5,7 +5,7 @@ import Dashboard from '~/monitoring/components/dashboard.vue';
import DashboardHeader from '~/monitoring/components/dashboard_header.vue';
import { createStore } from '~/monitoring/stores';
import { setupAllDashboards } from '../store_utils';
-import { propsData } from '../mock_data';
+import { dashboardProps } from '../fixture_data';
jest.mock('~/lib/utils/url_utility');
@@ -29,7 +29,7 @@ describe('Dashboard template', () => {
it('matches the default snapshot', () => {
wrapper = shallowMount(Dashboard, {
- propsData: { ...propsData },
+ propsData: { ...dashboardProps },
store,
stubs: {
DashboardHeader,
diff --git a/spec/frontend/monitoring/components/dashboard_url_time_spec.js b/spec/frontend/monitoring/components/dashboard_url_time_spec.js
index a74c621db9b..276e20bae6a 100644
--- a/spec/frontend/monitoring/components/dashboard_url_time_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_url_time_spec.js
@@ -9,7 +9,8 @@ import {
updateHistory,
} from '~/lib/utils/url_utility';
import axios from '~/lib/utils/axios_utils';
-import { mockProjectDir, propsData } from '../mock_data';
+import { mockProjectDir } from '../mock_data';
+import { dashboardProps } from '../fixture_data';
import Dashboard from '~/monitoring/components/dashboard.vue';
import DashboardHeader from '~/monitoring/components/dashboard_header.vue';
@@ -26,7 +27,7 @@ describe('dashboard invalid url parameters', () => {
const createMountedWrapper = (props = { hasMetrics: true }, options = {}) => {
wrapper = mount(Dashboard, {
- propsData: { ...propsData, ...props },
+ propsData: { ...dashboardProps, ...props },
store,
stubs: { 'graph-group': true, 'dashboard-panel': true, 'dashboard-header': DashboardHeader },
...options,
diff --git a/spec/frontend/monitoring/components/dashboards_dropdown_spec.js b/spec/frontend/monitoring/components/dashboards_dropdown_spec.js
index b29d86cbc5b..d09fcc92ee7 100644
--- a/spec/frontend/monitoring/components/dashboards_dropdown_spec.js
+++ b/spec/frontend/monitoring/components/dashboards_dropdown_spec.js
@@ -1,14 +1,12 @@
import { shallowMount } from '@vue/test-utils';
-import { GlDropdownItem, GlModal, GlLoadingIcon, GlAlert, GlIcon } from '@gitlab/ui';
-import waitForPromises from 'helpers/wait_for_promises';
+import { GlDropdownItem, GlIcon } from '@gitlab/ui';
import DashboardsDropdown from '~/monitoring/components/dashboards_dropdown.vue';
-import DuplicateDashboardForm from '~/monitoring/components/duplicate_dashboard_form.vue';
-import { dashboardGitResponse } from '../mock_data';
+import { dashboardGitResponse, selfMonitoringDashboardGitResponse } from '../mock_data';
const defaultBranch = 'master';
-
+const modalId = 'duplicateDashboardModalId';
const starredDashboards = dashboardGitResponse.filter(({ starred }) => starred);
const notStarredDashboards = dashboardGitResponse.filter(({ starred }) => !starred);
@@ -32,6 +30,7 @@ describe('DashboardsDropdown', () => {
propsData: {
...props,
defaultBranch,
+ modalId,
},
sync: false,
...storeOpts,
@@ -82,7 +81,7 @@ describe('DashboardsDropdown', () => {
const searchTerm = 'Default';
setSearchTerm(searchTerm);
- return wrapper.vm.$nextTick(() => {
+ return wrapper.vm.$nextTick().then(() => {
expect(findItems()).toHaveLength(1);
});
});
@@ -91,7 +90,7 @@ describe('DashboardsDropdown', () => {
const searchTerm = 'does-not-exist';
setSearchTerm(searchTerm);
- return wrapper.vm.$nextTick(() => {
+ return wrapper.vm.$nextTick().then(() => {
expect(findNoItemsMsg().isVisible()).toBe(true);
});
});
@@ -151,12 +150,18 @@ describe('DashboardsDropdown', () => {
});
});
- describe('when a system dashboard is selected', () => {
+ const duplicableCases = [
+ dashboardGitResponse[0],
+ dashboardGitResponse[2],
+ selfMonitoringDashboardGitResponse[0],
+ ];
+
+ describe.each(duplicableCases)('when the selected dashboard can be duplicated', dashboard => {
let duplicateDashboardAction;
let modalDirective;
beforeEach(() => {
- [mockSelectedDashboard] = dashboardGitResponse;
+ mockSelectedDashboard = dashboard;
modalDirective = jest.fn();
duplicateDashboardAction = jest.fn().mockResolvedValue();
@@ -172,152 +177,59 @@ describe('DashboardsDropdown', () => {
},
},
);
-
- wrapper.vm.$refs.duplicateDashboardModal.hide = jest.fn();
});
- it('displays an item for each dashboard plus a "duplicate dashboard" item', () => {
- const item = wrapper.findAll({ ref: 'duplicateDashboardItem' });
-
+ it('displays a dropdown item for each dashboard', () => {
expect(findItems().length).toEqual(dashboardGitResponse.length + 1);
- expect(item.length).toBe(1);
});
- describe('modal form', () => {
- let okEvent;
-
- const findModal = () => wrapper.find(GlModal);
- const findAlert = () => wrapper.find(GlAlert);
-
- beforeEach(() => {
- okEvent = {
- preventDefault: jest.fn(),
- };
- });
-
- it('exists and contains a form to duplicate a dashboard', () => {
- expect(findModal().exists()).toBe(true);
- expect(findModal().contains(DuplicateDashboardForm)).toBe(true);
- });
-
- it('saves a new dashboard', () => {
- findModal().vm.$emit('ok', okEvent);
-
- return waitForPromises().then(() => {
- expect(okEvent.preventDefault).toHaveBeenCalled();
-
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
- expect(wrapper.vm.$refs.duplicateDashboardModal.hide).toHaveBeenCalled();
- expect(wrapper.emitted().selectDashboard).toBeTruthy();
- expect(findAlert().exists()).toBe(false);
- });
- });
-
- describe('when a new dashboard is saved succesfully', () => {
- const newDashboard = {
- can_edit: true,
- default: false,
- display_name: 'A new dashboard',
- system_dashboard: false,
- };
-
- const submitForm = formVals => {
- duplicateDashboardAction.mockResolvedValueOnce(newDashboard);
- findModal()
- .find(DuplicateDashboardForm)
- .vm.$emit('change', {
- dashboard: 'common_metrics.yml',
- commitMessage: 'A commit message',
- ...formVals,
- });
- findModal().vm.$emit('ok', okEvent);
- };
-
- it('to the default branch, redirects to the new dashboard', () => {
- submitForm({
- branch: defaultBranch,
- });
-
- return waitForPromises().then(() => {
- expect(wrapper.emitted().selectDashboard[0][0]).toEqual(newDashboard);
- });
- });
-
- it('to a new branch refreshes in the current dashboard', () => {
- submitForm({
- branch: 'another-branch',
- });
-
- return waitForPromises().then(() => {
- expect(wrapper.emitted().selectDashboard[0][0]).toEqual(dashboardGitResponse[0]);
- });
- });
- });
-
- it('handles error when a new dashboard is not saved', () => {
- const errMsg = 'An error occurred';
-
- duplicateDashboardAction.mockRejectedValueOnce(errMsg);
- findModal().vm.$emit('ok', okEvent);
+ it('displays one "duplicate dashboard" dropdown item with a directive attached', () => {
+ const item = wrapper.findAll('[data-testid="duplicateDashboardItem"]');
- return waitForPromises().then(() => {
- expect(okEvent.preventDefault).toHaveBeenCalled();
+ expect(item.length).toBe(1);
+ });
- expect(findAlert().exists()).toBe(true);
- expect(findAlert().text()).toBe(errMsg);
+ it('"duplicate dashboard" dropdown item directive works', () => {
+ const item = wrapper.find('[data-testid="duplicateDashboardItem"]');
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
- expect(wrapper.vm.$refs.duplicateDashboardModal.hide).not.toHaveBeenCalled();
- });
- });
+ item.trigger('click');
- it('id is correct, as the value of modal directive binding matches modal id', () => {
- expect(modalDirective).toHaveBeenCalledTimes(1);
-
- // Binding's second argument contains the modal id
- expect(modalDirective.mock.calls[0][1]).toEqual(
- expect.objectContaining({
- value: findModal().props('modalId'),
- }),
- );
+ return wrapper.vm.$nextTick().then(() => {
+ expect(modalDirective).toHaveBeenCalled();
});
+ });
- it('updates the form on changes', () => {
- const formVals = {
- dashboard: 'common_metrics.yml',
- commitMessage: 'A commit message',
- };
-
- findModal()
- .find(DuplicateDashboardForm)
- .vm.$emit('change', formVals);
+ it('id is correct, as the value of modal directive binding matches modal id', () => {
+ expect(modalDirective).toHaveBeenCalledTimes(1);
- // Binding's second argument contains the modal id
- expect(wrapper.vm.form).toEqual(formVals);
- });
+ // Binding's second argument contains the modal id
+ expect(modalDirective.mock.calls[0][1]).toEqual(
+ expect.objectContaining({
+ value: modalId,
+ }),
+ );
});
});
- describe('when a custom dashboard is selected', () => {
- const findModal = () => wrapper.find(GlModal);
+ const nonDuplicableCases = [dashboardGitResponse[1], selfMonitoringDashboardGitResponse[1]];
- beforeEach(() => {
- wrapper = createComponent({
- selectedDashboard: dashboardGitResponse[1],
+ describe.each(nonDuplicableCases)(
+ 'when the selected dashboard can not be duplicated',
+ dashboard => {
+ beforeEach(() => {
+ mockSelectedDashboard = dashboard;
+
+ wrapper = createComponent();
});
- });
- it('displays an item for each dashboard', () => {
- const item = wrapper.findAll({ ref: 'duplicateDashboardItem' });
+ it('displays a dropdown list item for each dashboard, but no list item for "duplicate dashboard"', () => {
+ const item = wrapper.findAll('[data-testid="duplicateDashboardItem"]');
- expect(findItems()).toHaveLength(dashboardGitResponse.length);
- expect(item.length).toBe(0);
- });
-
- it('modal form does not exist and contains a form to duplicate a dashboard', () => {
- expect(findModal().exists()).toBe(false);
- });
- });
+ expect(findItems()).toHaveLength(dashboardGitResponse.length);
+ expect(item.length).toBe(0);
+ });
+ },
+ );
describe('when a dashboard gets selected by the user', () => {
beforeEach(() => {
diff --git a/spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js b/spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js
new file mode 100644
index 00000000000..d8ffb4443ac
--- /dev/null
+++ b/spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js
@@ -0,0 +1,111 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlAlert, GlLoadingIcon, GlModal } from '@gitlab/ui';
+
+import waitForPromises from 'helpers/wait_for_promises';
+
+import DuplicateDashboardModal from '~/monitoring/components/duplicate_dashboard_modal.vue';
+import DuplicateDashboardForm from '~/monitoring/components/duplicate_dashboard_form.vue';
+
+import { dashboardGitResponse } from '../mock_data';
+
+describe('duplicate dashboard modal', () => {
+ let wrapper;
+ let mockDashboards;
+ let mockSelectedDashboard;
+ let duplicateDashboardAction;
+ let okEvent;
+
+ function createComponent(opts = {}) {
+ const storeOpts = {
+ methods: {
+ duplicateSystemDashboard: jest.fn(),
+ },
+ computed: {
+ allDashboards: () => mockDashboards,
+ selectedDashboard: () => mockSelectedDashboard,
+ },
+ };
+
+ return shallowMount(DuplicateDashboardModal, {
+ propsData: {
+ defaultBranch: 'master',
+ modalId: 'id',
+ },
+ sync: false,
+ ...storeOpts,
+ ...opts,
+ });
+ }
+
+ const findAlert = () => wrapper.find(GlAlert);
+ const findModal = () => wrapper.find(GlModal);
+ const findDuplicateDashboardForm = () => wrapper.find(DuplicateDashboardForm);
+
+ beforeEach(() => {
+ mockDashboards = dashboardGitResponse;
+ [mockSelectedDashboard] = dashboardGitResponse;
+
+ duplicateDashboardAction = jest.fn().mockResolvedValue();
+
+ okEvent = {
+ preventDefault: jest.fn(),
+ };
+
+ wrapper = createComponent({
+ methods: {
+ // Mock vuex actions
+ duplicateSystemDashboard: duplicateDashboardAction,
+ },
+ });
+
+ wrapper.vm.$refs.duplicateDashboardModal.hide = jest.fn();
+ });
+
+ it('contains a form to duplicate a dashboard', () => {
+ expect(findDuplicateDashboardForm().exists()).toBe(true);
+ });
+
+ it('saves a new dashboard', () => {
+ findModal().vm.$emit('ok', okEvent);
+
+ return waitForPromises().then(() => {
+ expect(okEvent.preventDefault).toHaveBeenCalled();
+ expect(wrapper.emitted().dashboardDuplicated).toBeTruthy();
+ expect(wrapper.emitted().dashboardDuplicated[0]).toEqual([dashboardGitResponse[0]]);
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ expect(wrapper.vm.$refs.duplicateDashboardModal.hide).toHaveBeenCalled();
+ expect(findAlert().exists()).toBe(false);
+ });
+ });
+
+ it('handles error when a new dashboard is not saved', () => {
+ const errMsg = 'An error occurred';
+
+ duplicateDashboardAction.mockRejectedValueOnce(errMsg);
+ findModal().vm.$emit('ok', okEvent);
+
+ return waitForPromises().then(() => {
+ expect(okEvent.preventDefault).toHaveBeenCalled();
+
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().text()).toBe(errMsg);
+
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ expect(wrapper.vm.$refs.duplicateDashboardModal.hide).not.toHaveBeenCalled();
+ });
+ });
+
+ it('updates the form on changes', () => {
+ const formVals = {
+ dashboard: 'common_metrics.yml',
+ commitMessage: 'A commit message',
+ };
+
+ findModal()
+ .find(DuplicateDashboardForm)
+ .vm.$emit('change', formVals);
+
+ // Binding's second argument contains the modal id
+ expect(wrapper.vm.form).toEqual(formVals);
+ });
+});
diff --git a/spec/frontend/monitoring/components/empty_state_spec.js b/spec/frontend/monitoring/components/empty_state_spec.js
index e985e5fb443..abb8b21e9f4 100644
--- a/spec/frontend/monitoring/components/empty_state_spec.js
+++ b/spec/frontend/monitoring/components/empty_state_spec.js
@@ -1,10 +1,11 @@
import { shallowMount } from '@vue/test-utils';
+import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui';
+import { dashboardEmptyStates } from '~/monitoring/constants';
import EmptyState from '~/monitoring/components/empty_state.vue';
function createComponent(props) {
return shallowMount(EmptyState, {
propsData: {
- ...props,
settingsPath: '/settingsPath',
clustersPath: '/clustersPath',
documentationPath: '/documentationPath',
@@ -13,30 +14,40 @@ function createComponent(props) {
emptyNoDataSvgPath: '/path/to/no-data.svg',
emptyNoDataSmallSvgPath: '/path/to/no-data-small.svg',
emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg',
+ ...props,
},
});
}
describe('EmptyState', () => {
+ it('shows loading state with a loading icon', () => {
+ const wrapper = createComponent({
+ selectedState: dashboardEmptyStates.LOADING,
+ });
+
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.find(GlEmptyState).exists()).toBe(false);
+ });
+
it('shows gettingStarted state', () => {
const wrapper = createComponent({
- selectedState: 'gettingStarted',
+ selectedState: dashboardEmptyStates.GETTING_STARTED,
});
expect(wrapper.element).toMatchSnapshot();
});
- it('shows loading state', () => {
+ it('shows unableToConnect state', () => {
const wrapper = createComponent({
- selectedState: 'loading',
+ selectedState: dashboardEmptyStates.UNABLE_TO_CONNECT,
});
expect(wrapper.element).toMatchSnapshot();
});
- it('shows unableToConnect state', () => {
+ it('shows noData state', () => {
const wrapper = createComponent({
- selectedState: 'unableToConnect',
+ selectedState: dashboardEmptyStates.NO_DATA,
});
expect(wrapper.element).toMatchSnapshot();
diff --git a/spec/frontend/monitoring/components/graph_group_spec.js b/spec/frontend/monitoring/components/graph_group_spec.js
index 92829135c0f..81f5d90c310 100644
--- a/spec/frontend/monitoring/components/graph_group_spec.js
+++ b/spec/frontend/monitoring/components/graph_group_spec.js
@@ -1,13 +1,14 @@
import { shallowMount } from '@vue/test-utils';
import GraphGroup from '~/monitoring/components/graph_group.vue';
-import Icon from '~/vue_shared/components/icon.vue';
+import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
describe('Graph group component', () => {
let wrapper;
const findGroup = () => wrapper.find({ ref: 'graph-group' });
const findContent = () => wrapper.find({ ref: 'graph-group-content' });
- const findCaretIcon = () => wrapper.find(Icon);
+ const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const findCaretIcon = () => wrapper.find(GlIcon);
const findToggleButton = () => wrapper.find('[data-testid="group-toggle-button"]');
const createComponent = propsData => {
@@ -28,28 +29,28 @@ describe('Graph group component', () => {
});
});
+ it('should not show a loading icon', () => {
+ expect(findLoadingIcon().exists()).toBe(false);
+ });
+
it('should show the angle-down caret icon', () => {
expect(findContent().isVisible()).toBe(true);
expect(findCaretIcon().props('name')).toBe('angle-down');
});
it('should show the angle-right caret icon when the user collapses the group', () => {
- wrapper.vm.collapse();
+ findToggleButton().trigger('click');
- return wrapper.vm.$nextTick(() => {
+ return wrapper.vm.$nextTick().then(() => {
expect(findContent().isVisible()).toBe(false);
expect(findCaretIcon().props('name')).toBe('angle-right');
});
});
- it('should contain a tabindex', () => {
- expect(findGroup().contains('[tabindex]')).toBe(true);
- });
-
it('should contain a tab index for the collapse button', () => {
const groupToggle = findToggleButton();
- expect(groupToggle.contains('[tabindex]')).toBe(true);
+ expect(groupToggle.is('[tabindex]')).toBe(true);
});
it('should show the open the group when collapseGroup is set to true', () => {
@@ -57,77 +58,94 @@ describe('Graph group component', () => {
collapseGroup: true,
});
- return wrapper.vm.$nextTick(() => {
+ return wrapper.vm.$nextTick().then(() => {
expect(findContent().isVisible()).toBe(true);
expect(findCaretIcon().props('name')).toBe('angle-down');
});
});
+ });
- describe('When group is collapsed', () => {
- beforeEach(() => {
- createComponent({
- name: 'panel',
- collapseGroup: true,
- });
+ describe('When group is collapsed', () => {
+ beforeEach(() => {
+ createComponent({
+ name: 'panel',
+ collapseGroup: true,
});
+ });
- it('should show the angle-down caret icon when collapseGroup is true', () => {
- expect(wrapper.vm.caretIcon).toBe('angle-right');
- });
+ it('should show the angle-down caret icon when collapseGroup is true', () => {
+ expect(findCaretIcon().props('name')).toBe('angle-right');
+ });
- it('should show the angle-right caret icon when collapseGroup is false', () => {
- wrapper.vm.collapse();
+ it('should show the angle-right caret icon when collapseGroup is false', () => {
+ findToggleButton().trigger('click');
- expect(wrapper.vm.caretIcon).toBe('angle-down');
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findCaretIcon().props('name')).toBe('angle-down');
});
+ });
- it('should call collapse the graph group content when enter is pressed on the caret icon', () => {
- const graphGroupContent = findContent();
- const button = findToggleButton();
+ it('should call collapse the graph group content when enter is pressed on the caret icon', () => {
+ const graphGroupContent = findContent();
+ const button = findToggleButton();
- button.trigger('keyup.enter');
+ button.trigger('keyup.enter');
+
+ expect(graphGroupContent.isVisible()).toBe(false);
+ });
+ });
- expect(graphGroupContent.isVisible()).toBe(false);
+ describe('When groups can not be collapsed', () => {
+ beforeEach(() => {
+ createComponent({
+ name: 'panel',
+ showPanels: false,
+ collapseGroup: false,
});
});
- describe('When groups can not be collapsed', () => {
- beforeEach(() => {
- createComponent({
- name: 'panel',
- showPanels: false,
- collapseGroup: false,
- });
+ it('should not have a container when showPanels is false', () => {
+ expect(findGroup().exists()).toBe(false);
+ expect(findContent().exists()).toBe(true);
+ });
+ });
+
+ describe('When group is loading', () => {
+ beforeEach(() => {
+ createComponent({
+ name: 'panel',
+ isLoading: true,
});
+ });
- it('should not have a container when showPanels is false', () => {
- expect(findGroup().exists()).toBe(false);
- expect(findContent().exists()).toBe(true);
+ it('should show a loading icon', () => {
+ expect(findLoadingIcon().exists()).toBe(true);
+ });
+ });
+
+ describe('When group does not show a panel heading', () => {
+ beforeEach(() => {
+ createComponent({
+ name: 'panel',
+ showPanels: false,
+ collapseGroup: false,
});
});
- describe('When group does not show a panel heading', () => {
- beforeEach(() => {
- createComponent({
- name: 'panel',
- showPanels: false,
- collapseGroup: false,
- });
+ it('should collapse the panel content', () => {
+ expect(findContent().isVisible()).toBe(true);
+ expect(findCaretIcon().exists()).toBe(false);
+ });
+
+ it('should show the panel content when collapse is set to false', () => {
+ wrapper.setProps({
+ collapseGroup: false,
});
- it('should collapse the panel content', () => {
+ return wrapper.vm.$nextTick().then(() => {
expect(findContent().isVisible()).toBe(true);
expect(findCaretIcon().exists()).toBe(false);
});
-
- it('should show the panel content when clicked', () => {
- wrapper.vm.collapse();
-
- return wrapper.vm.$nextTick(() => {
- expect(findContent().isVisible()).toBe(true);
- expect(findCaretIcon().exists()).toBe(false);
- });
- });
});
});
});
diff --git a/spec/frontend/monitoring/components/links_section_spec.js b/spec/frontend/monitoring/components/links_section_spec.js
index 3b5b72d84ee..b771d63d51f 100644
--- a/spec/frontend/monitoring/components/links_section_spec.js
+++ b/spec/frontend/monitoring/components/links_section_spec.js
@@ -15,7 +15,7 @@ describe('Links Section component', () => {
const setState = links => {
store.state.monitoringDashboard = {
...store.state.monitoringDashboard,
- showEmptyState: false,
+ emptyState: null,
links,
};
};
diff --git a/spec/frontend/monitoring/components/refresh_button_spec.js b/spec/frontend/monitoring/components/refresh_button_spec.js
new file mode 100644
index 00000000000..29615638453
--- /dev/null
+++ b/spec/frontend/monitoring/components/refresh_button_spec.js
@@ -0,0 +1,143 @@
+import { shallowMount } from '@vue/test-utils';
+import { createStore } from '~/monitoring/stores';
+import { GlNewDropdown, GlNewDropdownItem, GlButton } from '@gitlab/ui';
+
+import RefreshButton from '~/monitoring/components/refresh_button.vue';
+
+describe('RefreshButton', () => {
+ let wrapper;
+ let store;
+ let dispatch;
+ let documentHidden;
+
+ const createWrapper = () => {
+ wrapper = shallowMount(RefreshButton, { store });
+ };
+
+ const findRefreshBtn = () => wrapper.find(GlButton);
+ const findDropdown = () => wrapper.find(GlNewDropdown);
+ const findOptions = () => findDropdown().findAll(GlNewDropdownItem);
+ const findOptionAt = index => findOptions().at(index);
+
+ const expectFetchDataToHaveBeenCalledTimes = times => {
+ const refreshCalls = dispatch.mock.calls.filter(([action, payload]) => {
+ return action === 'monitoringDashboard/fetchDashboardData' && payload === undefined;
+ });
+ expect(refreshCalls).toHaveLength(times);
+ };
+
+ beforeEach(() => {
+ store = createStore();
+ jest.spyOn(store, 'dispatch').mockResolvedValue();
+ dispatch = store.dispatch;
+
+ // Document can be mock hidden by overriding the `hidden` property
+ documentHidden = false;
+ Object.defineProperty(document, 'hidden', {
+ configurable: true,
+ get() {
+ return documentHidden;
+ },
+ });
+
+ createWrapper();
+ });
+
+ afterEach(() => {
+ dispatch.mockReset();
+ wrapper.destroy();
+ });
+
+ it('refreshes data when "refresh" is clicked', () => {
+ findRefreshBtn().vm.$emit('click');
+ expectFetchDataToHaveBeenCalledTimes(1);
+ });
+
+ it('refresh rate is "Off" in the dropdown', () => {
+ expect(findDropdown().props('text')).toBe('Off');
+ });
+
+ describe('refresh rate options', () => {
+ it('presents multiple options', () => {
+ expect(findOptions().length).toBeGreaterThan(1);
+ });
+
+ it('presents an "Off" option as the default option', () => {
+ expect(findOptionAt(0).text()).toBe('Off');
+ expect(findOptionAt(0).props('isChecked')).toBe(true);
+ });
+ });
+
+ describe('when a refresh rate is chosen', () => {
+ const optIndex = 2; // Other option than "Off"
+
+ beforeEach(() => {
+ findOptionAt(optIndex).vm.$emit('click');
+ return wrapper.vm.$nextTick;
+ });
+
+ it('refresh rate appears in the dropdown', () => {
+ expect(findDropdown().props('text')).toBe('10s');
+ });
+
+ it('refresh rate option is checked', () => {
+ expect(findOptionAt(0).props('isChecked')).toBe(false);
+ expect(findOptionAt(optIndex).props('isChecked')).toBe(true);
+ });
+
+ it('refreshes data when a new refresh rate is chosen', () => {
+ expectFetchDataToHaveBeenCalledTimes(1);
+ });
+
+ it('refreshes data after two intervals of time have passed', async () => {
+ jest.runOnlyPendingTimers();
+ expectFetchDataToHaveBeenCalledTimes(2);
+
+ await wrapper.vm.$nextTick();
+
+ jest.runOnlyPendingTimers();
+ expectFetchDataToHaveBeenCalledTimes(3);
+ });
+
+ it('does not refresh data if the document is hidden', async () => {
+ documentHidden = true;
+
+ jest.runOnlyPendingTimers();
+ expectFetchDataToHaveBeenCalledTimes(1);
+
+ await wrapper.vm.$nextTick();
+
+ jest.runOnlyPendingTimers();
+ expectFetchDataToHaveBeenCalledTimes(1);
+ });
+
+ it('data is not refreshed anymore after component is destroyed', () => {
+ expect(jest.getTimerCount()).toBe(1);
+
+ wrapper.destroy();
+
+ expect(jest.getTimerCount()).toBe(0);
+ });
+
+ describe('when "Off" refresh rate is chosen', () => {
+ beforeEach(() => {
+ findOptionAt(0).vm.$emit('click');
+ return wrapper.vm.$nextTick;
+ });
+
+ it('refresh rate is "Off" in the dropdown', () => {
+ expect(findDropdown().props('text')).toBe('Off');
+ });
+
+ it('refresh rate option is appears selected', () => {
+ expect(findOptionAt(0).props('isChecked')).toBe(true);
+ expect(findOptionAt(optIndex).props('isChecked')).toBe(false);
+ });
+
+ it('stops refreshing data', () => {
+ jest.runOnlyPendingTimers();
+ expectFetchDataToHaveBeenCalledTimes(1);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/monitoring/components/variables/custom_variable_spec.js b/spec/frontend/monitoring/components/variables/dropdown_field_spec.js
index 5a2b26219b6..cc384aef231 100644
--- a/spec/frontend/monitoring/components/variables/custom_variable_spec.js
+++ b/spec/frontend/monitoring/components/variables/dropdown_field_spec.js
@@ -1,18 +1,25 @@
import { shallowMount } from '@vue/test-utils';
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
-import CustomVariable from '~/monitoring/components/variables/custom_variable.vue';
+import DropdownField from '~/monitoring/components/variables/dropdown_field.vue';
describe('Custom variable component', () => {
let wrapper;
- const propsData = {
+
+ const defaultProps = {
name: 'env',
label: 'Select environment',
value: 'Production',
- options: [{ text: 'Production', value: 'prod' }, { text: 'Canary', value: 'canary' }],
+ options: {
+ values: [{ text: 'Production', value: 'prod' }, { text: 'Canary', value: 'canary' }],
+ },
};
- const createShallowWrapper = () => {
- wrapper = shallowMount(CustomVariable, {
- propsData,
+
+ const createShallowWrapper = props => {
+ wrapper = shallowMount(DropdownField, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
});
};
@@ -22,19 +29,25 @@ describe('Custom variable component', () => {
it('renders dropdown element when all necessary props are passed', () => {
createShallowWrapper();
- expect(findDropdown()).toExist();
+ expect(findDropdown().exists()).toBe(true);
});
it('renders dropdown element with a text', () => {
createShallowWrapper();
- expect(findDropdown().attributes('text')).toBe(propsData.value);
+ expect(findDropdown().attributes('text')).toBe(defaultProps.value);
});
it('renders all the dropdown items', () => {
createShallowWrapper();
- expect(findDropdownItems()).toHaveLength(propsData.options.length);
+ expect(findDropdownItems()).toHaveLength(defaultProps.options.values.length);
+ });
+
+ it('renders dropdown when values are missing', () => {
+ createShallowWrapper({ options: {} });
+
+ expect(findDropdown().exists()).toBe(true);
});
it('changing dropdown items triggers update', () => {
@@ -46,7 +59,7 @@ describe('Custom variable component', () => {
.vm.$emit('click');
return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('onUpdate', 'env', 'canary');
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', 'canary');
});
});
});
diff --git a/spec/frontend/monitoring/components/variables/text_variable_spec.js b/spec/frontend/monitoring/components/variables/text_field_spec.js
index f01584ae8bc..99c6facac38 100644
--- a/spec/frontend/monitoring/components/variables/text_variable_spec.js
+++ b/spec/frontend/monitoring/components/variables/text_field_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import { GlFormInput } from '@gitlab/ui';
-import TextVariable from '~/monitoring/components/variables/text_variable.vue';
+import TextField from '~/monitoring/components/variables/text_field.vue';
describe('Text variable component', () => {
let wrapper;
@@ -10,7 +10,7 @@ describe('Text variable component', () => {
value: 'test-pod',
};
const createShallowWrapper = () => {
- wrapper = shallowMount(TextVariable, {
+ wrapper = shallowMount(TextField, {
propsData,
});
};
@@ -40,7 +40,7 @@ describe('Text variable component', () => {
findInput().trigger('keyup.enter');
return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('onUpdate', 'pod', 'prod-pod');
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', 'prod-pod');
});
});
@@ -53,7 +53,7 @@ describe('Text variable component', () => {
findInput().trigger('blur');
return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('onUpdate', 'pod', 'canary-pod');
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', 'canary-pod');
});
});
});
diff --git a/spec/frontend/monitoring/components/variables_section_spec.js b/spec/frontend/monitoring/components/variables_section_spec.js
index fd814e81c8f..3097906ee68 100644
--- a/spec/frontend/monitoring/components/variables_section_spec.js
+++ b/spec/frontend/monitoring/components/variables_section_spec.js
@@ -1,13 +1,12 @@
import { shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import VariablesSection from '~/monitoring/components/variables_section.vue';
-import CustomVariable from '~/monitoring/components/variables/custom_variable.vue';
-import TextVariable from '~/monitoring/components/variables/text_variable.vue';
+import DropdownField from '~/monitoring/components/variables/dropdown_field.vue';
+import TextField from '~/monitoring/components/variables/text_field.vue';
import { updateHistory, mergeUrlParams } from '~/lib/utils/url_utility';
import { createStore } from '~/monitoring/stores';
import { convertVariablesForURL } from '~/monitoring/utils';
-import * as types from '~/monitoring/stores/mutation_types';
-import { mockTemplatingDataResponses } from '../mock_data';
+import { storeVariables } from '../mock_data';
jest.mock('~/lib/utils/url_utility', () => ({
updateHistory: jest.fn(),
@@ -17,11 +16,6 @@ jest.mock('~/lib/utils/url_utility', () => ({
describe('Metrics dashboard/variables section component', () => {
let store;
let wrapper;
- const sampleVariables = {
- label1: mockTemplatingDataResponses.simpleText.simpleText,
- label2: mockTemplatingDataResponses.advText.advText,
- label3: mockTemplatingDataResponses.simpleCustom.simpleCustom,
- };
const createShallowWrapper = () => {
wrapper = shallowMount(VariablesSection, {
@@ -29,30 +23,41 @@ describe('Metrics dashboard/variables section component', () => {
});
};
- const findTextInput = () => wrapper.findAll(TextVariable);
- const findCustomInput = () => wrapper.findAll(CustomVariable);
+ const findTextInputs = () => wrapper.findAll(TextField);
+ const findCustomInputs = () => wrapper.findAll(DropdownField);
beforeEach(() => {
store = createStore();
- store.state.monitoringDashboard.showEmptyState = false;
+ store.state.monitoringDashboard.emptyState = null;
});
it('does not show the variables section', () => {
createShallowWrapper();
- const allInputs = findTextInput().length + findCustomInput().length;
+ const allInputs = findTextInputs().length + findCustomInputs().length;
expect(allInputs).toBe(0);
});
- it('shows the variables section', () => {
- createShallowWrapper();
- store.commit(`monitoringDashboard/${types.SET_VARIABLES}`, sampleVariables);
+ describe('when variables are set', () => {
+ beforeEach(() => {
+ store.state.monitoringDashboard.variables = storeVariables;
+ createShallowWrapper();
+
+ return wrapper.vm.$nextTick;
+ });
+
+ it('shows the variables section', () => {
+ const allInputs = findTextInputs().length + findCustomInputs().length;
+
+ expect(allInputs).toBe(storeVariables.length);
+ });
- return wrapper.vm.$nextTick(() => {
- const allInputs = findTextInput().length + findCustomInput().length;
+ it('shows the right custom variable inputs', () => {
+ const customInputs = findCustomInputs();
- expect(allInputs).toBe(Object.keys(sampleVariables).length);
+ expect(customInputs.at(0).props('name')).toBe('customSimple');
+ expect(customInputs.at(1).props('name')).toBe('customAdvanced');
});
});
@@ -65,8 +70,8 @@ describe('Metrics dashboard/variables section component', () => {
monitoringDashboard: {
namespaced: true,
state: {
- showEmptyState: false,
- variables: sampleVariables,
+ emptyState: null,
+ variables: storeVariables,
},
actions: {
updateVariablesAndFetchData,
@@ -79,14 +84,14 @@ describe('Metrics dashboard/variables section component', () => {
});
it('merges the url params and refreshes the dashboard when a text-based variables inputs are updated', () => {
- const firstInput = findTextInput().at(0);
+ const firstInput = findTextInputs().at(0);
- firstInput.vm.$emit('onUpdate', 'label1', 'test');
+ firstInput.vm.$emit('input', 'test');
return wrapper.vm.$nextTick(() => {
expect(updateVariablesAndFetchData).toHaveBeenCalled();
expect(mergeUrlParams).toHaveBeenCalledWith(
- convertVariablesForURL(sampleVariables),
+ convertVariablesForURL(storeVariables),
window.location.href,
);
expect(updateHistory).toHaveBeenCalled();
@@ -94,14 +99,14 @@ describe('Metrics dashboard/variables section component', () => {
});
it('merges the url params and refreshes the dashboard when a custom-based variables inputs are updated', () => {
- const firstInput = findCustomInput().at(0);
+ const firstInput = findCustomInputs().at(0);
- firstInput.vm.$emit('onUpdate', 'label1', 'test');
+ firstInput.vm.$emit('input', 'test');
return wrapper.vm.$nextTick(() => {
expect(updateVariablesAndFetchData).toHaveBeenCalled();
expect(mergeUrlParams).toHaveBeenCalledWith(
- convertVariablesForURL(sampleVariables),
+ convertVariablesForURL(storeVariables),
window.location.href,
);
expect(updateHistory).toHaveBeenCalled();
@@ -109,9 +114,9 @@ describe('Metrics dashboard/variables section component', () => {
});
it('does not merge the url params and refreshes the dashboard if the value entered is not different that is what currently stored', () => {
- const firstInput = findTextInput().at(0);
+ const firstInput = findTextInputs().at(0);
- firstInput.vm.$emit('onUpdate', 'label1', 'Simple text');
+ firstInput.vm.$emit('input', 'My default value');
expect(updateVariablesAndFetchData).not.toHaveBeenCalled();
expect(mergeUrlParams).not.toHaveBeenCalled();
diff --git a/spec/frontend/monitoring/fixture_data.js b/spec/frontend/monitoring/fixture_data.js
index b7b72a15992..97edf7bda74 100644
--- a/spec/frontend/monitoring/fixture_data.js
+++ b/spec/frontend/monitoring/fixture_data.js
@@ -1,5 +1,8 @@
+import { stateAndPropsFromDataset } from '~/monitoring/utils';
import { mapToDashboardViewModel } from '~/monitoring/stores/utils';
import { metricStates } from '~/monitoring/constants';
+import { convertObjectProps } from '~/lib/utils/common_utils';
+import { convertToCamelCase } from '~/lib/utils/text_utility';
import { metricsResult } from './mock_data';
@@ -7,23 +10,54 @@ import { metricsResult } from './mock_data';
export const metricsDashboardResponse = getJSONFixture(
'metrics_dashboard/environment_metrics_dashboard.json',
);
+
export const metricsDashboardPayload = metricsDashboardResponse.dashboard;
+
+const datasetState = stateAndPropsFromDataset(
+ // It's preferable to have props in snake_case, this will be addressed at:
+ // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/33574
+ convertObjectProps(
+ // Some props use kebab-case, convert to snake_case first
+ key => convertToCamelCase(key.replace(/-/g, '_')),
+ metricsDashboardResponse.metrics_data,
+ ),
+);
+
+// new properties like addDashboardDocumentationPath prop and alertsEndpoint
+// was recently added to dashboard.vue component this needs to be
+// added to fixtures data
+// https://gitlab.com/gitlab-org/gitlab/-/issues/229256
+export const dashboardProps = {
+ ...datasetState.dataProps,
+ addDashboardDocumentationPath: 'https://path/to/docs',
+ alertsEndpoint: null,
+};
+
export const metricsDashboardViewModel = mapToDashboardViewModel(metricsDashboardPayload);
export const metricsDashboardPanelCount = 22;
export const metricResultStatus = {
// First metric in fixture `metrics_dashboard/environment_metrics_dashboard.json`
metricId: 'NO_DB_response_metrics_nginx_ingress_throughput_status_code',
- result: metricsResult,
+ data: {
+ resultType: 'matrix',
+ result: metricsResult,
+ },
};
export const metricResultPods = {
// Second metric in fixture `metrics_dashboard/environment_metrics_dashboard.json`
metricId: 'NO_DB_response_metrics_nginx_ingress_latency_pod_average',
- result: metricsResult,
+ data: {
+ resultType: 'matrix',
+ result: metricsResult,
+ },
};
export const metricResultEmpty = {
metricId: 'NO_DB_response_metrics_nginx_ingress_16_throughput_status_code',
- result: [],
+ data: {
+ resultType: 'matrix',
+ result: [],
+ },
};
// Graph data
diff --git a/spec/frontend/monitoring/graph_data.js b/spec/frontend/monitoring/graph_data.js
new file mode 100644
index 00000000000..e1b95723f3d
--- /dev/null
+++ b/spec/frontend/monitoring/graph_data.js
@@ -0,0 +1,164 @@
+import { mapPanelToViewModel, normalizeQueryResponseData } from '~/monitoring/stores/utils';
+import { panelTypes, metricStates } from '~/monitoring/constants';
+
+const initTime = 1435781451.781;
+
+const makeValue = val => [initTime, val];
+const makeValues = vals => vals.map((val, i) => [initTime + 15 * i, val]);
+
+// Normalized Prometheus Responses
+
+const scalarResult = ({ value = '1' } = {}) =>
+ normalizeQueryResponseData({
+ resultType: 'scalar',
+ result: makeValue(value),
+ });
+
+const vectorResult = ({ value1 = '1', value2 = '2' } = {}) =>
+ normalizeQueryResponseData({
+ resultType: 'vector',
+ result: [
+ {
+ metric: {
+ __name__: 'up',
+ job: 'prometheus',
+ instance: 'localhost:9090',
+ },
+ value: makeValue(value1),
+ },
+ {
+ metric: {
+ __name__: 'up',
+ job: 'node',
+ instance: 'localhost:9100',
+ },
+ value: makeValue(value2),
+ },
+ ],
+ });
+
+const matrixSingleResult = ({ values = ['1', '2', '3'] } = {}) =>
+ normalizeQueryResponseData({
+ resultType: 'matrix',
+ result: [
+ {
+ metric: {},
+ values: makeValues(values),
+ },
+ ],
+ });
+
+const matrixMultiResult = ({ values1 = ['1', '2', '3'], values2 = ['4', '5', '6'] } = {}) =>
+ normalizeQueryResponseData({
+ resultType: 'matrix',
+ result: [
+ {
+ metric: {
+ __name__: 'up',
+ job: 'prometheus',
+ instance: 'localhost:9090',
+ },
+ values: makeValues(values1),
+ },
+ {
+ metric: {
+ __name__: 'up',
+ job: 'node',
+ instance: 'localhost:9091',
+ },
+ values: makeValues(values2),
+ },
+ ],
+ });
+
+// GraphData factory
+
+/**
+ * Generate mock graph data according to options
+ *
+ * @param {Object} panelOptions - Panel options as in YML.
+ * @param {Object} dataOptions
+ * @param {Object} dataOptions.metricCount
+ * @param {Object} dataOptions.isMultiSeries
+ */
+export const timeSeriesGraphData = (panelOptions = {}, dataOptions = {}) => {
+ const { metricCount = 1, isMultiSeries = false } = dataOptions;
+
+ return mapPanelToViewModel({
+ title: 'Time Series Panel',
+ type: panelTypes.LINE_CHART,
+ x_label: 'X Axis',
+ y_label: 'Y Axis',
+ metrics: Array.from(Array(metricCount), (_, i) => ({
+ label: `Metric ${i + 1}`,
+ state: metricStates.OK,
+ result: isMultiSeries ? matrixMultiResult() : matrixSingleResult(),
+ })),
+ ...panelOptions,
+ });
+};
+
+/**
+ * Generate mock graph data according to options
+ *
+ * @param {Object} panelOptions - Panel options as in YML.
+ * @param {Object} dataOptions
+ * @param {Object} dataOptions.unit
+ * @param {Object} dataOptions.value
+ * @param {Object} dataOptions.isVector
+ */
+export const singleStatGraphData = (panelOptions = {}, dataOptions = {}) => {
+ const { unit, value = '1', isVector = false } = dataOptions;
+
+ return mapPanelToViewModel({
+ title: 'Single Stat Panel',
+ type: panelTypes.SINGLE_STAT,
+ metrics: [
+ {
+ label: 'Metric Label',
+ state: metricStates.OK,
+ result: isVector ? vectorResult({ value }) : scalarResult({ value }),
+ unit,
+ },
+ ],
+ ...panelOptions,
+ });
+};
+
+/**
+ * Generate mock graph data according to options
+ *
+ * @param {Object} panelOptions - Panel options as in YML.
+ * @param {Object} dataOptions
+ * @param {Array} dataOptions.values - Metric values
+ * @param {Array} dataOptions.upper - Upper boundary values
+ * @param {Array} dataOptions.lower - Lower boundary values
+ */
+export const anomalyGraphData = (panelOptions = {}, dataOptions = {}) => {
+ const { values, upper, lower } = dataOptions;
+
+ return mapPanelToViewModel({
+ title: 'Anomaly Panel',
+ type: panelTypes.ANOMALY_CHART,
+ x_label: 'X Axis',
+ y_label: 'Y Axis',
+ metrics: [
+ {
+ label: `Metric`,
+ state: metricStates.OK,
+ result: matrixSingleResult({ values }),
+ },
+ {
+ label: `Upper boundary`,
+ state: metricStates.OK,
+ result: matrixSingleResult({ values: upper }),
+ },
+ {
+ label: `Lower boundary`,
+ state: metricStates.OK,
+ result: matrixSingleResult({ values: lower }),
+ },
+ ],
+ ...panelOptions,
+ });
+};
diff --git a/spec/frontend/monitoring/mock_data.js b/spec/frontend/monitoring/mock_data.js
index 05b29e78ecd..49ad33402c6 100644
--- a/spec/frontend/monitoring/mock_data.js
+++ b/spec/frontend/monitoring/mock_data.js
@@ -5,28 +5,14 @@ import { TEST_HOST } from '../helpers/test_constants';
export const mockProjectDir = '/frontend-fixtures/environments-project';
export const mockApiEndpoint = `${TEST_HOST}/monitoring/mock`;
-export const propsData = {
- hasMetrics: false,
- documentationPath: '/path/to/docs',
- settingsPath: '/path/to/settings',
- clustersPath: '/path/to/clusters',
- tagsPath: '/path/to/tags',
- defaultBranch: 'master',
- emptyGettingStartedSvgPath: '/path/to/getting-started.svg',
- emptyLoadingSvgPath: '/path/to/loading.svg',
- emptyNoDataSvgPath: '/path/to/no-data.svg',
- emptyNoDataSmallSvgPath: '/path/to/no-data-small.svg',
- emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg',
- customMetricsAvailable: false,
- customMetricsPath: '',
- validateQueryPath: '',
-};
+export const customDashboardBasePath = '.gitlab/dashboards';
const customDashboardsData = new Array(30).fill(null).map((_, idx) => ({
default: false,
display_name: `Custom Dashboard ${idx}`,
can_edit: true,
system_dashboard: false,
+ out_of_the_box_dashboard: false,
project_blob_path: `${mockProjectDir}/blob/master/dashboards/.gitlab/dashboards/dashboard_${idx}.yml`,
path: `.gitlab/dashboards/dashboard_${idx}.yml`,
starred: false,
@@ -65,136 +51,6 @@ export const anomalyDeploymentData = [
},
];
-export const anomalyMockResultValues = {
- noAnomaly: [
- [
- ['2019-08-19T19:00:00.000Z', 1.25],
- ['2019-08-19T20:00:00.000Z', 1.45],
- ['2019-08-19T21:00:00.000Z', 1.55],
- ['2019-08-19T22:00:00.000Z', 1.48],
- ],
- [
- // upper boundary
- ['2019-08-19T19:00:00.000Z', 2],
- ['2019-08-19T20:00:00.000Z', 2.55],
- ['2019-08-19T21:00:00.000Z', 2.65],
- ['2019-08-19T22:00:00.000Z', 3.0],
- ],
- [
- // lower boundary
- ['2019-08-19T19:00:00.000Z', 0.45],
- ['2019-08-19T20:00:00.000Z', 0.65],
- ['2019-08-19T21:00:00.000Z', 0.7],
- ['2019-08-19T22:00:00.000Z', 0.8],
- ],
- ],
- noBoundary: [
- [
- ['2019-08-19T19:00:00.000Z', 1.25],
- ['2019-08-19T20:00:00.000Z', 1.45],
- ['2019-08-19T21:00:00.000Z', 1.55],
- ['2019-08-19T22:00:00.000Z', 1.48],
- ],
- [
- // empty upper boundary
- ],
- [
- // empty lower boundary
- ],
- ],
- oneAnomaly: [
- [
- ['2019-08-19T19:00:00.000Z', 1.25],
- ['2019-08-19T20:00:00.000Z', 3.45], // anomaly
- ['2019-08-19T21:00:00.000Z', 1.55],
- ],
- [
- // upper boundary
- ['2019-08-19T19:00:00.000Z', 2],
- ['2019-08-19T20:00:00.000Z', 2.55],
- ['2019-08-19T21:00:00.000Z', 2.65],
- ],
- [
- // lower boundary
- ['2019-08-19T19:00:00.000Z', 0.45],
- ['2019-08-19T20:00:00.000Z', 0.65],
- ['2019-08-19T21:00:00.000Z', 0.7],
- ],
- ],
- negativeBoundary: [
- [
- ['2019-08-19T19:00:00.000Z', 1.25],
- ['2019-08-19T20:00:00.000Z', 3.45], // anomaly
- ['2019-08-19T21:00:00.000Z', 1.55],
- ],
- [
- // upper boundary
- ['2019-08-19T19:00:00.000Z', 2],
- ['2019-08-19T20:00:00.000Z', 2.55],
- ['2019-08-19T21:00:00.000Z', 2.65],
- ],
- [
- // lower boundary
- ['2019-08-19T19:00:00.000Z', -1.25],
- ['2019-08-19T20:00:00.000Z', -2.65],
- ['2019-08-19T21:00:00.000Z', -3.7], // lowest point
- ],
- ],
-};
-
-export const anomalyMockGraphData = {
- title: 'Requests Per Second Mock Data',
- type: 'anomaly-chart',
- weight: 3,
- metrics: [
- {
- metricId: '90',
- id: 'metric',
- query_range: 'MOCK_PROMETHEUS_METRIC_QUERY_RANGE',
- unit: 'RPS',
- label: 'Metrics RPS',
- metric_id: 90,
- prometheus_endpoint_path: 'MOCK_METRIC_PEP',
- result: [
- {
- metric: {},
- values: [['2019-08-19T19:00:00.000Z', 0]],
- },
- ],
- },
- {
- metricId: '91',
- id: 'upper',
- query_range: '...',
- unit: 'RPS',
- label: 'Upper Limit Metrics RPS',
- metric_id: 91,
- prometheus_endpoint_path: 'MOCK_UPPER_PEP',
- result: [
- {
- metric: {},
- values: [['2019-08-19T19:00:00.000Z', 0]],
- },
- ],
- },
- {
- metricId: '92',
- id: 'lower',
- query_range: '...',
- unit: 'RPS',
- label: 'Lower Limit Metrics RPS',
- metric_id: 92,
- prometheus_endpoint_path: 'MOCK_LOWER_PEP',
- result: [
- {
- metric: {},
- values: [['2019-08-19T19:00:00.000Z', 0]],
- },
- ],
- },
- ],
-};
-
export const deploymentData = [
{
id: 111,
@@ -317,6 +173,7 @@ export const dashboardGitResponse = [
display_name: 'Default',
can_edit: false,
system_dashboard: true,
+ out_of_the_box_dashboard: true,
project_blob_path: null,
path: 'config/prometheus/common_metrics.yml',
starred: false,
@@ -327,6 +184,44 @@ export const dashboardGitResponse = [
display_name: 'dashboard.yml',
can_edit: true,
system_dashboard: false,
+ out_of_the_box_dashboard: false,
+ project_blob_path: `${mockProjectDir}/-/blob/master/.gitlab/dashboards/dashboard.yml`,
+ path: '.gitlab/dashboards/dashboard.yml',
+ starred: true,
+ user_starred_path: `${mockProjectDir}/metrics_user_starred_dashboards?dashboard_path=.gitlab/dashboards/dashboard.yml`,
+ },
+ {
+ default: false,
+ display_name: 'Pod Health',
+ can_edit: false,
+ system_dashboard: false,
+ out_of_the_box_dashboard: true,
+ project_blob_path: null,
+ path: 'config/prometheus/pod_metrics.yml',
+ starred: false,
+ user_starred_path: `${mockProjectDir}/metrics_user_starred_dashboards?dashboard_path=config/prometheus/pod_metrics.yml`,
+ },
+ ...customDashboardsData,
+];
+
+export const selfMonitoringDashboardGitResponse = [
+ {
+ default: true,
+ display_name: 'Default',
+ can_edit: false,
+ system_dashboard: false,
+ out_of_the_box_dashboard: true,
+ project_blob_path: null,
+ path: 'config/prometheus/self_monitoring_default.yml',
+ starred: false,
+ user_starred_path: `${mockProjectDir}/metrics_user_starred_dashboards?dashboard_path=config/prometheus/self_monitoring_default.yml`,
+ },
+ {
+ default: false,
+ display_name: 'dashboard.yml',
+ can_edit: true,
+ system_dashboard: false,
+ out_of_the_box_dashboard: false,
project_blob_path: `${mockProjectDir}/-/blob/master/.gitlab/dashboards/dashboard.yml`,
path: '.gitlab/dashboards/dashboard.yml',
starred: true,
@@ -349,30 +244,6 @@ export const metricsResult = [
},
];
-export const singleStatMetricsResult = {
- title: 'Super Chart A2',
- type: 'single-stat',
- weight: 2,
- metrics: [
- {
- id: 'metric_a1',
- metricId: '2',
- query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024',
- unit: 'MB',
- label: 'Total Consumption',
- metric_id: 2,
- prometheus_endpoint_path:
- '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
- result: [
- {
- metric: { job: 'prometheus' },
- value: ['2019-06-26T21:03:20.881Z', 91],
- },
- ],
- },
- ],
-};
-
export const graphDataPrometheusQueryRangeMultiTrack = {
title: 'Super Chart A3',
type: 'heatmap',
@@ -641,253 +512,186 @@ export const mockLinks = [
},
];
-const templatingVariableTypes = {
+export const templatingVariablesExamples = {
text: {
- simple: 'Simple text',
- advanced: {
- label: 'Variable 4',
+ textSimple: 'My default value',
+ textAdvanced: {
+ label: 'Advanced text variable',
type: 'text',
options: {
- default_value: 'default',
+ default_value: 'A default value',
},
},
},
custom: {
- simple: ['value1', 'value2', 'value3'],
- advanced: {
- normal: {
- label: 'Advanced Var',
- type: 'custom',
- options: {
- values: [
- { value: 'value1', text: 'Var 1 Option 1' },
- {
- value: 'value2',
- text: 'Var 1 Option 2',
- default: true,
- },
- ],
- },
- },
- withoutOpts: {
- type: 'custom',
- options: {},
+ customSimple: ['value1', 'value2', 'value3'],
+ customAdvanced: {
+ label: 'Advanced Var',
+ type: 'custom',
+ options: {
+ values: [
+ { value: 'value1', text: 'Var 1 Option 1' },
+ {
+ value: 'value2',
+ text: 'Var 1 Option 2',
+ default: true,
+ },
+ ],
},
- withoutLabel: {
- type: 'custom',
- options: {
- values: [
- { value: 'value1', text: 'Var 1 Option 1' },
- {
- value: 'value2',
- text: 'Var 1 Option 2',
- default: true,
- },
- ],
- },
+ },
+ customAdvancedWithoutOpts: {
+ type: 'custom',
+ options: {},
+ },
+ customAdvancedWithoutLabel: {
+ type: 'custom',
+ options: {
+ values: [
+ { value: 'value1', text: 'Var 1 Option 1' },
+ {
+ value: 'value2',
+ text: 'Var 1 Option 2',
+ default: true,
+ },
+ ],
},
- withoutType: {
- label: 'Variable 2',
- options: {
- values: [
- { value: 'value1', text: 'Var 1 Option 1' },
- {
- value: 'value2',
- text: 'Var 1 Option 2',
- default: true,
- },
- ],
- },
+ },
+ customAdvancedWithoutType: {
+ label: 'Variable 2',
+ options: {
+ values: [
+ { value: 'value1', text: 'Var 1 Option 1' },
+ {
+ value: 'value2',
+ text: 'Var 1 Option 2',
+ default: true,
+ },
+ ],
},
- withoutOptText: {
- label: 'Options without text',
- type: 'custom',
- options: {
- values: [
- { value: 'value1' },
- {
- value: 'value2',
- default: true,
- },
- ],
- },
+ },
+ customAdvancedWithoutOptText: {
+ label: 'Options without text',
+ type: 'custom',
+ options: {
+ values: [
+ { value: 'value1' },
+ {
+ value: 'value2',
+ default: true,
+ },
+ ],
},
},
},
-};
-
-const generateMockTemplatingData = data => {
- const vars = data
- ? {
- variables: {
- ...data,
- },
- }
- : {};
- return {
- dashboard: {
- templating: vars,
+ metricLabelValues: {
+ metricLabelValuesSimple: {
+ label: 'Metric Label Values',
+ type: 'metric_label_values',
+ options: {
+ prometheus_endpoint_path: '/series',
+ series_selector: 'backend:haproxy_backend_availability:ratio{env="{{env}}"}',
+ label: 'backend',
+ },
},
- };
+ },
};
-const responseForSimpleTextVariable = {
- simpleText: {
- label: 'simpleText',
+export const storeTextVariables = [
+ {
type: 'text',
- value: 'Simple text',
+ name: 'textSimple',
+ label: 'textSimple',
+ value: 'My default value',
},
-};
-
-const responseForAdvTextVariable = {
- advText: {
- label: 'Variable 4',
+ {
type: 'text',
- value: 'default',
+ name: 'textAdvanced',
+ label: 'Advanced text variable',
+ value: 'A default value',
},
-};
+];
-const responseForSimpleCustomVariable = {
- simpleCustom: {
- label: 'simpleCustom',
+export const storeCustomVariables = [
+ {
+ type: 'custom',
+ name: 'customSimple',
+ label: 'customSimple',
+ options: {
+ values: [
+ { default: false, text: 'value1', value: 'value1' },
+ { default: false, text: 'value2', value: 'value2' },
+ { default: false, text: 'value3', value: 'value3' },
+ ],
+ },
value: 'value1',
- options: [
- {
- default: false,
- text: 'value1',
- value: 'value1',
- },
- {
- default: false,
- text: 'value2',
- value: 'value2',
- },
- {
- default: false,
- text: 'value3',
- value: 'value3',
- },
- ],
+ },
+ {
type: 'custom',
+ name: 'customAdvanced',
+ label: 'Advanced Var',
+ options: {
+ values: [
+ { default: false, text: 'Var 1 Option 1', value: 'value1' },
+ { default: true, text: 'Var 1 Option 2', value: 'value2' },
+ ],
+ },
+ value: 'value2',
},
-};
-
-const responseForAdvancedCustomVariableWithoutOptions = {
- advCustomWithoutOpts: {
- label: 'advCustomWithoutOpts',
- options: [],
+ {
type: 'custom',
+ name: 'customAdvancedWithoutOpts',
+ label: 'customAdvancedWithoutOpts',
+ options: { values: [] },
+ value: null,
},
-};
-
-const responseForAdvancedCustomVariableWithoutLabel = {
- advCustomWithoutLabel: {
- label: 'advCustomWithoutLabel',
- value: 'value2',
- options: [
- {
- default: false,
- text: 'Var 1 Option 1',
- value: 'value1',
- },
- {
- default: true,
- text: 'Var 1 Option 2',
- value: 'value2',
- },
- ],
+ {
type: 'custom',
+ name: 'customAdvancedWithoutLabel',
+ label: 'customAdvancedWithoutLabel',
+ value: 'value2',
+ options: {
+ values: [
+ { default: false, text: 'Var 1 Option 1', value: 'value1' },
+ { default: true, text: 'Var 1 Option 2', value: 'value2' },
+ ],
+ },
},
-};
-
-const responseForAdvancedCustomVariableWithoutOptText = {
- advCustomWithoutOptText: {
+ {
+ type: 'custom',
+ name: 'customAdvancedWithoutOptText',
label: 'Options without text',
+ options: {
+ values: [
+ { default: false, text: 'value1', value: 'value1' },
+ { default: true, text: 'value2', value: 'value2' },
+ ],
+ },
value: 'value2',
- options: [
- {
- default: false,
- text: 'value1',
- value: 'value1',
- },
- {
- default: true,
- text: 'value2',
- value: 'value2',
- },
- ],
- type: 'custom',
},
-};
+];
-const responseForAdvancedCustomVariable = {
- ...responseForSimpleCustomVariable,
- advCustomNormal: {
- label: 'Advanced Var',
- value: 'value2',
- options: [
- {
- default: false,
- text: 'Var 1 Option 1',
- value: 'value1',
- },
- {
- default: true,
- text: 'Var 1 Option 2',
- value: 'value2',
- },
- ],
- type: 'custom',
+export const storeMetricLabelValuesVariables = [
+ {
+ type: 'metric_label_values',
+ name: 'metricLabelValuesSimple',
+ label: 'Metric Label Values',
+ options: { prometheusEndpointPath: '/series', label: 'backend', values: [] },
+ value: null,
},
-};
-
-const responsesForAllVariableTypes = {
- ...responseForSimpleTextVariable,
- ...responseForAdvTextVariable,
- ...responseForSimpleCustomVariable,
- ...responseForAdvancedCustomVariable,
-};
+];
-export const mockTemplatingData = {
- emptyTemplatingProp: generateMockTemplatingData(),
- emptyVariablesProp: generateMockTemplatingData({}),
- simpleText: generateMockTemplatingData({ simpleText: templatingVariableTypes.text.simple }),
- advText: generateMockTemplatingData({ advText: templatingVariableTypes.text.advanced }),
- simpleCustom: generateMockTemplatingData({ simpleCustom: templatingVariableTypes.custom.simple }),
- advCustomWithoutOpts: generateMockTemplatingData({
- advCustomWithoutOpts: templatingVariableTypes.custom.advanced.withoutOpts,
- }),
- advCustomWithoutType: generateMockTemplatingData({
- advCustomWithoutType: templatingVariableTypes.custom.advanced.withoutType,
- }),
- advCustomWithoutLabel: generateMockTemplatingData({
- advCustomWithoutLabel: templatingVariableTypes.custom.advanced.withoutLabel,
- }),
- advCustomWithoutOptText: generateMockTemplatingData({
- advCustomWithoutOptText: templatingVariableTypes.custom.advanced.withoutOptText,
- }),
- simpleAndAdv: generateMockTemplatingData({
- simpleCustom: templatingVariableTypes.custom.simple,
- advCustomNormal: templatingVariableTypes.custom.advanced.normal,
- }),
- allVariableTypes: generateMockTemplatingData({
- simpleText: templatingVariableTypes.text.simple,
- advText: templatingVariableTypes.text.advanced,
- simpleCustom: templatingVariableTypes.custom.simple,
- advCustomNormal: templatingVariableTypes.custom.advanced.normal,
- }),
-};
+export const storeVariables = [
+ ...storeTextVariables,
+ ...storeCustomVariables,
+ ...storeMetricLabelValuesVariables,
+];
-export const mockTemplatingDataResponses = {
- emptyTemplatingProp: {},
- emptyVariablesProp: {},
- simpleText: responseForSimpleTextVariable,
- advText: responseForAdvTextVariable,
- simpleCustom: responseForSimpleCustomVariable,
- advCustomWithoutOpts: responseForAdvancedCustomVariableWithoutOptions,
- advCustomWithoutType: {},
- advCustomWithoutLabel: responseForAdvancedCustomVariableWithoutLabel,
- advCustomWithoutOptText: responseForAdvancedCustomVariableWithoutOptText,
- simpleAndAdv: responseForAdvancedCustomVariable,
- allVariableTypes: responsesForAllVariableTypes,
+export const dashboardHeaderProps = {
+ defaultBranch: 'master',
+ addDashboardDocumentationPath: 'https://path/to/docs',
+ isRearrangingPanels: false,
+ selectedTimeRange: {
+ start: '2020-01-01T00:00:00.000Z',
+ end: '2020-01-01T01:00:00.000Z',
+ },
};
diff --git a/spec/frontend/monitoring/pages/dashboard_page_spec.js b/spec/frontend/monitoring/pages/dashboard_page_spec.js
index e3c56ef4cbf..675165e9e56 100644
--- a/spec/frontend/monitoring/pages/dashboard_page_spec.js
+++ b/spec/frontend/monitoring/pages/dashboard_page_spec.js
@@ -1,21 +1,42 @@
import { shallowMount } from '@vue/test-utils';
+import { createStore } from '~/monitoring/stores';
import DashboardPage from '~/monitoring/pages/dashboard_page.vue';
import Dashboard from '~/monitoring/components/dashboard.vue';
-import { propsData } from '../mock_data';
+import { dashboardProps } from '../fixture_data';
describe('monitoring/pages/dashboard_page', () => {
let wrapper;
+ let store;
+ let $route;
+
+ const buildRouter = () => {
+ const dashboard = {};
+ $route = {
+ params: { dashboard },
+ query: { dashboard },
+ };
+ };
const buildWrapper = (props = {}) => {
wrapper = shallowMount(DashboardPage, {
+ store,
propsData: {
...props,
},
+ mocks: {
+ $route,
+ },
});
};
const findDashboardComponent = () => wrapper.find(Dashboard);
+ beforeEach(() => {
+ buildRouter();
+ store = createStore();
+ jest.spyOn(store, 'dispatch').mockResolvedValue();
+ });
+
afterEach(() => {
if (wrapper) {
wrapper.destroy();
@@ -28,9 +49,18 @@ describe('monitoring/pages/dashboard_page', () => {
});
it('renders the dashboard page with dashboard component', () => {
- buildWrapper({ dashboardProps: propsData });
+ buildWrapper({ dashboardProps });
+
+ const allProps = {
+ ...dashboardProps,
+ // default props values
+ rearrangePanelsAvailable: false,
+ showHeader: true,
+ showPanels: true,
+ smallEmptyState: false,
+ };
- expect(findDashboardComponent().props()).toMatchObject(propsData);
expect(findDashboardComponent()).toExist();
+ expect(allProps).toMatchObject(findDashboardComponent().props());
});
});
diff --git a/spec/frontend/monitoring/router_spec.js b/spec/frontend/monitoring/router_spec.js
new file mode 100644
index 00000000000..5b8f4b3c83e
--- /dev/null
+++ b/spec/frontend/monitoring/router_spec.js
@@ -0,0 +1,81 @@
+import { mount, createLocalVue } from '@vue/test-utils';
+import VueRouter from 'vue-router';
+import DashboardPage from '~/monitoring/pages/dashboard_page.vue';
+import Dashboard from '~/monitoring/components/dashboard.vue';
+import { createStore } from '~/monitoring/stores';
+import createRouter from '~/monitoring/router';
+import { dashboardProps } from './fixture_data';
+import { dashboardHeaderProps } from './mock_data';
+
+describe('Monitoring router', () => {
+ let router;
+ let store;
+ const propsData = { dashboardProps: { ...dashboardProps, ...dashboardHeaderProps } };
+ const NEW_BASE_PATH = '/project/my-group/test-project/-/metrics';
+ const OLD_BASE_PATH = '/project/my-group/test-project/-/environments/71146/metrics';
+
+ const createWrapper = (basePath, routeArg) => {
+ const localVue = createLocalVue();
+ localVue.use(VueRouter);
+
+ router = createRouter(basePath);
+ if (routeArg !== undefined) {
+ router.push(routeArg);
+ }
+
+ return mount(DashboardPage, {
+ localVue,
+ store,
+ router,
+ propsData,
+ });
+ };
+
+ beforeEach(() => {
+ store = createStore();
+ jest.spyOn(store, 'dispatch').mockResolvedValue();
+ });
+
+ afterEach(() => {
+ window.location.hash = '';
+ });
+
+ describe('support old URL with full dashboard path', () => {
+ it.each`
+ route | currentDashboard
+ ${'/dashboard.yml'} | ${'dashboard.yml'}
+ ${'/folder1/dashboard.yml'} | ${'folder1/dashboard.yml'}
+ ${'/?dashboard=dashboard.yml'} | ${'dashboard.yml'}
+ `('sets component as $componentName for path "$route"', ({ route, currentDashboard }) => {
+ const wrapper = createWrapper(OLD_BASE_PATH, route);
+
+ expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/setCurrentDashboard', {
+ currentDashboard,
+ });
+
+ expect(wrapper.find(Dashboard)).toExist();
+ });
+ });
+
+ describe('supports new URL with short dashboard path', () => {
+ it.each`
+ route | currentDashboard
+ ${'/'} | ${null}
+ ${'/dashboard.yml'} | ${'dashboard.yml'}
+ ${'/folder1/dashboard.yml'} | ${'folder1/dashboard.yml'}
+ ${'/folder1%2Fdashboard.yml'} | ${'folder1/dashboard.yml'}
+ ${'/dashboard.yml'} | ${'dashboard.yml'}
+ ${'/config/prometheus/common_metrics.yml'} | ${'config/prometheus/common_metrics.yml'}
+ ${'/config/prometheus/pod_metrics.yml'} | ${'config/prometheus/pod_metrics.yml'}
+ ${'/config%2Fprometheus%2Fpod_metrics.yml'} | ${'config/prometheus/pod_metrics.yml'}
+ `('sets component as $componentName for path "$route"', ({ route, currentDashboard }) => {
+ const wrapper = createWrapper(NEW_BASE_PATH, route);
+
+ expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/setCurrentDashboard', {
+ currentDashboard,
+ });
+
+ expect(wrapper.find(Dashboard)).toExist();
+ });
+ });
+});
diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js
index d0290386f12..22f2b2e3c77 100644
--- a/spec/frontend/monitoring/store/actions_spec.js
+++ b/spec/frontend/monitoring/store/actions_spec.js
@@ -6,27 +6,30 @@ import statusCodes from '~/lib/utils/http_status';
import * as commonUtils from '~/lib/utils/common_utils';
import createFlash from '~/flash';
import { defaultTimeRange } from '~/vue_shared/constants';
+import * as getters from '~/monitoring/stores/getters';
import { ENVIRONMENT_AVAILABLE_STATE } from '~/monitoring/constants';
import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
import {
+ setGettingStartedEmptyState,
+ setInitialState,
+ setExpandedPanel,
+ clearExpandedPanel,
+ filterEnvironments,
fetchData,
fetchDashboard,
receiveMetricsDashboardSuccess,
+ fetchDashboardData,
+ fetchPrometheusMetric,
fetchDeploymentsData,
fetchEnvironmentsData,
- fetchDashboardData,
fetchAnnotations,
+ fetchDashboardValidationWarnings,
toggleStarredValue,
- fetchPrometheusMetric,
- setInitialState,
- filterEnvironments,
- setExpandedPanel,
- clearExpandedPanel,
- setGettingStartedEmptyState,
duplicateSystemDashboard,
updateVariablesAndFetchData,
+ fetchVariableMetricLabelValues,
} from '~/monitoring/stores/actions';
import {
gqClient,
@@ -35,12 +38,12 @@ import {
} from '~/monitoring/stores/utils';
import getEnvironments from '~/monitoring/queries/getEnvironments.query.graphql';
import getAnnotations from '~/monitoring/queries/getAnnotations.query.graphql';
+import getDashboardValidationWarnings from '~/monitoring/queries/getDashboardValidationWarnings.query.graphql';
import storeState from '~/monitoring/stores/state';
import {
deploymentData,
environmentData,
annotationsData,
- mockTemplatingData,
dashboardGitResponse,
mockDashboardsErrorResponse,
} from '../mock_data';
@@ -59,11 +62,17 @@ describe('Monitoring store actions', () => {
let store;
let state;
+ let dispatch;
+ let commit;
+
beforeEach(() => {
- store = createStore();
+ store = createStore({ getters });
state = store.state.monitoringDashboard;
mock = new MockAdapter(axios);
+ commit = jest.fn();
+ dispatch = jest.fn();
+
jest.spyOn(commonUtils, 'backOff').mockImplementation(callback => {
const q = new Promise((resolve, reject) => {
const stop = arg => (arg instanceof Error ? reject(arg) : resolve(arg));
@@ -78,6 +87,7 @@ describe('Monitoring store actions', () => {
return q;
});
});
+
afterEach(() => {
mock.reset();
@@ -85,377 +95,122 @@ describe('Monitoring store actions', () => {
createFlash.mockReset();
});
- describe('fetchData', () => {
- it('dispatches fetchEnvironmentsData and fetchEnvironmentsData', () => {
- return testAction(
- fetchData,
- null,
- state,
- [],
- [
- { type: 'fetchEnvironmentsData' },
- { type: 'fetchDashboard' },
- { type: 'fetchAnnotations' },
- ],
- );
- });
+ // Setup
- it('dispatches when feature metricsDashboardAnnotations is on', () => {
- const origGon = window.gon;
- window.gon = { features: { metricsDashboardAnnotations: true } };
-
- return testAction(
- fetchData,
+ describe('setGettingStartedEmptyState', () => {
+ it('should commit SET_GETTING_STARTED_EMPTY_STATE mutation', done => {
+ testAction(
+ setGettingStartedEmptyState,
null,
state,
- [],
[
- { type: 'fetchEnvironmentsData' },
- { type: 'fetchDashboard' },
- { type: 'fetchAnnotations' },
+ {
+ type: types.SET_GETTING_STARTED_EMPTY_STATE,
+ },
],
- ).then(() => {
- window.gon = origGon;
- });
- });
- });
-
- describe('fetchDeploymentsData', () => {
- it('dispatches receiveDeploymentsDataSuccess on success', () => {
- state.deploymentsEndpoint = '/success';
- mock.onGet(state.deploymentsEndpoint).reply(200, {
- deployments: deploymentData,
- });
-
- return testAction(
- fetchDeploymentsData,
- null,
- state,
- [],
- [{ type: 'receiveDeploymentsDataSuccess', payload: deploymentData }],
- );
- });
- it('dispatches receiveDeploymentsDataFailure on error', () => {
- state.deploymentsEndpoint = '/error';
- mock.onGet(state.deploymentsEndpoint).reply(500);
-
- return testAction(
- fetchDeploymentsData,
- null,
- state,
[],
- [{ type: 'receiveDeploymentsDataFailure' }],
- () => {
- expect(createFlash).toHaveBeenCalled();
- },
+ done,
);
});
});
- describe('fetchEnvironmentsData', () => {
- beforeEach(() => {
- state.projectPath = 'gitlab-org/gitlab-test';
- });
-
- it('setting SET_ENVIRONMENTS_FILTER should dispatch fetchEnvironmentsData', () => {
- jest.spyOn(gqClient, 'mutate').mockReturnValue({
- data: {
- project: {
- data: {
- environments: [],
- },
- },
+ describe('setInitialState', () => {
+ it('should commit SET_INITIAL_STATE mutation', done => {
+ testAction(
+ setInitialState,
+ {
+ currentDashboard: '.gitlab/dashboards/dashboard.yml',
+ deploymentsEndpoint: 'deployments.json',
},
- });
-
- return testAction(
- filterEnvironments,
- {},
state,
[
{
- type: 'SET_ENVIRONMENTS_FILTER',
- payload: {},
- },
- ],
- [
- {
- type: 'fetchEnvironmentsData',
+ type: types.SET_INITIAL_STATE,
+ payload: {
+ currentDashboard: '.gitlab/dashboards/dashboard.yml',
+ deploymentsEndpoint: 'deployments.json',
+ },
},
],
- );
- });
-
- it('fetch environments data call takes in search param', () => {
- const mockMutate = jest.spyOn(gqClient, 'mutate');
- const searchTerm = 'Something';
- const mutationVariables = {
- mutation: getEnvironments,
- variables: {
- projectPath: state.projectPath,
- search: searchTerm,
- states: [ENVIRONMENT_AVAILABLE_STATE],
- },
- };
- state.environmentsSearchTerm = searchTerm;
- mockMutate.mockResolvedValue({});
-
- return testAction(
- fetchEnvironmentsData,
- null,
- state,
[],
- [
- { type: 'requestEnvironmentsData' },
- { type: 'receiveEnvironmentsDataSuccess', payload: [] },
- ],
- () => {
- expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
- },
+ done,
);
});
+ });
- it('dispatches receiveEnvironmentsDataSuccess on success', () => {
- jest.spyOn(gqClient, 'mutate').mockResolvedValue({
- data: {
- project: {
- data: {
- environments: environmentData,
- },
- },
- },
- });
+ describe('setExpandedPanel', () => {
+ it('Sets a panel as expanded', () => {
+ const group = 'group_1';
+ const panel = { title: 'A Panel' };
return testAction(
- fetchEnvironmentsData,
- null,
+ setExpandedPanel,
+ { group, panel },
state,
+ [{ type: types.SET_EXPANDED_PANEL, payload: { group, panel } }],
[],
- [
- { type: 'requestEnvironmentsData' },
- {
- type: 'receiveEnvironmentsDataSuccess',
- payload: parseEnvironmentsResponse(environmentData, state.projectPath),
- },
- ],
);
});
+ });
- it('dispatches receiveEnvironmentsDataFailure on error', () => {
- jest.spyOn(gqClient, 'mutate').mockRejectedValue({});
-
+ describe('clearExpandedPanel', () => {
+ it('Clears a panel as expanded', () => {
return testAction(
- fetchEnvironmentsData,
- null,
+ clearExpandedPanel,
+ undefined,
state,
+ [{ type: types.SET_EXPANDED_PANEL, payload: { group: null, panel: null } }],
[],
- [{ type: 'requestEnvironmentsData' }, { type: 'receiveEnvironmentsDataFailure' }],
);
});
});
- describe('fetchAnnotations', () => {
- beforeEach(() => {
- state.timeRange = {
- start: '2020-04-15T12:54:32.137Z',
- end: '2020-08-15T12:54:32.137Z',
- };
- state.projectPath = 'gitlab-org/gitlab-test';
- state.currentEnvironmentName = 'production';
- state.currentDashboard = '.gitlab/dashboards/custom_dashboard.yml';
- });
-
- it('fetches annotations data and dispatches receiveAnnotationsSuccess', () => {
- const mockMutate = jest.spyOn(gqClient, 'mutate');
- const mutationVariables = {
- mutation: getAnnotations,
- variables: {
- projectPath: state.projectPath,
- environmentName: state.currentEnvironmentName,
- dashboardPath: state.currentDashboard,
- startingFrom: state.timeRange.start,
- },
- };
- const parsedResponse = parseAnnotationsResponse(annotationsData);
-
- mockMutate.mockResolvedValue({
- data: {
- project: {
- environments: {
- nodes: [
- {
- metricsDashboard: {
- annotations: {
- nodes: parsedResponse,
- },
- },
- },
- ],
- },
- },
- },
- });
+ // All Data
+ describe('fetchData', () => {
+ it('dispatches fetchEnvironmentsData and fetchEnvironmentsData', () => {
return testAction(
- fetchAnnotations,
+ fetchData,
null,
state,
[],
- [{ type: 'receiveAnnotationsSuccess', payload: parsedResponse }],
- () => {
- expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
- },
+ [
+ { type: 'fetchEnvironmentsData' },
+ { type: 'fetchDashboard' },
+ { type: 'fetchAnnotations' },
+ ],
);
});
- it('dispatches receiveAnnotationsFailure if the annotations API call fails', () => {
- const mockMutate = jest.spyOn(gqClient, 'mutate');
- const mutationVariables = {
- mutation: getAnnotations,
- variables: {
- projectPath: state.projectPath,
- environmentName: state.currentEnvironmentName,
- dashboardPath: state.currentDashboard,
- startingFrom: state.timeRange.start,
- },
- };
-
- mockMutate.mockRejectedValue({});
+ it('dispatches when feature metricsDashboardAnnotations is on', () => {
+ const origGon = window.gon;
+ window.gon = { features: { metricsDashboardAnnotations: true } };
return testAction(
- fetchAnnotations,
+ fetchData,
null,
state,
[],
- [{ type: 'receiveAnnotationsFailure' }],
- () => {
- expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
- },
- );
- });
- });
-
- describe('Toggles starred value of current dashboard', () => {
- let unstarredDashboard;
- let starredDashboard;
-
- beforeEach(() => {
- state.isUpdatingStarredValue = false;
- [unstarredDashboard, starredDashboard] = dashboardGitResponse;
- });
-
- describe('toggleStarredValue', () => {
- it('performs no changes if no dashboard is selected', () => {
- return testAction(toggleStarredValue, null, state, [], []);
- });
-
- it('performs no changes if already changing starred value', () => {
- state.selectedDashboard = unstarredDashboard;
- state.isUpdatingStarredValue = true;
- return testAction(toggleStarredValue, null, state, [], []);
- });
-
- it('stars dashboard if it is not starred', () => {
- state.selectedDashboard = unstarredDashboard;
- mock.onPost(unstarredDashboard.user_starred_path).reply(200);
-
- return testAction(toggleStarredValue, null, state, [
- { type: types.REQUEST_DASHBOARD_STARRING },
- {
- type: types.RECEIVE_DASHBOARD_STARRING_SUCCESS,
- payload: {
- newStarredValue: true,
- selectedDashboard: unstarredDashboard,
- },
- },
- ]);
- });
-
- it('unstars dashboard if it is starred', () => {
- state.selectedDashboard = starredDashboard;
- mock.onPost(starredDashboard.user_starred_path).reply(200);
-
- return testAction(toggleStarredValue, null, state, [
- { type: types.REQUEST_DASHBOARD_STARRING },
- { type: types.RECEIVE_DASHBOARD_STARRING_FAILURE },
- ]);
- });
- });
- });
-
- describe('Set initial state', () => {
- it('should commit SET_INITIAL_STATE mutation', done => {
- testAction(
- setInitialState,
- {
- currentDashboard: '.gitlab/dashboards/dashboard.yml',
- deploymentsEndpoint: 'deployments.json',
- },
- state,
- [
- {
- type: types.SET_INITIAL_STATE,
- payload: {
- currentDashboard: '.gitlab/dashboards/dashboard.yml',
- deploymentsEndpoint: 'deployments.json',
- },
- },
- ],
- [],
- done,
- );
- });
- });
- describe('Set empty states', () => {
- it('should commit SET_METRICS_ENDPOINT mutation', done => {
- testAction(
- setGettingStartedEmptyState,
- null,
- state,
[
- {
- type: types.SET_GETTING_STARTED_EMPTY_STATE,
- },
+ { type: 'fetchEnvironmentsData' },
+ { type: 'fetchDashboard' },
+ { type: 'fetchAnnotations' },
],
- [],
- done,
- );
+ ).then(() => {
+ window.gon = origGon;
+ });
});
});
- describe('updateVariablesAndFetchData', () => {
- it('should commit UPDATE_VARIABLES mutation and fetch data', done => {
- testAction(
- updateVariablesAndFetchData,
- { pod: 'POD' },
- state,
- [
- {
- type: types.UPDATE_VARIABLES,
- payload: { pod: 'POD' },
- },
- ],
- [
- {
- type: 'fetchDashboardData',
- },
- ],
- done,
- );
- });
- });
+ // Metrics dashboard
describe('fetchDashboard', () => {
- let dispatch;
- let commit;
const response = metricsDashboardResponse;
beforeEach(() => {
- dispatch = jest.fn();
- commit = jest.fn();
state.dashboardEndpoint = '/dashboard';
});
- it('on success, dispatches receive and success actions', () => {
+ it('on success, dispatches receive and success actions, then fetches dashboard warnings', () => {
document.body.dataset.page = 'projects:environments:metrics';
mock.onGet(state.dashboardEndpoint).reply(200, response);
@@ -470,6 +225,7 @@ describe('Monitoring store actions', () => {
type: 'receiveMetricsDashboardSuccess',
payload: { response },
},
+ { type: 'fetchDashboardValidationWarnings' },
],
);
});
@@ -478,9 +234,12 @@ describe('Monitoring store actions', () => {
let result;
beforeEach(() => {
const params = {};
+ const localGetters = {
+ fullDashboardPath: store.getters['monitoringDashboard/fullDashboardPath'],
+ };
result = () => {
mock.onGet(state.dashboardEndpoint).replyOnce(500, mockDashboardsErrorResponse);
- return fetchDashboard({ state, commit, dispatch }, params);
+ return fetchDashboard({ state, commit, dispatch, getters: localGetters }, params);
};
});
@@ -532,15 +291,8 @@ describe('Monitoring store actions', () => {
});
});
});
- describe('receiveMetricsDashboardSuccess', () => {
- let commit;
- let dispatch;
-
- beforeEach(() => {
- commit = jest.fn();
- dispatch = jest.fn();
- });
+ describe('receiveMetricsDashboardSuccess', () => {
it('stores groups', () => {
const response = metricsDashboardResponse;
receiveMetricsDashboardSuccess({ state, commit, dispatch }, { response });
@@ -552,32 +304,6 @@ describe('Monitoring store actions', () => {
expect(dispatch).toHaveBeenCalledWith('fetchDashboardData');
});
- it('stores templating variables', () => {
- const response = {
- ...metricsDashboardResponse.dashboard,
- ...mockTemplatingData.allVariableTypes.dashboard,
- };
-
- receiveMetricsDashboardSuccess(
- { state, commit, dispatch },
- {
- response: {
- ...metricsDashboardResponse,
- dashboard: {
- ...metricsDashboardResponse.dashboard,
- ...mockTemplatingData.allVariableTypes.dashboard,
- },
- },
- },
- );
-
- expect(commit).toHaveBeenCalledWith(
- types.RECEIVE_METRICS_DASHBOARD_SUCCESS,
-
- response,
- );
- });
-
it('sets the dashboards loaded from the repository', () => {
const params = {};
const response = metricsDashboardResponse;
@@ -596,23 +322,21 @@ describe('Monitoring store actions', () => {
expect(commit).toHaveBeenCalledWith(types.SET_ALL_DASHBOARDS, dashboardGitResponse);
});
});
- describe('fetchDashboardData', () => {
- let commit;
- let dispatch;
+ // Metrics
+
+ describe('fetchDashboardData', () => {
beforeEach(() => {
jest.spyOn(Tracking, 'event');
- commit = jest.fn();
- dispatch = jest.fn();
state.timeRange = defaultTimeRange;
});
it('commits empty state when state.groups is empty', done => {
- const getters = {
+ const localGetters = {
metricsWithData: () => [],
};
- fetchDashboardData({ state, commit, dispatch, getters })
+ fetchDashboardData({ state, commit, dispatch, getters: localGetters })
.then(() => {
expect(Tracking.event).toHaveBeenCalledWith(
document.body.dataset.page,
@@ -623,25 +347,33 @@ describe('Monitoring store actions', () => {
value: 0,
},
);
- expect(dispatch).toHaveBeenCalledTimes(1);
+ expect(dispatch).toHaveBeenCalledTimes(2);
expect(dispatch).toHaveBeenCalledWith('fetchDeploymentsData');
+ expect(dispatch).toHaveBeenCalledWith('fetchVariableMetricLabelValues', {
+ defaultQueryParams: {
+ start_time: expect.any(String),
+ end_time: expect.any(String),
+ step: expect.any(Number),
+ },
+ });
expect(createFlash).not.toHaveBeenCalled();
done();
})
.catch(done.fail);
});
+
it('dispatches fetchPrometheusMetric for each panel query', done => {
state.dashboard.panelGroups = convertObjectPropsToCamelCase(
metricsDashboardResponse.dashboard.panel_groups,
);
const [metric] = state.dashboard.panelGroups[0].panels[0].metrics;
- const getters = {
+ const localGetters = {
metricsWithData: () => [metric.id],
};
- fetchDashboardData({ state, commit, dispatch, getters })
+ fetchDashboardData({ state, commit, dispatch, getters: localGetters })
.then(() => {
expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetric', {
metric,
@@ -673,21 +405,27 @@ describe('Monitoring store actions', () => {
const metric = state.dashboard.panelGroups[0].panels[0].metrics[0];
dispatch.mockResolvedValueOnce(); // fetchDeploymentsData
+ dispatch.mockResolvedValueOnce(); // fetchVariableMetricLabelValues
// Mock having one out of four metrics failing
dispatch.mockRejectedValueOnce(new Error('Error fetching this metric'));
dispatch.mockResolvedValue();
fetchDashboardData({ state, commit, dispatch })
.then(() => {
- expect(dispatch).toHaveBeenCalledTimes(metricsDashboardPanelCount + 1); // plus 1 for deployments
+ const defaultQueryParams = {
+ start_time: expect.any(String),
+ end_time: expect.any(String),
+ step: expect.any(Number),
+ };
+
+ expect(dispatch).toHaveBeenCalledTimes(metricsDashboardPanelCount + 2); // plus 1 for deployments
expect(dispatch).toHaveBeenCalledWith('fetchDeploymentsData');
+ expect(dispatch).toHaveBeenCalledWith('fetchVariableMetricLabelValues', {
+ defaultQueryParams,
+ });
expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetric', {
metric,
- defaultQueryParams: {
- start_time: expect.any(String),
- end_time: expect.any(String),
- step: expect.any(Number),
- },
+ defaultQueryParams,
});
expect(createFlash).toHaveBeenCalledTimes(1);
@@ -698,6 +436,7 @@ describe('Monitoring store actions', () => {
done();
});
});
+
describe('fetchPrometheusMetric', () => {
const defaultQueryParams = {
start_time: '2019-08-06T12:40:02.184Z',
@@ -738,7 +477,7 @@ describe('Monitoring store actions', () => {
type: types.RECEIVE_METRIC_RESULT_SUCCESS,
payload: {
metricId: metric.metricId,
- result: data.result,
+ data,
},
},
],
@@ -775,7 +514,7 @@ describe('Monitoring store actions', () => {
type: types.RECEIVE_METRIC_RESULT_SUCCESS,
payload: {
metricId: metric.metricId,
- result: data.result,
+ data,
},
},
],
@@ -817,7 +556,7 @@ describe('Monitoring store actions', () => {
type: types.RECEIVE_METRIC_RESULT_SUCCESS,
payload: {
metricId: metric.metricId,
- result: data.result,
+ data,
},
},
],
@@ -852,7 +591,7 @@ describe('Monitoring store actions', () => {
type: types.RECEIVE_METRIC_RESULT_SUCCESS,
payload: {
metricId: metric.metricId,
- result: data.result,
+ data,
},
},
],
@@ -901,6 +640,402 @@ describe('Monitoring store actions', () => {
});
});
+ // Deployments
+
+ describe('fetchDeploymentsData', () => {
+ it('dispatches receiveDeploymentsDataSuccess on success', () => {
+ state.deploymentsEndpoint = '/success';
+ mock.onGet(state.deploymentsEndpoint).reply(200, {
+ deployments: deploymentData,
+ });
+
+ return testAction(
+ fetchDeploymentsData,
+ null,
+ state,
+ [],
+ [{ type: 'receiveDeploymentsDataSuccess', payload: deploymentData }],
+ );
+ });
+ it('dispatches receiveDeploymentsDataFailure on error', () => {
+ state.deploymentsEndpoint = '/error';
+ mock.onGet(state.deploymentsEndpoint).reply(500);
+
+ return testAction(
+ fetchDeploymentsData,
+ null,
+ state,
+ [],
+ [{ type: 'receiveDeploymentsDataFailure' }],
+ () => {
+ expect(createFlash).toHaveBeenCalled();
+ },
+ );
+ });
+ });
+
+ // Environments
+
+ describe('fetchEnvironmentsData', () => {
+ beforeEach(() => {
+ state.projectPath = 'gitlab-org/gitlab-test';
+ });
+
+ it('setting SET_ENVIRONMENTS_FILTER should dispatch fetchEnvironmentsData', () => {
+ jest.spyOn(gqClient, 'mutate').mockReturnValue({
+ data: {
+ project: {
+ data: {
+ environments: [],
+ },
+ },
+ },
+ });
+
+ return testAction(
+ filterEnvironments,
+ {},
+ state,
+ [
+ {
+ type: 'SET_ENVIRONMENTS_FILTER',
+ payload: {},
+ },
+ ],
+ [
+ {
+ type: 'fetchEnvironmentsData',
+ },
+ ],
+ );
+ });
+
+ it('fetch environments data call takes in search param', () => {
+ const mockMutate = jest.spyOn(gqClient, 'mutate');
+ const searchTerm = 'Something';
+ const mutationVariables = {
+ mutation: getEnvironments,
+ variables: {
+ projectPath: state.projectPath,
+ search: searchTerm,
+ states: [ENVIRONMENT_AVAILABLE_STATE],
+ },
+ };
+ state.environmentsSearchTerm = searchTerm;
+ mockMutate.mockResolvedValue({});
+
+ return testAction(
+ fetchEnvironmentsData,
+ null,
+ state,
+ [],
+ [
+ { type: 'requestEnvironmentsData' },
+ { type: 'receiveEnvironmentsDataSuccess', payload: [] },
+ ],
+ () => {
+ expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
+ },
+ );
+ });
+
+ it('dispatches receiveEnvironmentsDataSuccess on success', () => {
+ jest.spyOn(gqClient, 'mutate').mockResolvedValue({
+ data: {
+ project: {
+ data: {
+ environments: environmentData,
+ },
+ },
+ },
+ });
+
+ return testAction(
+ fetchEnvironmentsData,
+ null,
+ state,
+ [],
+ [
+ { type: 'requestEnvironmentsData' },
+ {
+ type: 'receiveEnvironmentsDataSuccess',
+ payload: parseEnvironmentsResponse(environmentData, state.projectPath),
+ },
+ ],
+ );
+ });
+
+ it('dispatches receiveEnvironmentsDataFailure on error', () => {
+ jest.spyOn(gqClient, 'mutate').mockRejectedValue({});
+
+ return testAction(
+ fetchEnvironmentsData,
+ null,
+ state,
+ [],
+ [{ type: 'requestEnvironmentsData' }, { type: 'receiveEnvironmentsDataFailure' }],
+ );
+ });
+ });
+
+ describe('fetchAnnotations', () => {
+ beforeEach(() => {
+ state.timeRange = {
+ start: '2020-04-15T12:54:32.137Z',
+ end: '2020-08-15T12:54:32.137Z',
+ };
+ state.projectPath = 'gitlab-org/gitlab-test';
+ state.currentEnvironmentName = 'production';
+ state.currentDashboard = '.gitlab/dashboards/custom_dashboard.yml';
+ // testAction doesn't have access to getters. The state is passed in as getters
+ // instead of the actual getters inside the testAction method implementation.
+ // All methods downstream that needs access to getters will throw and error.
+ // For that reason, the result of the getter is set as a state variable.
+ state.fullDashboardPath = store.getters['monitoringDashboard/fullDashboardPath'];
+ });
+
+ it('fetches annotations data and dispatches receiveAnnotationsSuccess', () => {
+ const mockMutate = jest.spyOn(gqClient, 'mutate');
+ const mutationVariables = {
+ mutation: getAnnotations,
+ variables: {
+ projectPath: state.projectPath,
+ environmentName: state.currentEnvironmentName,
+ dashboardPath: state.currentDashboard,
+ startingFrom: state.timeRange.start,
+ },
+ };
+ const parsedResponse = parseAnnotationsResponse(annotationsData);
+
+ mockMutate.mockResolvedValue({
+ data: {
+ project: {
+ environments: {
+ nodes: [
+ {
+ metricsDashboard: {
+ annotations: {
+ nodes: parsedResponse,
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ return testAction(
+ fetchAnnotations,
+ null,
+ state,
+ [],
+ [{ type: 'receiveAnnotationsSuccess', payload: parsedResponse }],
+ () => {
+ expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
+ },
+ );
+ });
+
+ it('dispatches receiveAnnotationsFailure if the annotations API call fails', () => {
+ const mockMutate = jest.spyOn(gqClient, 'mutate');
+ const mutationVariables = {
+ mutation: getAnnotations,
+ variables: {
+ projectPath: state.projectPath,
+ environmentName: state.currentEnvironmentName,
+ dashboardPath: state.currentDashboard,
+ startingFrom: state.timeRange.start,
+ },
+ };
+
+ mockMutate.mockRejectedValue({});
+
+ return testAction(
+ fetchAnnotations,
+ null,
+ state,
+ [],
+ [{ type: 'receiveAnnotationsFailure' }],
+ () => {
+ expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
+ },
+ );
+ });
+ });
+
+ describe('fetchDashboardValidationWarnings', () => {
+ let mockMutate;
+ let mutationVariables;
+
+ beforeEach(() => {
+ state.projectPath = 'gitlab-org/gitlab-test';
+ state.currentEnvironmentName = 'production';
+ state.currentDashboard = '.gitlab/dashboards/dashboard_with_warnings.yml';
+ // testAction doesn't have access to getters. The state is passed in as getters
+ // instead of the actual getters inside the testAction method implementation.
+ // All methods downstream that needs access to getters will throw and error.
+ // For that reason, the result of the getter is set as a state variable.
+ state.fullDashboardPath = store.getters['monitoringDashboard/fullDashboardPath'];
+
+ mockMutate = jest.spyOn(gqClient, 'mutate');
+ mutationVariables = {
+ mutation: getDashboardValidationWarnings,
+ variables: {
+ projectPath: state.projectPath,
+ environmentName: state.currentEnvironmentName,
+ dashboardPath: state.fullDashboardPath,
+ },
+ };
+ });
+
+ it('dispatches receiveDashboardValidationWarningsSuccess with true payload when there are warnings', () => {
+ mockMutate.mockResolvedValue({
+ data: {
+ project: {
+ id: 'gid://gitlab/Project/29',
+ environments: {
+ nodes: [
+ {
+ name: 'production',
+ metricsDashboard: {
+ path: '.gitlab/dashboards/dashboard_errors_test.yml',
+ schemaValidationWarnings: ["unit: can't be blank"],
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ return testAction(
+ fetchDashboardValidationWarnings,
+ null,
+ state,
+ [],
+ [{ type: 'receiveDashboardValidationWarningsSuccess', payload: true }],
+ () => {
+ expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
+ },
+ );
+ });
+
+ it('dispatches receiveDashboardValidationWarningsSuccess with false payload when there are no warnings', () => {
+ mockMutate.mockResolvedValue({
+ data: {
+ project: {
+ id: 'gid://gitlab/Project/29',
+ environments: {
+ nodes: [
+ {
+ name: 'production',
+ metricsDashboard: {
+ path: '.gitlab/dashboards/dashboard_errors_test.yml',
+ schemaValidationWarnings: [],
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ return testAction(
+ fetchDashboardValidationWarnings,
+ null,
+ state,
+ [],
+ [{ type: 'receiveDashboardValidationWarningsSuccess', payload: false }],
+ () => {
+ expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
+ },
+ );
+ });
+
+ it('dispatches receiveDashboardValidationWarningsSuccess with false payload when the response is empty ', () => {
+ mockMutate.mockResolvedValue({
+ data: {
+ project: null,
+ },
+ });
+
+ return testAction(
+ fetchDashboardValidationWarnings,
+ null,
+ state,
+ [],
+ [{ type: 'receiveDashboardValidationWarningsSuccess', payload: false }],
+ () => {
+ expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
+ },
+ );
+ });
+
+ it('dispatches receiveDashboardValidationWarningsFailure if the warnings API call fails', () => {
+ mockMutate.mockRejectedValue({});
+
+ return testAction(
+ fetchDashboardValidationWarnings,
+ null,
+ state,
+ [],
+ [{ type: 'receiveDashboardValidationWarningsFailure' }],
+ () => {
+ expect(mockMutate).toHaveBeenCalledWith(mutationVariables);
+ },
+ );
+ });
+ });
+
+ // Dashboard manipulation
+
+ describe('toggleStarredValue', () => {
+ let unstarredDashboard;
+ let starredDashboard;
+
+ beforeEach(() => {
+ state.isUpdatingStarredValue = false;
+ [unstarredDashboard, starredDashboard] = dashboardGitResponse;
+ });
+
+ it('performs no changes if no dashboard is selected', () => {
+ return testAction(toggleStarredValue, null, state, [], []);
+ });
+
+ it('performs no changes if already changing starred value', () => {
+ state.selectedDashboard = unstarredDashboard;
+ state.isUpdatingStarredValue = true;
+ return testAction(toggleStarredValue, null, state, [], []);
+ });
+
+ it('stars dashboard if it is not starred', () => {
+ state.selectedDashboard = unstarredDashboard;
+ mock.onPost(unstarredDashboard.user_starred_path).reply(200);
+
+ return testAction(toggleStarredValue, null, state, [
+ { type: types.REQUEST_DASHBOARD_STARRING },
+ {
+ type: types.RECEIVE_DASHBOARD_STARRING_SUCCESS,
+ payload: {
+ newStarredValue: true,
+ selectedDashboard: unstarredDashboard,
+ },
+ },
+ ]);
+ });
+
+ it('unstars dashboard if it is starred', () => {
+ state.selectedDashboard = starredDashboard;
+ mock.onPost(starredDashboard.user_starred_path).reply(200);
+
+ return testAction(toggleStarredValue, null, state, [
+ { type: types.REQUEST_DASHBOARD_STARRING },
+ { type: types.RECEIVE_DASHBOARD_STARRING_FAILURE },
+ ]);
+ });
+ });
+
describe('duplicateSystemDashboard', () => {
beforeEach(() => {
state.dashboardsEndpoint = '/dashboards.json';
@@ -979,30 +1114,95 @@ describe('Monitoring store actions', () => {
});
});
- describe('setExpandedPanel', () => {
- it('Sets a panel as expanded', () => {
- const group = 'group_1';
- const panel = { title: 'A Panel' };
+ // Variables manipulation
- return testAction(
- setExpandedPanel,
- { group, panel },
+ describe('updateVariablesAndFetchData', () => {
+ it('should commit UPDATE_VARIABLE_VALUE mutation and fetch data', done => {
+ testAction(
+ updateVariablesAndFetchData,
+ { pod: 'POD' },
state,
- [{ type: types.SET_EXPANDED_PANEL, payload: { group, panel } }],
- [],
+ [
+ {
+ type: types.UPDATE_VARIABLE_VALUE,
+ payload: { pod: 'POD' },
+ },
+ ],
+ [
+ {
+ type: 'fetchDashboardData',
+ },
+ ],
+ done,
);
});
});
- describe('clearExpandedPanel', () => {
- it('Clears a panel as expanded', () => {
+ describe('fetchVariableMetricLabelValues', () => {
+ const variable = {
+ type: 'metric_label_values',
+ name: 'label1',
+ options: {
+ prometheusEndpointPath: '/series?match[]=metric_name',
+ label: 'job',
+ },
+ };
+
+ const defaultQueryParams = {
+ start_time: '2019-08-06T12:40:02.184Z',
+ end_time: '2019-08-06T20:40:02.184Z',
+ };
+
+ beforeEach(() => {
+ state = {
+ ...state,
+ timeRange: defaultTimeRange,
+ variables: [variable],
+ };
+ });
+
+ it('should commit UPDATE_VARIABLE_METRIC_LABEL_VALUES mutation and fetch data', () => {
+ const data = [
+ {
+ __name__: 'up',
+ job: 'prometheus',
+ },
+ {
+ __name__: 'up',
+ job: 'POD',
+ },
+ ];
+
+ mock.onGet('/series?match[]=metric_name').reply(200, {
+ status: 'success',
+ data,
+ });
+
return testAction(
- clearExpandedPanel,
- undefined,
+ fetchVariableMetricLabelValues,
+ { defaultQueryParams },
state,
- [{ type: types.SET_EXPANDED_PANEL, payload: { group: null, panel: null } }],
+ [
+ {
+ type: types.UPDATE_VARIABLE_METRIC_LABEL_VALUES,
+ payload: { variable, label: 'job', data },
+ },
+ ],
[],
);
});
+
+ it('should notify the user that dynamic options were not loaded', () => {
+ mock.onGet('/series?match[]=metric_name').reply(500);
+
+ return testAction(fetchVariableMetricLabelValues, { defaultQueryParams }, state, [], []).then(
+ () => {
+ expect(createFlash).toHaveBeenCalledTimes(1);
+ expect(createFlash).toHaveBeenCalledWith(
+ expect.stringContaining('error getting options for variable "label1"'),
+ );
+ },
+ );
+ });
});
});
diff --git a/spec/frontend/monitoring/store/getters_spec.js b/spec/frontend/monitoring/store/getters_spec.js
index 933ccb1e46c..a69f5265ea7 100644
--- a/spec/frontend/monitoring/store/getters_spec.js
+++ b/spec/frontend/monitoring/store/getters_spec.js
@@ -4,10 +4,11 @@ import mutations from '~/monitoring/stores/mutations';
import * as types from '~/monitoring/stores/mutation_types';
import { metricStates } from '~/monitoring/constants';
import {
+ customDashboardBasePath,
environmentData,
metricsResult,
dashboardGitResponse,
- mockTemplatingDataResponses,
+ storeVariables,
mockLinks,
} from '../mock_data';
import {
@@ -27,7 +28,10 @@ describe('Monitoring store Getters', () => {
const { metricId } = state.dashboard.panelGroups[group].panels[panel].metrics[metric];
mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, {
metricId,
- result,
+ data: {
+ resultType: 'matrix',
+ result,
+ },
});
};
@@ -340,19 +344,21 @@ describe('Monitoring store Getters', () => {
});
it('transforms the variables object to an array in the [variable, variable_value] format for all variable types', () => {
- mutations[types.SET_VARIABLES](state, mockTemplatingDataResponses.allVariableTypes);
+ state.variables = storeVariables;
const variablesArray = getters.getCustomVariablesParams(state);
expect(variablesArray).toEqual({
- 'variables[advCustomNormal]': 'value2',
- 'variables[advText]': 'default',
- 'variables[simpleCustom]': 'value1',
- 'variables[simpleText]': 'Simple text',
+ 'variables[textSimple]': 'My default value',
+ 'variables[textAdvanced]': 'A default value',
+ 'variables[customSimple]': 'value1',
+ 'variables[customAdvanced]': 'value2',
+ 'variables[customAdvancedWithoutLabel]': 'value2',
+ 'variables[customAdvancedWithoutOptText]': 'value2',
});
});
it('transforms the variables object to an empty array when no keys are present', () => {
- mutations[types.SET_VARIABLES](state, {});
+ state.variables = [];
const variablesArray = getters.getCustomVariablesParams(state);
expect(variablesArray).toEqual({});
@@ -361,45 +367,53 @@ describe('Monitoring store Getters', () => {
describe('selectedDashboard', () => {
const { selectedDashboard } = getters;
+ const localGetters = state => ({
+ fullDashboardPath: getters.fullDashboardPath(state),
+ });
it('returns a dashboard', () => {
const state = {
allDashboards: dashboardGitResponse,
currentDashboard: dashboardGitResponse[0].path,
+ customDashboardBasePath,
};
- expect(selectedDashboard(state)).toEqual(dashboardGitResponse[0]);
+ expect(selectedDashboard(state, localGetters(state))).toEqual(dashboardGitResponse[0]);
});
it('returns a non-default dashboard', () => {
const state = {
allDashboards: dashboardGitResponse,
currentDashboard: dashboardGitResponse[1].path,
+ customDashboardBasePath,
};
- expect(selectedDashboard(state)).toEqual(dashboardGitResponse[1]);
+ expect(selectedDashboard(state, localGetters(state))).toEqual(dashboardGitResponse[1]);
});
it('returns a default dashboard when no dashboard is selected', () => {
const state = {
allDashboards: dashboardGitResponse,
currentDashboard: null,
+ customDashboardBasePath,
};
- expect(selectedDashboard(state)).toEqual(dashboardGitResponse[0]);
+ expect(selectedDashboard(state, localGetters(state))).toEqual(dashboardGitResponse[0]);
});
it('returns a default dashboard when dashboard cannot be found', () => {
const state = {
allDashboards: dashboardGitResponse,
currentDashboard: 'wrong_path',
+ customDashboardBasePath,
};
- expect(selectedDashboard(state)).toEqual(dashboardGitResponse[0]);
+ expect(selectedDashboard(state, localGetters(state))).toEqual(dashboardGitResponse[0]);
});
it('returns null when no dashboards are present', () => {
const state = {
allDashboards: [],
currentDashboard: dashboardGitResponse[0].path,
+ customDashboardBasePath,
};
- expect(selectedDashboard(state)).toEqual(null);
+ expect(selectedDashboard(state, localGetters(state))).toEqual(null);
});
});
diff --git a/spec/frontend/monitoring/store/mutations_spec.js b/spec/frontend/monitoring/store/mutations_spec.js
index 0283f1a86a4..14b38d79aa2 100644
--- a/spec/frontend/monitoring/store/mutations_spec.js
+++ b/spec/frontend/monitoring/store/mutations_spec.js
@@ -3,9 +3,9 @@ import httpStatusCodes from '~/lib/utils/http_status';
import mutations from '~/monitoring/stores/mutations';
import * as types from '~/monitoring/stores/mutation_types';
import state from '~/monitoring/stores/state';
-import { metricStates } from '~/monitoring/constants';
+import { dashboardEmptyStates, metricStates } from '~/monitoring/constants';
-import { deploymentData, dashboardGitResponse } from '../mock_data';
+import { deploymentData, dashboardGitResponse, storeTextVariables } from '../mock_data';
import { metricsDashboardPayload } from '../fixture_data';
describe('Monitoring mutations', () => {
@@ -15,6 +15,14 @@ describe('Monitoring mutations', () => {
stateCopy = state();
});
+ describe('REQUEST_METRICS_DASHBOARD', () => {
+ it('sets an empty loading state', () => {
+ mutations[types.REQUEST_METRICS_DASHBOARD](stateCopy);
+
+ expect(stateCopy.emptyState).toBe(dashboardEmptyStates.LOADING);
+ });
+ });
+
describe('RECEIVE_METRICS_DASHBOARD_SUCCESS', () => {
let payload;
const getGroups = () => stateCopy.dashboard.panelGroups;
@@ -23,6 +31,18 @@ describe('Monitoring mutations', () => {
stateCopy.dashboard.panelGroups = [];
payload = metricsDashboardPayload;
});
+ it('sets an empty noData state when the dashboard is empty', () => {
+ const emptyDashboardPayload = {
+ ...payload,
+ panel_groups: [],
+ };
+
+ mutations[types.RECEIVE_METRICS_DASHBOARD_SUCCESS](stateCopy, emptyDashboardPayload);
+ const groups = getGroups();
+
+ expect(groups).toEqual([]);
+ expect(stateCopy.emptyState).toBe(dashboardEmptyStates.NO_DATA);
+ });
it('adds a key to the group', () => {
mutations[types.RECEIVE_METRICS_DASHBOARD_SUCCESS](stateCopy, payload);
const groups = getGroups();
@@ -72,6 +92,20 @@ describe('Monitoring mutations', () => {
});
});
+ describe('RECEIVE_METRICS_DASHBOARD_FAILURE', () => {
+ it('sets an empty noData state when an empty error occurs', () => {
+ mutations[types.RECEIVE_METRICS_DASHBOARD_FAILURE](stateCopy);
+
+ expect(stateCopy.emptyState).toBe(dashboardEmptyStates.NO_DATA);
+ });
+
+ it('sets an empty unableToConnect state when an error occurs', () => {
+ mutations[types.RECEIVE_METRICS_DASHBOARD_FAILURE](stateCopy, 'myerror');
+
+ expect(stateCopy.emptyState).toBe(dashboardEmptyStates.UNABLE_TO_CONNECT);
+ });
+ });
+
describe('Dashboard starring mutations', () => {
it('REQUEST_DASHBOARD_STARRING', () => {
stateCopy = { isUpdatingStarredValue: false };
@@ -225,11 +259,28 @@ describe('Monitoring mutations', () => {
describe('Individual panel/metric results', () => {
const metricId = 'NO_DB_response_metrics_nginx_ingress_throughput_status_code';
- const result = [
- {
- values: [[0, 1], [1, 1], [1, 3]],
- },
- ];
+ const data = {
+ resultType: 'matrix',
+ result: [
+ {
+ metric: {
+ __name__: 'up',
+ job: 'prometheus',
+ instance: 'localhost:9090',
+ },
+ values: [[1435781430.781, '1'], [1435781445.781, '1'], [1435781460.781, '1']],
+ },
+ {
+ metric: {
+ __name__: 'up',
+ job: 'node',
+ instance: 'localhost:9091',
+ },
+ values: [[1435781430.781, '0'], [1435781445.781, '0'], [1435781460.781, '1']],
+ },
+ ],
+ };
+
const dashboard = metricsDashboardPayload;
const getMetric = () => stateCopy.dashboard.panelGroups[1].panels[0].metrics[0];
@@ -238,13 +289,10 @@ describe('Monitoring mutations', () => {
mutations[types.RECEIVE_METRICS_DASHBOARD_SUCCESS](stateCopy, dashboard);
});
it('stores a loading state on a metric', () => {
- expect(stateCopy.showEmptyState).toBe(true);
-
mutations[types.REQUEST_METRIC_RESULT](stateCopy, {
metricId,
});
- expect(stateCopy.showEmptyState).toBe(true);
expect(getMetric()).toEqual(
expect.objectContaining({
loading: true,
@@ -257,26 +305,16 @@ describe('Monitoring mutations', () => {
beforeEach(() => {
mutations[types.RECEIVE_METRICS_DASHBOARD_SUCCESS](stateCopy, dashboard);
});
- it('clears empty state', () => {
- expect(stateCopy.showEmptyState).toBe(true);
-
- mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](stateCopy, {
- metricId,
- result,
- });
-
- expect(stateCopy.showEmptyState).toBe(false);
- });
it('adds results to the store', () => {
expect(getMetric().result).toBe(null);
mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](stateCopy, {
metricId,
- result,
+ data,
});
- expect(getMetric().result).toHaveLength(result.length);
+ expect(getMetric().result).toHaveLength(data.result.length);
expect(getMetric()).toEqual(
expect.objectContaining({
loading: false,
@@ -290,16 +328,6 @@ describe('Monitoring mutations', () => {
beforeEach(() => {
mutations[types.RECEIVE_METRICS_DASHBOARD_SUCCESS](stateCopy, dashboard);
});
- it('maintains the loading state when a metric fails', () => {
- expect(stateCopy.showEmptyState).toBe(true);
-
- mutations[types.RECEIVE_METRIC_RESULT_FAILURE](stateCopy, {
- metricId,
- error: 'an error',
- });
-
- expect(stateCopy.showEmptyState).toBe(true);
- });
it('stores a timeout error in a metric', () => {
mutations[types.RECEIVE_METRIC_RESULT_FAILURE](stateCopy, {
@@ -369,6 +397,7 @@ describe('Monitoring mutations', () => {
});
});
});
+
describe('SET_ALL_DASHBOARDS', () => {
it('stores `undefined` dashboards as an empty array', () => {
mutations[types.SET_ALL_DASHBOARDS](stateCopy, undefined);
@@ -410,30 +439,53 @@ describe('Monitoring mutations', () => {
});
});
- describe('SET_VARIABLES', () => {
- it('stores an empty variables array when no custom variables are given', () => {
- mutations[types.SET_VARIABLES](stateCopy, {});
-
- expect(stateCopy.variables).toEqual({});
- });
-
- it('stores variables in the key key_value format in the array', () => {
- mutations[types.SET_VARIABLES](stateCopy, { pod: 'POD', stage: 'main ops' });
+ describe('UPDATE_VARIABLE_VALUE', () => {
+ it('updates only the value of the variable in variables', () => {
+ stateCopy.variables = storeTextVariables;
+ mutations[types.UPDATE_VARIABLE_VALUE](stateCopy, { name: 'textSimple', value: 'New Value' });
- expect(stateCopy.variables).toEqual({ pod: 'POD', stage: 'main ops' });
+ expect(stateCopy.variables[0].value).toEqual('New Value');
});
});
- describe('UPDATE_VARIABLES', () => {
- afterEach(() => {
- mutations[types.SET_VARIABLES](stateCopy, {});
- });
-
- it('updates only the value of the variable in variables', () => {
- mutations[types.SET_VARIABLES](stateCopy, { environment: { value: 'prod', type: 'text' } });
- mutations[types.UPDATE_VARIABLES](stateCopy, { key: 'environment', value: 'new prod' });
+ describe('UPDATE_VARIABLE_METRIC_LABEL_VALUES', () => {
+ it('updates options in a variable', () => {
+ const data = [
+ {
+ __name__: 'up',
+ job: 'prometheus',
+ env: 'prd',
+ },
+ {
+ __name__: 'up',
+ job: 'prometheus',
+ env: 'stg',
+ },
+ {
+ __name__: 'up',
+ job: 'node',
+ env: 'prod',
+ },
+ {
+ __name__: 'up',
+ job: 'node',
+ env: 'stg',
+ },
+ ];
+
+ const variable = {
+ options: {},
+ };
+
+ mutations[types.UPDATE_VARIABLE_METRIC_LABEL_VALUES](stateCopy, {
+ variable,
+ label: 'job',
+ data,
+ });
- expect(stateCopy.variables).toEqual({ environment: { value: 'new prod', type: 'text' } });
+ expect(variable.options).toEqual({
+ values: [{ text: 'prometheus', value: 'prometheus' }, { text: 'node', value: 'node' }],
+ });
});
});
});
diff --git a/spec/frontend/monitoring/store/utils_spec.js b/spec/frontend/monitoring/store/utils_spec.js
index 2dea40585f1..b97948fa1bf 100644
--- a/spec/frontend/monitoring/store/utils_spec.js
+++ b/spec/frontend/monitoring/store/utils_spec.js
@@ -5,9 +5,10 @@ import {
parseAnnotationsResponse,
removeLeadingSlash,
mapToDashboardViewModel,
- normalizeQueryResult,
+ normalizeQueryResponseData,
convertToGrafanaTimeRange,
addDashboardMetaDataToLink,
+ normalizeCustomDashboardPath,
} from '~/monitoring/stores/utils';
import * as urlUtils from '~/lib/utils/url_utility';
import { annotationsData } from '../mock_data';
@@ -21,7 +22,7 @@ describe('mapToDashboardViewModel', () => {
dashboard: '',
panelGroups: [],
links: [],
- variables: {},
+ variables: [],
});
});
@@ -51,7 +52,7 @@ describe('mapToDashboardViewModel', () => {
expect(mapToDashboardViewModel(response)).toEqual({
dashboard: 'Dashboard Name',
links: [],
- variables: {},
+ variables: [],
panelGroups: [
{
group: 'Group 1',
@@ -423,22 +424,20 @@ describe('mapToDashboardViewModel', () => {
urlUtils.queryToObject.mockReturnValueOnce();
- expect(mapToDashboardViewModel(response)).toMatchObject({
- dashboard: 'Dashboard Name',
- links: [],
- variables: {
- pod: {
- label: 'pod',
- type: 'text',
- value: 'kubernetes',
- },
- pod_2: {
- label: 'pod_2',
- type: 'text',
- value: 'kubernetes-2',
- },
+ expect(mapToDashboardViewModel(response).variables).toEqual([
+ {
+ name: 'pod',
+ label: 'pod',
+ type: 'text',
+ value: 'kubernetes',
},
- });
+ {
+ name: 'pod_2',
+ label: 'pod_2',
+ type: 'text',
+ value: 'kubernetes-2',
+ },
+ ]);
});
it('sets variables as-is from yml file if URL has no matching variables', () => {
@@ -457,22 +456,20 @@ describe('mapToDashboardViewModel', () => {
'var-environment': 'POD',
});
- expect(mapToDashboardViewModel(response)).toMatchObject({
- dashboard: 'Dashboard Name',
- links: [],
- variables: {
- pod: {
- label: 'pod',
- type: 'text',
- value: 'kubernetes',
- },
- pod_2: {
- label: 'pod_2',
- type: 'text',
- value: 'kubernetes-2',
- },
+ expect(mapToDashboardViewModel(response).variables).toEqual([
+ {
+ label: 'pod',
+ name: 'pod',
+ type: 'text',
+ value: 'kubernetes',
},
- });
+ {
+ label: 'pod_2',
+ name: 'pod_2',
+ type: 'text',
+ value: 'kubernetes-2',
+ },
+ ]);
});
it('merges variables from URL with the ones from yml file', () => {
@@ -493,44 +490,20 @@ describe('mapToDashboardViewModel', () => {
'var-pod_2': 'POD2',
});
- expect(mapToDashboardViewModel(response)).toMatchObject({
- dashboard: 'Dashboard Name',
- links: [],
- variables: {
- pod: {
- label: 'pod',
- type: 'text',
- value: 'POD1',
- },
- pod_2: {
- label: 'pod_2',
- type: 'text',
- value: 'POD2',
- },
+ expect(mapToDashboardViewModel(response).variables).toEqual([
+ {
+ label: 'pod',
+ name: 'pod',
+ type: 'text',
+ value: 'POD1',
},
- });
- });
- });
-});
-
-describe('normalizeQueryResult', () => {
- const testData = {
- metric: {
- __name__: 'up',
- job: 'prometheus',
- instance: 'localhost:9090',
- },
- values: [[1435781430.781, '1'], [1435781445.781, '1'], [1435781460.781, '1']],
- };
-
- it('processes a simple matrix result', () => {
- expect(normalizeQueryResult(testData)).toEqual({
- metric: { __name__: 'up', job: 'prometheus', instance: 'localhost:9090' },
- values: [
- ['2015-07-01T20:10:30.781Z', 1],
- ['2015-07-01T20:10:45.781Z', 1],
- ['2015-07-01T20:11:00.781Z', 1],
- ],
+ {
+ label: 'pod_2',
+ name: 'pod_2',
+ type: 'text',
+ value: 'POD2',
+ },
+ ]);
});
});
});
@@ -720,3 +693,187 @@ describe('user-defined links utils', () => {
});
});
});
+
+describe('normalizeQueryResponseData', () => {
+ // Data examples from
+ // https://prometheus.io/docs/prometheus/latest/querying/api/#expression-queries
+
+ it('processes a string result', () => {
+ const mockScalar = {
+ resultType: 'string',
+ result: [1435781451.781, '1'],
+ };
+
+ expect(normalizeQueryResponseData(mockScalar)).toEqual([
+ {
+ metric: {},
+ value: ['2015-07-01T20:10:51.781Z', '1'],
+ values: [['2015-07-01T20:10:51.781Z', '1']],
+ },
+ ]);
+ });
+
+ it('processes a scalar result', () => {
+ const mockScalar = {
+ resultType: 'scalar',
+ result: [1435781451.781, '1'],
+ };
+
+ expect(normalizeQueryResponseData(mockScalar)).toEqual([
+ {
+ metric: {},
+ value: ['2015-07-01T20:10:51.781Z', 1],
+ values: [['2015-07-01T20:10:51.781Z', 1]],
+ },
+ ]);
+ });
+
+ it('processes a vector result', () => {
+ const mockVector = {
+ resultType: 'vector',
+ result: [
+ {
+ metric: {
+ __name__: 'up',
+ job: 'prometheus',
+ instance: 'localhost:9090',
+ },
+ value: [1435781451.781, '1'],
+ },
+ {
+ metric: {
+ __name__: 'up',
+ job: 'node',
+ instance: 'localhost:9100',
+ },
+ value: [1435781451.781, '0'],
+ },
+ ],
+ };
+
+ expect(normalizeQueryResponseData(mockVector)).toEqual([
+ {
+ metric: { __name__: 'up', job: 'prometheus', instance: 'localhost:9090' },
+ value: ['2015-07-01T20:10:51.781Z', 1],
+ values: [['2015-07-01T20:10:51.781Z', 1]],
+ },
+ {
+ metric: { __name__: 'up', job: 'node', instance: 'localhost:9100' },
+ value: ['2015-07-01T20:10:51.781Z', 0],
+ values: [['2015-07-01T20:10:51.781Z', 0]],
+ },
+ ]);
+ });
+
+ it('processes a matrix result', () => {
+ const mockMatrix = {
+ resultType: 'matrix',
+ result: [
+ {
+ metric: {
+ __name__: 'up',
+ job: 'prometheus',
+ instance: 'localhost:9090',
+ },
+ values: [[1435781430.781, '1'], [1435781445.781, '2'], [1435781460.781, '3']],
+ },
+ {
+ metric: {
+ __name__: 'up',
+ job: 'node',
+ instance: 'localhost:9091',
+ },
+ values: [[1435781430.781, '4'], [1435781445.781, '5'], [1435781460.781, '6']],
+ },
+ ],
+ };
+
+ expect(normalizeQueryResponseData(mockMatrix)).toEqual([
+ {
+ metric: { __name__: 'up', instance: 'localhost:9090', job: 'prometheus' },
+ value: ['2015-07-01T20:11:00.781Z', 3],
+ values: [
+ ['2015-07-01T20:10:30.781Z', 1],
+ ['2015-07-01T20:10:45.781Z', 2],
+ ['2015-07-01T20:11:00.781Z', 3],
+ ],
+ },
+ {
+ metric: { __name__: 'up', instance: 'localhost:9091', job: 'node' },
+ value: ['2015-07-01T20:11:00.781Z', 6],
+ values: [
+ ['2015-07-01T20:10:30.781Z', 4],
+ ['2015-07-01T20:10:45.781Z', 5],
+ ['2015-07-01T20:11:00.781Z', 6],
+ ],
+ },
+ ]);
+ });
+
+ it('processes a scalar result with a NaN result', () => {
+ // Queries may return "NaN" string values.
+ // e.g. when Prometheus cannot find a metric the query
+ // `scalar(does_not_exist)` will return a "NaN" value.
+
+ const mockScalar = {
+ resultType: 'scalar',
+ result: [1435781451.781, 'NaN'],
+ };
+
+ expect(normalizeQueryResponseData(mockScalar)).toEqual([
+ {
+ metric: {},
+ value: ['2015-07-01T20:10:51.781Z', NaN],
+ values: [['2015-07-01T20:10:51.781Z', NaN]],
+ },
+ ]);
+ });
+
+ it('processes a matrix result with a "NaN" value', () => {
+ // Queries may return "NaN" string values.
+ const mockMatrix = {
+ resultType: 'matrix',
+ result: [
+ {
+ metric: {
+ __name__: 'up',
+ job: 'prometheus',
+ instance: 'localhost:9090',
+ },
+ values: [[1435781430.781, '1'], [1435781460.781, 'NaN']],
+ },
+ ],
+ };
+
+ expect(normalizeQueryResponseData(mockMatrix)).toEqual([
+ {
+ metric: { __name__: 'up', instance: 'localhost:9090', job: 'prometheus' },
+ value: ['2015-07-01T20:11:00.781Z', NaN],
+ values: [['2015-07-01T20:10:30.781Z', 1], ['2015-07-01T20:11:00.781Z', NaN]],
+ },
+ ]);
+ });
+});
+
+describe('normalizeCustomDashboardPath', () => {
+ it.each`
+ input | expected
+ ${[undefined]} | ${''}
+ ${[null]} | ${''}
+ ${[]} | ${''}
+ ${['links.yml']} | ${'links.yml'}
+ ${['links.yml', '.gitlab/dashboards']} | ${'.gitlab/dashboards/links.yml'}
+ ${['config/prometheus/common_metrics.yml']} | ${'config/prometheus/common_metrics.yml'}
+ ${['config/prometheus/common_metrics.yml', '.gitlab/dashboards']} | ${'config/prometheus/common_metrics.yml'}
+ ${['dir1/links.yml', '.gitlab/dashboards']} | ${'.gitlab/dashboards/dir1/links.yml'}
+ ${['dir1/dir2/links.yml', '.gitlab/dashboards']} | ${'.gitlab/dashboards/dir1/dir2/links.yml'}
+ ${['.gitlab/dashboards/links.yml']} | ${'.gitlab/dashboards/links.yml'}
+ ${['.gitlab/dashboards/links.yml', '.gitlab/dashboards']} | ${'.gitlab/dashboards/links.yml'}
+ ${['.gitlab/dashboards/dir1/links.yml', '.gitlab/dashboards']} | ${'.gitlab/dashboards/dir1/links.yml'}
+ ${['.gitlab/dashboards/dir1/dir2/links.yml', '.gitlab/dashboards']} | ${'.gitlab/dashboards/dir1/dir2/links.yml'}
+ ${['config/prometheus/pod_metrics.yml', '.gitlab/dashboards']} | ${'config/prometheus/pod_metrics.yml'}
+ ${['config/prometheus/pod_metrics.yml']} | ${'config/prometheus/pod_metrics.yml'}
+ `(`normalizeCustomDashboardPath returns $expected for $input`, ({ input, expected }) => {
+ expect(normalizeCustomDashboardPath(...input)).toEqual(expected);
+ });
+});
diff --git a/spec/frontend/monitoring/store/variable_mapping_spec.js b/spec/frontend/monitoring/store/variable_mapping_spec.js
index 5164ed1b54b..de124b0313c 100644
--- a/spec/frontend/monitoring/store/variable_mapping_spec.js
+++ b/spec/frontend/monitoring/store/variable_mapping_spec.js
@@ -1,94 +1,209 @@
-import { parseTemplatingVariables, mergeURLVariables } from '~/monitoring/stores/variable_mapping';
+import {
+ parseTemplatingVariables,
+ mergeURLVariables,
+ optionsFromSeriesData,
+} from '~/monitoring/stores/variable_mapping';
+import {
+ templatingVariablesExamples,
+ storeTextVariables,
+ storeCustomVariables,
+ storeMetricLabelValuesVariables,
+} from '../mock_data';
import * as urlUtils from '~/lib/utils/url_utility';
-import { mockTemplatingData, mockTemplatingDataResponses } from '../mock_data';
-
-describe('parseTemplatingVariables', () => {
- it.each`
- case | input | expected
- ${'Returns empty object for no dashboard input'} | ${{}} | ${{}}
- ${'Returns empty object for empty dashboard input'} | ${{ dashboard: {} }} | ${{}}
- ${'Returns empty object for empty templating prop'} | ${mockTemplatingData.emptyTemplatingProp} | ${{}}
- ${'Returns empty object for empty variables prop'} | ${mockTemplatingData.emptyVariablesProp} | ${{}}
- ${'Returns parsed object for simple text variable'} | ${mockTemplatingData.simpleText} | ${mockTemplatingDataResponses.simpleText}
- ${'Returns parsed object for advanced text variable'} | ${mockTemplatingData.advText} | ${mockTemplatingDataResponses.advText}
- ${'Returns parsed object for simple custom variable'} | ${mockTemplatingData.simpleCustom} | ${mockTemplatingDataResponses.simpleCustom}
- ${'Returns parsed object for advanced custom variable without options'} | ${mockTemplatingData.advCustomWithoutOpts} | ${mockTemplatingDataResponses.advCustomWithoutOpts}
- ${'Returns parsed object for advanced custom variable for option without text'} | ${mockTemplatingData.advCustomWithoutOptText} | ${mockTemplatingDataResponses.advCustomWithoutOptText}
- ${'Returns parsed object for advanced custom variable without type'} | ${mockTemplatingData.advCustomWithoutType} | ${{}}
- ${'Returns parsed object for advanced custom variable without label'} | ${mockTemplatingData.advCustomWithoutLabel} | ${mockTemplatingDataResponses.advCustomWithoutLabel}
- ${'Returns parsed object for simple and advanced custom variables'} | ${mockTemplatingData.simpleAndAdv} | ${mockTemplatingDataResponses.simpleAndAdv}
- ${'Returns parsed object for all variable types'} | ${mockTemplatingData.allVariableTypes} | ${mockTemplatingDataResponses.allVariableTypes}
- `('$case', ({ input, expected }) => {
- expect(parseTemplatingVariables(input?.dashboard?.templating)).toEqual(expected);
- });
-});
-describe('mergeURLVariables', () => {
- beforeEach(() => {
- jest.spyOn(urlUtils, 'queryToObject');
- });
+describe('Monitoring variable mapping', () => {
+ describe('parseTemplatingVariables', () => {
+ it.each`
+ case | input
+ ${'For undefined templating object'} | ${undefined}
+ ${'For empty templating object'} | ${{}}
+ `('$case, returns an empty array', ({ input }) => {
+ expect(parseTemplatingVariables(input)).toEqual([]);
+ });
- afterEach(() => {
- urlUtils.queryToObject.mockRestore();
+ it.each`
+ case | input | output
+ ${'Returns parsed object for text variables'} | ${templatingVariablesExamples.text} | ${storeTextVariables}
+ ${'Returns parsed object for custom variables'} | ${templatingVariablesExamples.custom} | ${storeCustomVariables}
+ ${'Returns parsed object for metric label value variables'} | ${templatingVariablesExamples.metricLabelValues} | ${storeMetricLabelValuesVariables}
+ `('$case, returns an empty array', ({ input, output }) => {
+ expect(parseTemplatingVariables(input)).toEqual(output);
+ });
});
- it('returns empty object if variables are not defined in yml or URL', () => {
- urlUtils.queryToObject.mockReturnValueOnce({});
+ describe('mergeURLVariables', () => {
+ beforeEach(() => {
+ jest.spyOn(urlUtils, 'queryToObject');
+ });
- expect(mergeURLVariables({})).toEqual({});
- });
+ afterEach(() => {
+ urlUtils.queryToObject.mockRestore();
+ });
- it('returns empty object if variables are defined in URL but not in yml', () => {
- urlUtils.queryToObject.mockReturnValueOnce({
- 'var-env': 'one',
- 'var-instance': 'localhost',
+ it('returns empty object if variables are not defined in yml or URL', () => {
+ urlUtils.queryToObject.mockReturnValueOnce({});
+
+ expect(mergeURLVariables([])).toEqual([]);
});
- expect(mergeURLVariables({})).toEqual({});
- });
+ it('returns empty object if variables are defined in URL but not in yml', () => {
+ urlUtils.queryToObject.mockReturnValueOnce({
+ 'var-env': 'one',
+ 'var-instance': 'localhost',
+ });
- it('returns yml variables if variables defined in yml but not in the URL', () => {
- urlUtils.queryToObject.mockReturnValueOnce({});
+ expect(mergeURLVariables([])).toEqual([]);
+ });
- const params = {
- env: 'one',
- instance: 'localhost',
- };
+ it('returns yml variables if variables defined in yml but not in the URL', () => {
+ urlUtils.queryToObject.mockReturnValueOnce({});
+
+ const variables = [
+ {
+ name: 'env',
+ value: 'one',
+ },
+ {
+ name: 'instance',
+ value: 'localhost',
+ },
+ ];
+
+ expect(mergeURLVariables(variables)).toEqual(variables);
+ });
- expect(mergeURLVariables(params)).toEqual(params);
- });
+ it('returns yml variables if variables defined in URL do not match with yml variables', () => {
+ const urlParams = {
+ 'var-env': 'one',
+ 'var-instance': 'localhost',
+ };
+ const variables = [
+ {
+ name: 'env',
+ value: 'one',
+ },
+ {
+ name: 'service',
+ value: 'database',
+ },
+ ];
+ urlUtils.queryToObject.mockReturnValueOnce(urlParams);
+
+ expect(mergeURLVariables(variables)).toEqual(variables);
+ });
- it('returns yml variables if variables defined in URL do not match with yml variables', () => {
- const urlParams = {
- 'var-env': 'one',
- 'var-instance': 'localhost',
- };
- const ymlParams = {
- pod: { value: 'one' },
- service: { value: 'database' },
- };
- urlUtils.queryToObject.mockReturnValueOnce(urlParams);
-
- expect(mergeURLVariables(ymlParams)).toEqual(ymlParams);
+ it('returns merged yml and URL variables if there is some match', () => {
+ const urlParams = {
+ 'var-env': 'one',
+ 'var-instance': 'localhost:8080',
+ };
+ const variables = [
+ {
+ name: 'instance',
+ value: 'localhost',
+ },
+ {
+ name: 'service',
+ value: 'database',
+ },
+ ];
+
+ urlUtils.queryToObject.mockReturnValueOnce(urlParams);
+
+ expect(mergeURLVariables(variables)).toEqual([
+ {
+ name: 'instance',
+ value: 'localhost:8080',
+ },
+ {
+ name: 'service',
+ value: 'database',
+ },
+ ]);
+ });
});
- it('returns merged yml and URL variables if there is some match', () => {
- const urlParams = {
- 'var-env': 'one',
- 'var-instance': 'localhost:8080',
- };
- const ymlParams = {
- instance: { value: 'localhost' },
- service: { value: 'database' },
- };
+ describe('optionsFromSeriesData', () => {
+ it('fetches the label values from missing data', () => {
+ expect(optionsFromSeriesData({ label: 'job' })).toEqual([]);
+ });
- const merged = {
- instance: { value: 'localhost:8080' },
- service: { value: 'database' },
- };
+ it('fetches the label values from a simple series', () => {
+ const data = [
+ {
+ __name__: 'up',
+ job: 'job1',
+ },
+ {
+ __name__: 'up',
+ job: 'job2',
+ },
+ ];
+
+ expect(optionsFromSeriesData({ label: 'job', data })).toEqual([
+ { text: 'job1', value: 'job1' },
+ { text: 'job2', value: 'job2' },
+ ]);
+ });
- urlUtils.queryToObject.mockReturnValueOnce(urlParams);
+ it('fetches the label values from multiple series', () => {
+ const data = [
+ {
+ __name__: 'up',
+ job: 'job1',
+ instance: 'host1',
+ },
+ {
+ __name__: 'up',
+ job: 'job2',
+ instance: 'host1',
+ },
+ {
+ __name__: 'up',
+ job: 'job1',
+ instance: 'host2',
+ },
+ {
+ __name__: 'up',
+ job: 'job2',
+ instance: 'host2',
+ },
+ ];
+
+ expect(optionsFromSeriesData({ label: '__name__', data })).toEqual([
+ { text: 'up', value: 'up' },
+ ]);
+
+ expect(optionsFromSeriesData({ label: 'job', data })).toEqual([
+ { text: 'job1', value: 'job1' },
+ { text: 'job2', value: 'job2' },
+ ]);
+
+ expect(optionsFromSeriesData({ label: 'instance', data })).toEqual([
+ { text: 'host1', value: 'host1' },
+ { text: 'host2', value: 'host2' },
+ ]);
+ });
- expect(mergeURLVariables(ymlParams)).toEqual(merged);
+ it('fetches the label values from a series with missing values', () => {
+ const data = [
+ {
+ __name__: 'up',
+ job: 'job1',
+ },
+ {
+ __name__: 'up',
+ job: 'job2',
+ },
+ {
+ __name__: 'up',
+ },
+ ];
+
+ expect(optionsFromSeriesData({ label: 'job', data })).toEqual([
+ { text: 'job1', value: 'job1' },
+ { text: 'job2', value: 'job2' },
+ ]);
+ });
});
});
diff --git a/spec/frontend/monitoring/store_utils.js b/spec/frontend/monitoring/store_utils.js
index eb2578aa9db..6c8267e6a3c 100644
--- a/spec/frontend/monitoring/store_utils.js
+++ b/spec/frontend/monitoring/store_utils.js
@@ -8,7 +8,10 @@ export const setMetricResult = ({ store, result, group = 0, panel = 0, metric =
store.commit(`monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, {
metricId,
- result,
+ data: {
+ resultType: 'matrix',
+ result,
+ },
});
};
@@ -32,12 +35,6 @@ export const setupStoreWithDashboard = store => {
);
};
-export const setupStoreWithVariable = store => {
- store.commit(`monitoringDashboard/${types.SET_VARIABLES}`, {
- label1: 'pod',
- });
-};
-
export const setupStoreWithLinks = store => {
store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DASHBOARD_SUCCESS}`, {
...metricsDashboardPayload,
@@ -60,3 +57,24 @@ export const setupStoreWithData = store => {
setEnvironmentData(store);
};
+
+export const setupStoreWithDataForPanelCount = (store, panelCount) => {
+ const payloadPanelGroup = metricsDashboardPayload.panel_groups[0];
+
+ const panelGroupCustom = {
+ ...payloadPanelGroup,
+ panels: payloadPanelGroup.panels.slice(0, panelCount),
+ };
+
+ const metricsDashboardPayloadCustom = {
+ ...metricsDashboardPayload,
+ panel_groups: [panelGroupCustom],
+ };
+
+ store.commit(
+ `monitoringDashboard/${types.RECEIVE_METRICS_DASHBOARD_SUCCESS}`,
+ metricsDashboardPayloadCustom,
+ );
+
+ setMetricResult({ store, result: metricsResult, panel: 0 });
+};
diff --git a/spec/frontend/monitoring/utils_spec.js b/spec/frontend/monitoring/utils_spec.js
index 039cf275eea..35ca6ba9b52 100644
--- a/spec/frontend/monitoring/utils_spec.js
+++ b/spec/frontend/monitoring/utils_spec.js
@@ -1,12 +1,8 @@
import * as monitoringUtils from '~/monitoring/utils';
import * as urlUtils from '~/lib/utils/url_utility';
import { TEST_HOST } from 'jest/helpers/test_constants';
-import {
- mockProjectDir,
- singleStatMetricsResult,
- anomalyMockGraphData,
- barMockData,
-} from './mock_data';
+import { mockProjectDir, barMockData } from './mock_data';
+import { singleStatGraphData, anomalyGraphData } from './graph_data';
import { metricsDashboardViewModel, graphData } from './fixture_data';
const mockPath = `${TEST_HOST}${mockProjectDir}/-/environments/29/metrics`;
@@ -82,7 +78,7 @@ describe('monitoring/utils', () => {
it('validates data with the query format', () => {
const validGraphData = monitoringUtils.graphDataValidatorForValues(
true,
- singleStatMetricsResult,
+ singleStatGraphData(),
);
expect(validGraphData).toBe(true);
@@ -105,13 +101,13 @@ describe('monitoring/utils', () => {
let threeMetrics;
let fourMetrics;
beforeEach(() => {
- oneMetric = singleStatMetricsResult;
- threeMetrics = anomalyMockGraphData;
+ oneMetric = singleStatGraphData();
+ threeMetrics = anomalyGraphData();
const metrics = [...threeMetrics.metrics];
metrics.push(threeMetrics.metrics[0]);
fourMetrics = {
- ...anomalyMockGraphData,
+ ...anomalyGraphData(),
metrics,
};
});
@@ -429,14 +425,41 @@ describe('monitoring/utils', () => {
describe('convertVariablesForURL', () => {
it.each`
- input | expected
- ${undefined} | ${{}}
- ${null} | ${{}}
- ${{}} | ${{}}
- ${{ env: { value: 'prod' } }} | ${{ 'var-env': 'prod' }}
- ${{ 'var-env': { value: 'prod' } }} | ${{ 'var-var-env': 'prod' }}
+ input | expected
+ ${[]} | ${{}}
+ ${[{ name: 'env', value: 'prod' }]} | ${{ 'var-env': 'prod' }}
+ ${[{ name: 'env1', value: 'prod' }, { name: 'env2', value: null }]} | ${{ 'var-env1': 'prod' }}
+ ${[{ name: 'var-env', value: 'prod' }]} | ${{ 'var-var-env': 'prod' }}
`('convertVariablesForURL returns $expected with input $input', ({ input, expected }) => {
expect(monitoringUtils.convertVariablesForURL(input)).toEqual(expected);
});
});
+
+ describe('setCustomVariablesFromUrl', () => {
+ beforeEach(() => {
+ jest.spyOn(urlUtils, 'updateHistory');
+ });
+
+ afterEach(() => {
+ urlUtils.updateHistory.mockRestore();
+ });
+
+ it.each`
+ input | urlParams
+ ${[]} | ${''}
+ ${[{ name: 'env', value: 'prod' }]} | ${'?var-env=prod'}
+ ${[{ name: 'env1', value: 'prod' }, { name: 'env2', value: null }]} | ${'?var-env=prod&var-env1=prod'}
+ `(
+ 'setCustomVariablesFromUrl updates history with query "$urlParams" with input $input',
+ ({ input, urlParams }) => {
+ monitoringUtils.setCustomVariablesFromUrl(input);
+
+ expect(urlUtils.updateHistory).toHaveBeenCalledTimes(1);
+ expect(urlUtils.updateHistory).toHaveBeenCalledWith({
+ url: `${TEST_HOST}/${urlParams}`,
+ title: '',
+ });
+ },
+ );
+ });
});
diff --git a/spec/frontend/namespace_storage_limit_alert_spec.js b/spec/frontend/namespace_storage_limit_alert_spec.js
deleted file mode 100644
index ef398b12e1f..00000000000
--- a/spec/frontend/namespace_storage_limit_alert_spec.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import Cookies from 'js-cookie';
-import initNamespaceStorageLimitAlert from '~/namespace_storage_limit_alert';
-
-describe('broadcast message on dismiss', () => {
- const dismiss = () => {
- const button = document.querySelector('.js-namespace-storage-alert-dismiss');
- button.click();
- };
-
- beforeEach(() => {
- setFixtures(`
- <div class="js-namespace-storage-alert">
- <button class="js-namespace-storage-alert-dismiss" data-id="1" data-level="info"></button>
- </div>
- `);
-
- initNamespaceStorageLimitAlert();
- });
-
- it('removes alert', () => {
- expect(document.querySelector('.js-namespace-storage-alert')).toBeTruthy();
-
- dismiss();
-
- expect(document.querySelector('.js-namespace-storage-alert')).toBeNull();
- });
-
- it('calls Cookies.set', () => {
- jest.spyOn(Cookies, 'set');
- dismiss();
-
- expect(Cookies.set).toHaveBeenCalledWith('hide_storage_limit_alert_1_info', true, {
- expires: 365,
- });
- });
-});
diff --git a/spec/frontend/notes/components/multiline_comment_utils_spec.js b/spec/frontend/notes/components/multiline_comment_utils_spec.js
index 261bfb106e7..af4394cc648 100644
--- a/spec/frontend/notes/components/multiline_comment_utils_spec.js
+++ b/spec/frontend/notes/components/multiline_comment_utils_spec.js
@@ -2,35 +2,23 @@ import {
getSymbol,
getStartLineNumber,
getEndLineNumber,
+ getCommentedLines,
} from '~/notes/components/multiline_comment_utils';
describe('Multiline comment utilities', () => {
- describe('getStartLineNumber', () => {
+ describe('get start & end line numbers', () => {
+ const lineRanges = ['old', 'new', null].map(type => ({
+ start: { new_line: 1, old_line: 1, type },
+ end: { new_line: 2, old_line: 2, type },
+ }));
it.each`
- lineCode | type | result
- ${'abcdef_1_1'} | ${'old'} | ${'-1'}
- ${'abcdef_1_1'} | ${'new'} | ${'+1'}
- ${'abcdef_1_1'} | ${null} | ${'1'}
- ${'abcdef'} | ${'new'} | ${''}
- ${'abcdef'} | ${'old'} | ${''}
- ${'abcdef'} | ${null} | ${''}
- `('returns line number', ({ lineCode, type, result }) => {
- expect(getStartLineNumber({ start_line_code: lineCode, start_line_type: type })).toEqual(
- result,
- );
- });
- });
- describe('getEndLineNumber', () => {
- it.each`
- lineCode | type | result
- ${'abcdef_1_1'} | ${'old'} | ${'-1'}
- ${'abcdef_1_1'} | ${'new'} | ${'+1'}
- ${'abcdef_1_1'} | ${null} | ${'1'}
- ${'abcdef'} | ${'new'} | ${''}
- ${'abcdef'} | ${'old'} | ${''}
- ${'abcdef'} | ${null} | ${''}
- `('returns line number', ({ lineCode, type, result }) => {
- expect(getEndLineNumber({ end_line_code: lineCode, end_line_type: type })).toEqual(result);
+ lineRange | start | end
+ ${lineRanges[0]} | ${'-1'} | ${'-2'}
+ ${lineRanges[1]} | ${'+1'} | ${'+2'}
+ ${lineRanges[2]} | ${'1'} | ${'2'}
+ `('returns line numbers `$start` & `$end`', ({ lineRange, start, end }) => {
+ expect(getStartLineNumber(lineRange)).toEqual(start);
+ expect(getEndLineNumber(lineRange)).toEqual(end);
});
});
describe('getSymbol', () => {
@@ -46,4 +34,30 @@ describe('Multiline comment utilities', () => {
expect(getSymbol(type)).toEqual(result);
});
});
+ describe('getCommentedLines', () => {
+ const diffLines = [{ line_code: '1' }, { line_code: '2' }, { line_code: '3' }];
+ it('returns a default object when `selectedCommentPosition` is not provided', () => {
+ expect(getCommentedLines(undefined, diffLines)).toEqual({ startLine: 4, endLine: 4 });
+ });
+ it('returns an object with startLine and endLine equal to 0', () => {
+ const selectedCommentPosition = {
+ start: { line_code: '1' },
+ end: { line_code: '1' },
+ };
+ expect(getCommentedLines(selectedCommentPosition, diffLines)).toEqual({
+ startLine: 0,
+ endLine: 0,
+ });
+ });
+ it('returns an object with startLine and endLine equal to 0 and 1', () => {
+ const selectedCommentPosition = {
+ start: { line_code: '1' },
+ end: { line_code: '2' },
+ };
+ expect(getCommentedLines(selectedCommentPosition, diffLines)).toEqual({
+ startLine: 0,
+ endLine: 1,
+ });
+ });
+ });
});
diff --git a/spec/frontend/notes/components/note_actions_spec.js b/spec/frontend/notes/components/note_actions_spec.js
index 220ac22d8eb..5cc56cdefae 100644
--- a/spec/frontend/notes/components/note_actions_spec.js
+++ b/spec/frontend/notes/components/note_actions_spec.js
@@ -127,25 +127,63 @@ describe('noteActions', () => {
.catch(done.fail);
});
- it('should be possible to assign or unassign the comment author', () => {
- wrapper = shallowMountNoteActions(props, {
- targetType: () => 'issue',
- });
-
+ it('should not be possible to assign or unassign the comment author in a merge request', () => {
const assignUserButton = wrapper.find('[data-testid="assign-user"]');
- expect(assignUserButton.exists()).toBe(true);
+ expect(assignUserButton.exists()).toBe(false);
+ });
+ });
+ });
- assignUserButton.trigger('click');
- axiosMock.onPut(`${TEST_HOST}/api/v4/projects/group/project/issues/1`).reply(() => {
- expect(actions.updateAssignees).toHaveBeenCalled();
- });
+ describe('when a user has access to edit an issue', () => {
+ const testButtonClickTriggersAction = () => {
+ axiosMock.onPut(`${TEST_HOST}/api/v4/projects/group/project/issues/1`).reply(() => {
+ expect(actions.updateAssignees).toHaveBeenCalled();
});
- it('should not be possible to assign or unassign the comment author in a merge request', () => {
- const assignUserButton = wrapper.find('[data-testid="assign-user"]');
- expect(assignUserButton.exists()).toBe(false);
+ const assignUserButton = wrapper.find('[data-testid="assign-user"]');
+ expect(assignUserButton.exists()).toBe(true);
+ assignUserButton.trigger('click');
+ };
+
+ beforeEach(() => {
+ wrapper = shallowMountNoteActions(props, {
+ targetType: () => 'issue',
});
+ store.state.noteableData = {
+ current_user: {
+ can_update: true,
+ },
+ };
+ store.state.userData = userDataMock;
});
+
+ afterEach(() => {
+ wrapper.destroy();
+ axiosMock.restore();
+ });
+
+ it('should be possible to assign the comment author', testButtonClickTriggersAction);
+ it('should be possible to unassign the comment author', testButtonClickTriggersAction);
+ });
+
+ describe('when a user does not have access to edit an issue', () => {
+ const testButtonDoesNotRender = () => {
+ const assignUserButton = wrapper.find('[data-testid="assign-user"]');
+ expect(assignUserButton.exists()).toBe(false);
+ };
+
+ beforeEach(() => {
+ wrapper = shallowMountNoteActions(props, {
+ targetType: () => 'issue',
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('should not be possible to assign the comment author', testButtonDoesNotRender);
+ it('should not be possible to unassign the comment author', testButtonDoesNotRender);
});
describe('user is not logged in', () => {
diff --git a/spec/frontend/notes/components/note_form_spec.js b/spec/frontend/notes/components/note_form_spec.js
index 15802841c57..a5b5204509e 100644
--- a/spec/frontend/notes/components/note_form_spec.js
+++ b/spec/frontend/notes/components/note_form_spec.js
@@ -245,6 +245,24 @@ describe('issue_note_form component', () => {
expect(updateDraft).toHaveBeenCalledWith(dummyAutosaveKey, dummyContent);
});
+
+ it('does not save draft when ctrl+enter is pressed', () => {
+ const options = {
+ noteBody: '',
+ autosaveKey: dummyAutosaveKey,
+ };
+
+ props = { ...props, ...options };
+ wrapper = createComponentWrapper();
+
+ wrapper.setData({ isSubmittingWithKeydown: true });
+
+ const textarea = wrapper.find('textarea');
+ textarea.setValue('some content');
+ textarea.trigger('keydown.enter', { metaKey: true });
+
+ expect(updateDraft).not.toHaveBeenCalled();
+ });
});
describe('with batch comments', () => {
diff --git a/spec/frontend/notes/components/noteable_note_spec.js b/spec/frontend/notes/components/noteable_note_spec.js
index aa3eaa97e20..fc238feb974 100644
--- a/spec/frontend/notes/components/noteable_note_spec.js
+++ b/spec/frontend/notes/components/noteable_note_spec.js
@@ -34,7 +34,13 @@ describe('issue_note', () => {
note,
},
localVue,
- stubs: ['note-header', 'user-avatar-link', 'note-actions', 'note-body'],
+ stubs: [
+ 'note-header',
+ 'user-avatar-link',
+ 'note-actions',
+ 'note-body',
+ 'multiline-comment-form',
+ ],
});
});
@@ -46,12 +52,30 @@ describe('issue_note', () => {
it('should render if has multiline comment', () => {
const position = {
line_range: {
- start_line_code: 'abc_1_1',
- end_line_code: 'abc_2_2',
+ start: {
+ line_code: 'abc_1_1',
+ type: null,
+ old_line: '1',
+ new_line: '1',
+ },
+ end: {
+ line_code: 'abc_2_2',
+ type: null,
+ old_line: '2',
+ new_line: '2',
+ },
},
};
+ const line = {
+ line_code: 'abc_1_1',
+ type: null,
+ old_line: '1',
+ new_line: '1',
+ };
wrapper.setProps({
note: { ...note, position },
+ discussionRoot: true,
+ line,
});
return wrapper.vm.$nextTick().then(() => {
@@ -59,15 +83,51 @@ describe('issue_note', () => {
});
});
+ it('should render multiline comment if editing discussion root', () => {
+ wrapper.setProps({ discussionRoot: true });
+ wrapper.vm.isEditing = true;
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findMultilineComment().exists()).toBe(true);
+ });
+ });
+
+ it('should not render multiline comment form unless it is the discussion root', () => {
+ wrapper.setProps({ discussionRoot: false });
+ wrapper.vm.isEditing = true;
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findMultilineComment().exists()).toBe(false);
+ });
+ });
+
it('should not render if has single line comment', () => {
const position = {
line_range: {
- start_line_code: 'abc_1_1',
- end_line_code: 'abc_1_1',
+ start: {
+ line_code: 'abc_1_1',
+ type: null,
+ old_line: '1',
+ new_line: '1',
+ },
+ end: {
+ line_code: 'abc_1_1',
+ type: null,
+ old_line: '1',
+ new_line: '1',
+ },
},
};
+ const line = {
+ line_code: 'abc_1_1',
+ type: null,
+ old_line: '1',
+ new_line: '1',
+ };
wrapper.setProps({
note: { ...note, position },
+ discussionRoot: true,
+ line,
});
return wrapper.vm.$nextTick().then(() => {
@@ -139,6 +199,7 @@ describe('issue_note', () => {
store.hotUpdate({
actions: {
updateNote() {},
+ setSelectedCommentPositionHover() {},
},
});
const noteBodyComponent = wrapper.find(NoteBody);
diff --git a/spec/frontend/notes/mixins/discussion_navigation_spec.js b/spec/frontend/notes/mixins/discussion_navigation_spec.js
index ae30a36fc81..ecff95b6fe0 100644
--- a/spec/frontend/notes/mixins/discussion_navigation_spec.js
+++ b/spec/frontend/notes/mixins/discussion_navigation_spec.js
@@ -91,6 +91,8 @@ describe('Discussion navigation mixin', () => {
beforeEach(() => {
window.mrTabs.currentAction = 'show';
wrapper.vm[fn](...args);
+
+ return wrapper.vm.$nextTick();
});
it('sets current discussion', () => {
@@ -112,6 +114,8 @@ describe('Discussion navigation mixin', () => {
beforeEach(() => {
window.mrTabs.currentAction = 'diffs';
wrapper.vm[fn](...args);
+
+ return wrapper.vm.$nextTick();
});
it('sets current discussion', () => {
@@ -137,6 +141,8 @@ describe('Discussion navigation mixin', () => {
beforeEach(() => {
window.mrTabs.currentAction = 'other';
wrapper.vm[fn](...args);
+
+ return wrapper.vm.$nextTick();
});
it('sets current discussion', () => {
diff --git a/spec/frontend/notes/old_notes_spec.js b/spec/frontend/notes/old_notes_spec.js
index cb1d563ece7..dee4f93f0ce 100644
--- a/spec/frontend/notes/old_notes_spec.js
+++ b/spec/frontend/notes/old_notes_spec.js
@@ -624,7 +624,7 @@ describe.skip('Old Notes (~/notes.js)', () => {
});
});
- describe('postComment with Slash commands', () => {
+ describe('postComment with quick actions', () => {
const sampleComment = '/assign @root\n/award :100:';
const note = {
commands_changes: {
@@ -640,6 +640,7 @@ describe.skip('Old Notes (~/notes.js)', () => {
let $notesContainer;
beforeEach(() => {
+ loadFixtures('commit/show.html');
mockAxios.onPost(NOTES_POST_PATH).reply(200, note);
new Notes('', []);
@@ -659,14 +660,49 @@ describe.skip('Old Notes (~/notes.js)', () => {
$form.find('textarea.js-note-text').val(sampleComment);
});
- it('should remove slash command placeholder when comment with slash commands is done posting', done => {
+ it('should remove quick action placeholder when comment with quick actions is done posting', done => {
jest.spyOn(gl.awardsHandler, 'addAwardToEmojiBar');
$('.js-comment-button').click();
- expect($notesContainer.find('.system-note.being-posted').length).toEqual(1); // Placeholder shown
+ expect($notesContainer.find('.note.being-posted').length).toEqual(1); // Placeholder shown
setImmediate(() => {
- expect($notesContainer.find('.system-note.being-posted').length).toEqual(0); // Placeholder removed
+ expect($notesContainer.find('.note.being-posted').length).toEqual(0); // Placeholder removed
+ done();
+ });
+ });
+ });
+
+ describe('postComment with slash when quick actions are not supported', () => {
+ const sampleComment = '/assign @root';
+ let $form;
+ let $notesContainer;
+
+ beforeEach(() => {
+ const note = {
+ id: 1234,
+ html: `<li class="note note-row-1234 timeline-entry" id="note_1234">
+ <div class="note-text">${sampleComment}</div>
+ </li>`,
+ note: sampleComment,
+ valid: true,
+ };
+ mockAxios.onPost(NOTES_POST_PATH).reply(200, note);
+
+ new Notes('', []);
+ $form = $('form.js-main-target-form');
+ $notesContainer = $('ul.main-notes-list');
+ $form.find('textarea.js-note-text').val(sampleComment);
+ });
+
+ it('should show message placeholder including lines starting with slash', done => {
+ $('.js-comment-button').click();
+
+ expect($notesContainer.find('.note.being-posted').length).toEqual(1); // Placeholder shown
+ expect($notesContainer.find('.note-body p').text()).toEqual(sampleComment); // No quick action processing
+
+ setImmediate(() => {
+ expect($notesContainer.find('.note.being-posted').length).toEqual(0); // Placeholder removed
done();
});
});
diff --git a/spec/frontend/notes/stores/actions_spec.js b/spec/frontend/notes/stores/actions_spec.js
index ef87cb3bee7..909a4a797ae 100644
--- a/spec/frontend/notes/stores/actions_spec.js
+++ b/spec/frontend/notes/stores/actions_spec.js
@@ -18,6 +18,8 @@ import {
batchSuggestionsInfoMock,
} from '../mock_data';
import axios from '~/lib/utils/axios_utils';
+import * as utils from '~/notes/stores/utils';
+import updateIssueConfidentialMutation from '~/sidebar/components/confidential/queries/update_issue_confidential.mutation.graphql';
const TEST_ERROR_MESSAGE = 'Test error message';
jest.mock('~/flash');
@@ -272,9 +274,54 @@ describe('Actions Notes Store', () => {
});
});
+ describe('fetchData', () => {
+ describe('given there are no notes', () => {
+ const lastFetchedAt = '13579';
+
+ beforeEach(() => {
+ axiosMock
+ .onGet(notesDataMock.notesPath)
+ .replyOnce(200, { notes: [], last_fetched_at: lastFetchedAt });
+ });
+
+ it('should commit SET_LAST_FETCHED_AT', () =>
+ testAction(
+ actions.fetchData,
+ undefined,
+ { notesData: notesDataMock },
+ [{ type: 'SET_LAST_FETCHED_AT', payload: lastFetchedAt }],
+ [],
+ ));
+ });
+
+ describe('given there are notes', () => {
+ const lastFetchedAt = '12358';
+
+ beforeEach(() => {
+ axiosMock
+ .onGet(notesDataMock.notesPath)
+ .replyOnce(200, { notes: discussionMock.notes, last_fetched_at: lastFetchedAt });
+ });
+
+ it('should dispatch updateOrCreateNotes, startTaskList and commit SET_LAST_FETCHED_AT', () =>
+ testAction(
+ actions.fetchData,
+ undefined,
+ { notesData: notesDataMock },
+ [{ type: 'SET_LAST_FETCHED_AT', payload: lastFetchedAt }],
+ [
+ { type: 'updateOrCreateNotes', payload: discussionMock.notes },
+ { type: 'startTaskList' },
+ ],
+ ));
+ });
+ });
+
describe('poll', () => {
beforeEach(done => {
- jest.spyOn(axios, 'get');
+ axiosMock
+ .onGet(notesDataMock.notesPath)
+ .reply(200, { notes: [], last_fetched_at: '123456' }, { 'poll-interval': '1000' });
store
.dispatch('setNotesData', notesDataMock)
@@ -283,15 +330,10 @@ describe('Actions Notes Store', () => {
});
it('calls service with last fetched state', done => {
- axiosMock
- .onAny()
- .reply(200, { notes: [], last_fetched_at: '123456' }, { 'poll-interval': '1000' });
-
store
.dispatch('poll')
.then(() => new Promise(resolve => requestAnimationFrame(resolve)))
.then(() => {
- expect(axios.get).toHaveBeenCalled();
expect(store.state.lastFetchedAt).toBe('123456');
jest.advanceTimersByTime(1500);
@@ -303,8 +345,9 @@ describe('Actions Notes Store', () => {
}),
)
.then(() => {
- expect(axios.get.mock.calls.length).toBe(2);
- expect(axios.get.mock.calls[axios.get.mock.calls.length - 1][1].headers).toEqual({
+ const expectedGetRequests = 2;
+ expect(axiosMock.history.get.length).toBe(expectedGetRequests);
+ expect(axiosMock.history.get[expectedGetRequests - 1].headers).toMatchObject({
'X-Last-Fetched-At': '123456',
});
})
@@ -449,7 +492,7 @@ describe('Actions Notes Store', () => {
it('commits ADD_NEW_NOTE and dispatches updateMergeRequestWidget', done => {
testAction(
actions.createNewNote,
- { endpoint: `${gl.TEST_HOST}`, data: {} },
+ { endpoint: `${TEST_HOST}`, data: {} },
store.state,
[
{
@@ -485,7 +528,7 @@ describe('Actions Notes Store', () => {
it('does not commit ADD_NEW_NOTE or dispatch updateMergeRequestWidget', done => {
testAction(
actions.createNewNote,
- { endpoint: `${gl.TEST_HOST}`, data: {} },
+ { endpoint: `${TEST_HOST}`, data: {} },
store.state,
[],
[],
@@ -508,7 +551,7 @@ describe('Actions Notes Store', () => {
it('commits UPDATE_NOTE and dispatches updateMergeRequestWidget', done => {
testAction(
actions.toggleResolveNote,
- { endpoint: `${gl.TEST_HOST}`, isResolved: true, discussion: false },
+ { endpoint: `${TEST_HOST}`, isResolved: true, discussion: false },
store.state,
[
{
@@ -533,7 +576,7 @@ describe('Actions Notes Store', () => {
it('commits UPDATE_DISCUSSION and dispatches updateMergeRequestWidget', done => {
testAction(
actions.toggleResolveNote,
- { endpoint: `${gl.TEST_HOST}`, isResolved: true, discussion: true },
+ { endpoint: `${TEST_HOST}`, isResolved: true, discussion: true },
store.state,
[
{
@@ -1084,6 +1127,19 @@ describe('Actions Notes Store', () => {
});
});
+ describe('setSelectedCommentPosition', () => {
+ it('calls the correct mutation with the correct args', done => {
+ testAction(
+ actions.setSelectedCommentPosition,
+ {},
+ {},
+ [{ type: mutationTypes.SET_SELECTED_COMMENT_POSITION, payload: {} }],
+ [],
+ done,
+ );
+ });
+ });
+
describe('softDeleteDescriptionVersion', () => {
const endpoint = '/path/to/diff/1';
const payload = {
@@ -1142,6 +1198,14 @@ describe('Actions Notes Store', () => {
});
});
+ describe('setConfidentiality', () => {
+ it('calls the correct mutation with the correct args', () => {
+ testAction(actions.setConfidentiality, true, { noteableData: { confidential: false } }, [
+ { type: mutationTypes.SET_ISSUE_CONFIDENTIAL, payload: true },
+ ]);
+ });
+ });
+
describe('updateAssignees', () => {
it('update the assignees state', done => {
testAction(
@@ -1154,4 +1218,49 @@ describe('Actions Notes Store', () => {
);
});
});
+
+ describe('updateConfidentialityOnIssue', () => {
+ state = { noteableData: { confidential: false } };
+ const iid = '1';
+ const projectPath = 'full/path';
+ const getters = { getNoteableData: { iid } };
+ const actionArgs = { fullPath: projectPath, confidential: true };
+ const confidential = true;
+
+ beforeEach(() => {
+ jest
+ .spyOn(utils.gqClient, 'mutate')
+ .mockResolvedValue({ data: { issueSetConfidential: { issue: { confidential } } } });
+ });
+
+ it('calls gqClient mutation one time', () => {
+ actions.updateConfidentialityOnIssue({ commit: () => {}, state, getters }, actionArgs);
+
+ expect(utils.gqClient.mutate).toHaveBeenCalledTimes(1);
+ });
+
+ it('calls gqClient mutation with the correct values', () => {
+ actions.updateConfidentialityOnIssue({ commit: () => {}, state, getters }, actionArgs);
+
+ expect(utils.gqClient.mutate).toHaveBeenCalledWith({
+ mutation: updateIssueConfidentialMutation,
+ variables: { input: { iid, projectPath, confidential } },
+ });
+ });
+
+ describe('on success of mutation', () => {
+ it('calls commit with the correct values', () => {
+ const commitSpy = jest.fn();
+
+ return actions
+ .updateConfidentialityOnIssue({ commit: commitSpy, state, getters }, actionArgs)
+ .then(() => {
+ expect(commitSpy).toHaveBeenCalledWith(
+ mutationTypes.SET_ISSUE_CONFIDENTIAL,
+ confidential,
+ );
+ });
+ });
+ });
+ });
});
diff --git a/spec/frontend/notes/stores/mutation_spec.js b/spec/frontend/notes/stores/mutation_spec.js
index 75ef007b78d..0ad18ba9b6a 100644
--- a/spec/frontend/notes/stores/mutation_spec.js
+++ b/spec/frontend/notes/stores/mutation_spec.js
@@ -524,6 +524,26 @@ describe('Notes Store mutations', () => {
});
});
+ describe('SET_SELECTED_COMMENT_POSITION', () => {
+ it('should set comment position state', () => {
+ const state = {};
+
+ mutations.SET_SELECTED_COMMENT_POSITION(state, {});
+
+ expect(state.selectedCommentPosition).toEqual({});
+ });
+ });
+
+ describe('SET_SELECTED_COMMENT_POSITION_HOVER', () => {
+ it('should set comment hover position state', () => {
+ const state = {};
+
+ mutations.SET_SELECTED_COMMENT_POSITION_HOVER(state, {});
+
+ expect(state.selectedCommentPositionHover).toEqual({});
+ });
+ });
+
describe('DISABLE_COMMENTS', () => {
it('should set comments disabled state', () => {
const state = {};
@@ -806,6 +826,20 @@ describe('Notes Store mutations', () => {
});
});
+ describe('SET_ISSUE_CONFIDENTIAL', () => {
+ let state;
+
+ beforeEach(() => {
+ state = { noteableData: { confidential: false } };
+ });
+
+ it('sets sort order', () => {
+ mutations.SET_ISSUE_CONFIDENTIAL(state, true);
+
+ expect(state.noteableData.confidential).toBe(true);
+ });
+ });
+
describe('UPDATE_ASSIGNEES', () => {
it('should update assignees', () => {
const state = {
diff --git a/spec/frontend/pager_spec.js b/spec/frontend/pager_spec.js
index d7177a32cde..47056c2804c 100644
--- a/spec/frontend/pager_spec.js
+++ b/spec/frontend/pager_spec.js
@@ -3,6 +3,7 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import Pager from '~/pager';
import { removeParams } from '~/lib/utils/url_utility';
+import { TEST_HOST } from 'jest/helpers/test_constants';
jest.mock('~/lib/utils/url_utility', () => ({
removeParams: jest.fn().mockName('removeParams'),
@@ -32,7 +33,7 @@ describe('pager', () => {
});
it('should use data-href attribute from list element', () => {
- const href = `${gl.TEST_HOST}/some_list.json`;
+ const href = `${TEST_HOST}/some_list.json`;
setFixtures(`<div class="content_list" data-href="${href}"></div>`);
Pager.init();
@@ -40,7 +41,7 @@ describe('pager', () => {
});
it('should use current url if data-href attribute not provided', () => {
- const href = `${gl.TEST_HOST}/some_list`;
+ const href = `${TEST_HOST}/some_list`;
removeParams.mockReturnValue(href);
Pager.init();
@@ -56,7 +57,7 @@ describe('pager', () => {
it('keeps extra query parameters from url', () => {
window.history.replaceState({}, null, '?filter=test&offset=100');
- const href = `${gl.TEST_HOST}/some_list?filter=test`;
+ const href = `${TEST_HOST}/some_list?filter=test`;
removeParams.mockReturnValue(href);
Pager.init();
diff --git a/spec/frontend/pages/admin/jobs/index/components/stop_jobs_modal_spec.js b/spec/frontend/pages/admin/jobs/index/components/stop_jobs_modal_spec.js
index fe17c03389c..fb7a07b7bc7 100644
--- a/spec/frontend/pages/admin/jobs/index/components/stop_jobs_modal_spec.js
+++ b/spec/frontend/pages/admin/jobs/index/components/stop_jobs_modal_spec.js
@@ -3,6 +3,7 @@ import { redirectTo } from '~/lib/utils/url_utility';
import mountComponent from 'helpers/vue_mount_component_helper';
import axios from '~/lib/utils/axios_utils';
import stopJobsModal from '~/pages/admin/jobs/index/components/stop_jobs_modal.vue';
+import { TEST_HOST } from 'jest/helpers/test_constants';
jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
@@ -11,7 +12,7 @@ jest.mock('~/lib/utils/url_utility', () => ({
describe('stop_jobs_modal.vue', () => {
const props = {
- url: `${gl.TEST_HOST}/stop_jobs_modal.vue/stopAll`,
+ url: `${TEST_HOST}/stop_jobs_modal.vue/stopAll`,
};
let vm;
@@ -26,7 +27,7 @@ describe('stop_jobs_modal.vue', () => {
describe('onSubmit', () => {
it('stops jobs and redirects to overview page', done => {
- const responseURL = `${gl.TEST_HOST}/stop_jobs_modal.vue/jobs`;
+ const responseURL = `${TEST_HOST}/stop_jobs_modal.vue/jobs`;
jest.spyOn(axios, 'post').mockImplementation(url => {
expect(url).toBe(props.url);
return Promise.resolve({
diff --git a/spec/frontend/pages/labels/components/promote_label_modal_spec.js b/spec/frontend/pages/labels/components/promote_label_modal_spec.js
index 9d5beca70b5..d4aabcc02f4 100644
--- a/spec/frontend/pages/labels/components/promote_label_modal_spec.js
+++ b/spec/frontend/pages/labels/components/promote_label_modal_spec.js
@@ -3,6 +3,7 @@ import mountComponent from 'helpers/vue_mount_component_helper';
import promoteLabelModal from '~/pages/projects/labels/components/promote_label_modal.vue';
import eventHub from '~/pages/projects/labels/event_hub';
import axios from '~/lib/utils/axios_utils';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('Promote label modal', () => {
let vm;
@@ -11,7 +12,7 @@ describe('Promote label modal', () => {
labelTitle: 'Documentation',
labelColor: '#5cb85c',
labelTextColor: '#ffffff',
- url: `${gl.TEST_HOST}/dummy/promote/labels`,
+ url: `${TEST_HOST}/dummy/promote/labels`,
groupName: 'group',
};
@@ -51,7 +52,7 @@ describe('Promote label modal', () => {
});
it('redirects when a label is promoted', done => {
- const responseURL = `${gl.TEST_HOST}/dummy/endpoint`;
+ const responseURL = `${TEST_HOST}/dummy/endpoint`;
jest.spyOn(axios, 'post').mockImplementation(url => {
expect(url).toBe(labelMockData.url);
expect(eventHub.$emit).toHaveBeenCalledWith(
diff --git a/spec/frontend/pages/milestones/shared/components/delete_milestone_modal_spec.js b/spec/frontend/pages/milestones/shared/components/delete_milestone_modal_spec.js
index ff5dc6d8988..c376cf02594 100644
--- a/spec/frontend/pages/milestones/shared/components/delete_milestone_modal_spec.js
+++ b/spec/frontend/pages/milestones/shared/components/delete_milestone_modal_spec.js
@@ -4,6 +4,7 @@ import mountComponent from 'helpers/vue_mount_component_helper';
import axios from '~/lib/utils/axios_utils';
import deleteMilestoneModal from '~/pages/milestones/shared/components/delete_milestone_modal.vue';
import eventHub from '~/pages/milestones/shared/event_hub';
+import { TEST_HOST } from 'jest/helpers/test_constants';
jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
@@ -17,7 +18,7 @@ describe('delete_milestone_modal.vue', () => {
mergeRequestCount: 2,
milestoneId: 3,
milestoneTitle: 'my milestone title',
- milestoneUrl: `${gl.TEST_HOST}/delete_milestone_modal.vue/milestone`,
+ milestoneUrl: `${TEST_HOST}/delete_milestone_modal.vue/milestone`,
};
let vm;
@@ -32,7 +33,7 @@ describe('delete_milestone_modal.vue', () => {
});
it('deletes milestone and redirects to overview page', done => {
- const responseURL = `${gl.TEST_HOST}/delete_milestone_modal.vue/milestoneOverview`;
+ const responseURL = `${TEST_HOST}/delete_milestone_modal.vue/milestoneOverview`;
jest.spyOn(axios, 'delete').mockImplementation(url => {
expect(url).toBe(props.milestoneUrl);
expect(eventHub.$emit).toHaveBeenCalledWith(
diff --git a/spec/frontend/pages/milestones/shared/components/promote_milestone_modal_spec.js b/spec/frontend/pages/milestones/shared/components/promote_milestone_modal_spec.js
index ff896354d96..87d32a67d47 100644
--- a/spec/frontend/pages/milestones/shared/components/promote_milestone_modal_spec.js
+++ b/spec/frontend/pages/milestones/shared/components/promote_milestone_modal_spec.js
@@ -3,13 +3,14 @@ import mountComponent from 'helpers/vue_mount_component_helper';
import promoteMilestoneModal from '~/pages/milestones/shared/components/promote_milestone_modal.vue';
import eventHub from '~/pages/milestones/shared/event_hub';
import axios from '~/lib/utils/axios_utils';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('Promote milestone modal', () => {
let vm;
const Component = Vue.extend(promoteMilestoneModal);
const milestoneMockData = {
milestoneTitle: 'v1.0',
- url: `${gl.TEST_HOST}/dummy/promote/milestones`,
+ url: `${TEST_HOST}/dummy/promote/milestones`,
groupName: 'group',
};
@@ -46,7 +47,7 @@ describe('Promote milestone modal', () => {
});
it('redirects when a milestone is promoted', done => {
- const responseURL = `${gl.TEST_HOST}/dummy/endpoint`;
+ const responseURL = `${TEST_HOST}/dummy/endpoint`;
jest.spyOn(axios, 'post').mockImplementation(url => {
expect(url).toBe(milestoneMockData.url);
expect(eventHub.$emit).toHaveBeenCalledWith(
diff --git a/spec/frontend/pages/projects/forks/new/components/fork_groups_list_item_spec.js b/spec/frontend/pages/projects/forks/new/components/fork_groups_list_item_spec.js
new file mode 100644
index 00000000000..73e3c385d33
--- /dev/null
+++ b/spec/frontend/pages/projects/forks/new/components/fork_groups_list_item_spec.js
@@ -0,0 +1,78 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlBadge, GlButton, GlLink } from '@gitlab/ui';
+import ForkGroupsListItem from '~/pages/projects/forks/new/components/fork_groups_list_item.vue';
+
+describe('Fork groups list item component', () => {
+ let wrapper;
+
+ const DEFAULT_PROPS = {
+ hasReachedProjectLimit: false,
+ };
+
+ const DEFAULT_GROUP_DATA = {
+ id: 22,
+ name: 'Gitlab Org',
+ description: 'Ad et ipsam earum id aut nobis.',
+ visibility: 'public',
+ full_name: 'Gitlab Org',
+ created_at: '2020-06-22T03:32:05.664Z',
+ updated_at: '2020-06-22T03:32:05.664Z',
+ avatar_url: null,
+ fork_path: '/twitter/typeahead-js/-/forks?namespace_key=22',
+ forked_project_path: null,
+ permission: 'Owner',
+ relative_path: '/gitlab-org',
+ markdown_description:
+ '<p data-sourcepos="1:1-1:31" dir="auto">Ad et ipsam earum id aut nobis.</p>',
+ can_create_project: true,
+ marked_for_deletion: false,
+ };
+
+ const DUMMY_PATH = '/dummy/path';
+
+ const createWrapper = propsData => {
+ wrapper = shallowMount(ForkGroupsListItem, {
+ propsData: {
+ ...DEFAULT_PROPS,
+ ...propsData,
+ },
+ });
+ };
+
+ it('renders pending removal badge if applicable', () => {
+ createWrapper({ group: { ...DEFAULT_GROUP_DATA, marked_for_deletion: true } });
+
+ expect(wrapper.find(GlBadge).text()).toBe('pending removal');
+ });
+
+ it('renders go to fork button if has forked project', () => {
+ createWrapper({ group: { ...DEFAULT_GROUP_DATA, forked_project_path: DUMMY_PATH } });
+
+ expect(wrapper.find(GlButton).text()).toBe('Go to fork');
+ expect(wrapper.find(GlButton).attributes().href).toBe(DUMMY_PATH);
+ });
+
+ it('renders select button if has no forked project', () => {
+ createWrapper({
+ group: { ...DEFAULT_GROUP_DATA, forked_project_path: null, fork_path: DUMMY_PATH },
+ });
+
+ expect(wrapper.find(GlButton).text()).toBe('Select');
+ expect(wrapper.find('form').attributes().action).toBe(DUMMY_PATH);
+ });
+
+ it('renders link to current group', () => {
+ const DUMMY_FULL_NAME = 'dummy';
+ createWrapper({
+ group: { ...DEFAULT_GROUP_DATA, relative_path: DUMMY_PATH, full_name: DUMMY_FULL_NAME },
+ });
+
+ expect(
+ wrapper
+ .findAll(GlLink)
+ .filter(w => w.text() === DUMMY_FULL_NAME)
+ .at(0)
+ .attributes().href,
+ ).toBe(DUMMY_PATH);
+ });
+});
diff --git a/spec/frontend/pages/projects/forks/new/components/fork_groups_list_spec.js b/spec/frontend/pages/projects/forks/new/components/fork_groups_list_spec.js
new file mode 100644
index 00000000000..979dff78eba
--- /dev/null
+++ b/spec/frontend/pages/projects/forks/new/components/fork_groups_list_spec.js
@@ -0,0 +1,133 @@
+import AxiosMockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import { shallowMount } from '@vue/test-utils';
+import { GlLoadingIcon, GlSearchBoxByType } from '@gitlab/ui';
+import { nextTick } from 'vue';
+import createFlash from '~/flash';
+import ForkGroupsList from '~/pages/projects/forks/new/components/fork_groups_list.vue';
+import ForkGroupsListItem from '~/pages/projects/forks/new/components/fork_groups_list_item.vue';
+import waitForPromises from 'helpers/wait_for_promises';
+
+jest.mock('~/flash', () => jest.fn());
+
+describe('Fork groups list component', () => {
+ let wrapper;
+ let axiosMock;
+
+ const DEFAULT_PROPS = {
+ endpoint: '/dummy',
+ hasReachedProjectLimit: false,
+ };
+
+ const replyWith = (...args) => axiosMock.onGet(DEFAULT_PROPS.endpoint).reply(...args);
+
+ const createWrapper = propsData => {
+ wrapper = shallowMount(ForkGroupsList, {
+ propsData: {
+ ...DEFAULT_PROPS,
+ ...propsData,
+ },
+ stubs: {
+ GlTabs: {
+ template: '<div><slot></slot><slot name="tabs-end"></slot></div>',
+ },
+ },
+ });
+ };
+
+ beforeEach(() => {
+ axiosMock = new AxiosMockAdapter(axios);
+ });
+
+ afterEach(() => {
+ axiosMock.reset();
+
+ if (wrapper) {
+ wrapper.destroy();
+ wrapper = null;
+ }
+ });
+
+ it('fires load groups request on mount', async () => {
+ replyWith(200, { namespaces: [] });
+ createWrapper();
+
+ await waitForPromises();
+
+ expect(axiosMock.history.get[0].url).toBe(DEFAULT_PROPS.endpoint);
+ });
+
+ it('displays flash if loading groups fails', async () => {
+ replyWith(500);
+ createWrapper();
+
+ await waitForPromises();
+
+ expect(createFlash).toHaveBeenCalled();
+ });
+
+ it('displays loading indicator while loading groups', () => {
+ replyWith(() => new Promise(() => {}));
+ createWrapper();
+
+ expect(wrapper.contains(GlLoadingIcon)).toBe(true);
+ });
+
+ it('displays empty text if no groups are available', async () => {
+ const EMPTY_TEXT = 'No available groups to fork the project.';
+ replyWith(200, { namespaces: [] });
+ createWrapper();
+
+ await waitForPromises();
+
+ expect(wrapper.text()).toContain(EMPTY_TEXT);
+ });
+
+ it('displays filter field when groups are available', async () => {
+ replyWith(200, { namespaces: [{ name: 'dummy1' }, { name: 'dummy2' }] });
+ createWrapper();
+
+ await waitForPromises();
+
+ expect(wrapper.contains(GlSearchBoxByType)).toBe(true);
+ });
+
+ it('renders list items for each available group', async () => {
+ const namespaces = [{ name: 'dummy1' }, { name: 'dummy2' }, { name: 'otherdummy' }];
+ const hasReachedProjectLimit = true;
+
+ replyWith(200, { namespaces });
+ createWrapper({ hasReachedProjectLimit });
+
+ await waitForPromises();
+
+ expect(wrapper.findAll(ForkGroupsListItem)).toHaveLength(namespaces.length);
+
+ namespaces.forEach((namespace, idx) => {
+ expect(
+ wrapper
+ .findAll(ForkGroupsListItem)
+ .at(idx)
+ .props(),
+ ).toStrictEqual({ group: namespace, hasReachedProjectLimit });
+ });
+ });
+
+ it('filters repositories on the fly', async () => {
+ replyWith(200, {
+ namespaces: [{ name: 'dummy1' }, { name: 'dummy2' }, { name: 'otherdummy' }],
+ });
+ createWrapper();
+ await waitForPromises();
+ wrapper.find(GlSearchBoxByType).vm.$emit('input', 'other');
+ await nextTick();
+
+ expect(wrapper.findAll(ForkGroupsListItem)).toHaveLength(1);
+ expect(
+ wrapper
+ .findAll(ForkGroupsListItem)
+ .at(0)
+ .props().group.name,
+ ).toBe('otherdummy');
+ });
+});
diff --git a/spec/frontend/pages/projects/graphs/code_coverage_spec.js b/spec/frontend/pages/projects/graphs/code_coverage_spec.js
index 4990985b076..30c7ff78c6e 100644
--- a/spec/frontend/pages/projects/graphs/code_coverage_spec.js
+++ b/spec/frontend/pages/projects/graphs/code_coverage_spec.js
@@ -5,7 +5,7 @@ import { GlAreaChart } from '@gitlab/ui/dist/charts';
import axios from '~/lib/utils/axios_utils';
import CodeCoverage from '~/pages/projects/graphs/components/code_coverage.vue';
-import codeCoverageMockData from './mock_data';
+import { codeCoverageMockData, sortedDataByDates } from './mock_data';
import waitForPromises from 'helpers/wait_for_promises';
import httpStatusCodes from '~/lib/utils/http_status';
@@ -52,6 +52,10 @@ describe('Code Coverage', () => {
expect(findAreaChart().exists()).toBe(true);
});
+ it('sorts the dates in ascending order', () => {
+ expect(wrapper.vm.sortedData).toEqual(sortedDataByDates);
+ });
+
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
diff --git a/spec/frontend/pages/projects/graphs/mock_data.js b/spec/frontend/pages/projects/graphs/mock_data.js
index a15f861ee7a..28d97b9d3f0 100644
--- a/spec/frontend/pages/projects/graphs/mock_data.js
+++ b/spec/frontend/pages/projects/graphs/mock_data.js
@@ -1,60 +1,69 @@
-export default [
+export const codeCoverageMockData = [
{
group_name: 'rspec',
data: [
- { date: '2020-04-30', coverage: 40.0 },
- { date: '2020-05-01', coverage: 80.0 },
- { date: '2020-05-02', coverage: 99.0 },
- { date: '2020-05-10', coverage: 80.0 },
- { date: '2020-05-15', coverage: 70.0 },
{ date: '2020-05-20', coverage: 69.0 },
+ { date: '2020-05-15', coverage: 70.0 },
+ { date: '2020-05-10', coverage: 80.0 },
+ { date: '2020-05-02', coverage: 99.0 },
+ { date: '2020-05-01', coverage: 80.0 },
+ { date: '2020-04-30', coverage: 40.0 },
],
},
{
group_name: 'cypress',
data: [
- { date: '2022-07-30', coverage: 1.0 },
- { date: '2022-08-01', coverage: 2.4 },
- { date: '2022-08-02', coverage: 5.0 },
- { date: '2022-08-10', coverage: 15.0 },
- { date: '2022-08-15', coverage: 30.0 },
{ date: '2022-08-20', coverage: 40.0 },
+ { date: '2022-08-15', coverage: 30.0 },
+ { date: '2022-08-10', coverage: 15.0 },
+ { date: '2022-08-02', coverage: 5.0 },
+ { date: '2022-08-01', coverage: 2.4 },
+ { date: '2022-07-30', coverage: 1.0 },
],
},
{
group_name: 'karma',
data: [
- { date: '2020-05-01', coverage: 94.0 },
- { date: '2020-05-02', coverage: 94.0 },
- { date: '2020-05-03', coverage: 94.0 },
- { date: '2020-05-04', coverage: 94.0 },
- { date: '2020-05-05', coverage: 92.0 },
- { date: '2020-05-06', coverage: 91.0 },
- { date: '2020-05-07', coverage: 78.0 },
- { date: '2020-05-08', coverage: 94.0 },
- { date: '2020-05-09', coverage: 94.0 },
- { date: '2020-05-10', coverage: 94.0 },
- { date: '2020-05-11', coverage: 94.0 },
- { date: '2020-05-12', coverage: 94.0 },
- { date: '2020-05-13', coverage: 92.0 },
- { date: '2020-05-14', coverage: 91.0 },
- { date: '2020-05-15', coverage: 78.0 },
- { date: '2020-05-16', coverage: 94.0 },
- { date: '2020-05-17', coverage: 94.0 },
- { date: '2020-05-18', coverage: 93.0 },
- { date: '2020-05-19', coverage: 92.0 },
- { date: '2020-05-20', coverage: 91.0 },
- { date: '2020-05-21', coverage: 90.0 },
- { date: '2020-05-22', coverage: 91.0 },
- { date: '2020-05-23', coverage: 92.0 },
- { date: '2020-05-24', coverage: 75.0 },
- { date: '2020-05-25', coverage: 74.0 },
- { date: '2020-05-26', coverage: 74.0 },
- { date: '2020-05-27', coverage: 74.0 },
- { date: '2020-05-28', coverage: 80.0 },
- { date: '2020-05-29', coverage: 85.0 },
- { date: '2020-05-30', coverage: 92.0 },
{ date: '2020-05-31', coverage: 91.0 },
+ { date: '2020-05-30', coverage: 94.0 },
+ { date: '2020-05-29', coverage: 94.0 },
+ { date: '2020-05-28', coverage: 92.0 },
+ { date: '2020-05-27', coverage: 91.0 },
+ { date: '2020-05-26', coverage: 78.0 },
+ { date: '2020-05-25', coverage: 94.0 },
+ { date: '2020-05-24', coverage: 94.0 },
+ { date: '2020-05-23', coverage: 94.0 },
+ { date: '2020-05-22', coverage: 94.0 },
+ { date: '2020-05-21', coverage: 94.0 },
+ { date: '2020-05-20', coverage: 92.0 },
+ { date: '2020-05-19', coverage: 91.0 },
+ { date: '2020-05-18', coverage: 78.0 },
+ { date: '2020-05-17', coverage: 94.0 },
+ { date: '2020-05-16', coverage: 94.0 },
+ { date: '2020-05-15', coverage: 93.0 },
+ { date: '2020-05-14', coverage: 92.0 },
+ { date: '2020-05-13', coverage: 91.0 },
+ { date: '2020-05-12', coverage: 90.0 },
+ { date: '2020-05-11', coverage: 91.0 },
+ { date: '2020-05-10', coverage: 92.0 },
+ { date: '2020-05-09', coverage: 75.0 },
+ { date: '2020-05-08', coverage: 74.0 },
+ { date: '2020-05-07', coverage: 74.0 },
+ { date: '2020-05-06', coverage: 74.0 },
+ { date: '2020-05-05', coverage: 80.0 },
+ { date: '2020-05-04', coverage: 85.0 },
+ { date: '2020-05-03', coverage: 92.0 },
+ { date: '2020-05-02', coverage: 94.0 },
+ { date: '2020-05-01', coverage: 94.0 },
],
},
];
+
+export const sortedDataByDates = [
+ { date: '2020-04-30', coverage: 40.0 },
+ { date: '2020-05-01', coverage: 80.0 },
+ { date: '2020-05-02', coverage: 99.0 },
+ { date: '2020-05-10', coverage: 80.0 },
+ { date: '2020-05-15', coverage: 70.0 },
+ { date: '2020-05-20', coverage: 69.0 },
+];
diff --git a/spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js b/spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js
index 9cc1d6eeb5a..9a119377542 100644
--- a/spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js
+++ b/spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js
@@ -1,4 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import { trimText } from 'helpers/text_helper';
import IntervalPatternInput from '~/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue';
describe('Interval Pattern Input Component', () => {
@@ -14,15 +15,22 @@ describe('Interval Pattern Input Component', () => {
everyWeek: `0 ${mockHour} * * ${mockWeekDayIndex}`,
everyMonth: `0 ${mockHour} ${mockDay} * *`,
};
-
- const findEveryDayRadio = () => wrapper.find('#every-day');
- const findEveryWeekRadio = () => wrapper.find('#every-week');
- const findEveryMonthRadio = () => wrapper.find('#every-month');
- const findCustomRadio = () => wrapper.find('#custom');
+ const customKey = 'custom';
+ const everyDayKey = 'everyDay';
+ const cronIntervalNotInPreset = `0 12 * * *`;
+
+ const findEveryDayRadio = () => wrapper.find(`[data-testid=${everyDayKey}]`);
+ const findEveryWeekRadio = () => wrapper.find('[data-testid="everyWeek"]');
+ const findEveryMonthRadio = () => wrapper.find('[data-testid="everyMonth"]');
+ const findCustomRadio = () => wrapper.find(`[data-testid="${customKey}"]`);
const findCustomInput = () => wrapper.find('#schedule_cron');
- const selectEveryDayRadio = () => findEveryDayRadio().setChecked();
- const selectEveryWeekRadio = () => findEveryWeekRadio().setChecked();
- const selectEveryMonthRadio = () => findEveryMonthRadio().setChecked();
+ const findAllLabels = () => wrapper.findAll('label');
+ const findSelectedRadio = () =>
+ wrapper.findAll('input[type="radio"]').wrappers.find(x => x.element.checked);
+ const findSelectedRadioKey = () => findSelectedRadio()?.attributes('data-testid');
+ const selectEveryDayRadio = () => findEveryDayRadio().trigger('click');
+ const selectEveryWeekRadio = () => findEveryWeekRadio().trigger('click');
+ const selectEveryMonthRadio = () => findEveryMonthRadio().trigger('click');
const selectCustomRadio = () => findCustomRadio().trigger('click');
const createWrapper = (props = {}, data = {}) => {
@@ -30,7 +38,7 @@ describe('Interval Pattern Input Component', () => {
throw new Error('A wrapper already exists');
}
- wrapper = shallowMount(IntervalPatternInput, {
+ wrapper = mount(IntervalPatternInput, {
propsData: { ...props },
data() {
return {
@@ -63,8 +71,8 @@ describe('Interval Pattern Input Component', () => {
createWrapper();
});
- it('to a non empty string when no initial value is not passed', () => {
- expect(findCustomInput()).not.toBe('');
+ it('defaults to every day value when no `initialCronInterval` is passed', () => {
+ expect(findCustomInput().element.value).toBe(cronIntervalPresets.everyDay);
});
});
@@ -85,20 +93,20 @@ describe('Interval Pattern Input Component', () => {
createWrapper();
});
- it('when a default option is selected', () => {
+ it('when a default option is selected', async () => {
selectEveryDayRadio();
- return wrapper.vm.$nextTick().then(() => {
- expect(findCustomInput().attributes('disabled')).toBeUndefined();
- });
+ await wrapper.vm.$nextTick();
+
+ expect(findCustomInput().attributes('disabled')).toBeUndefined();
});
- it('when the custom option is selected', () => {
+ it('when the custom option is selected', async () => {
selectCustomRadio();
- return wrapper.vm.$nextTick().then(() => {
- expect(findCustomInput().attributes('disabled')).toBeUndefined();
- });
+ await wrapper.vm.$nextTick();
+
+ expect(findCustomInput().attributes('disabled')).toBeUndefined();
});
});
@@ -115,40 +123,83 @@ describe('Interval Pattern Input Component', () => {
});
});
+ describe('Time strings', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('renders each label for radio options properly', () => {
+ const labels = findAllLabels().wrappers.map(el => trimText(el.text()));
+
+ expect(labels).toEqual([
+ 'Every day (at 4:00am)',
+ 'Every week (Monday at 4:00am)',
+ 'Every month (Day 1 at 4:00am)',
+ 'Custom ( Cron syntax )',
+ ]);
+ });
+ });
+
describe('User Actions with radio buttons', () => {
- it.each`
- desc | initialCronInterval | act | expectedValue
- ${'when everyday is selected, update value'} | ${'1 2 3 4 5'} | ${selectEveryDayRadio} | ${cronIntervalPresets.everyDay}
- ${'when everyweek is selected, update value'} | ${'1 2 3 4 5'} | ${selectEveryWeekRadio} | ${cronIntervalPresets.everyWeek}
- ${'when everymonth is selected, update value'} | ${'1 2 3 4 5'} | ${selectEveryMonthRadio} | ${cronIntervalPresets.everyMonth}
- ${'when custom is selected, add space to value'} | ${cronIntervalPresets.everyMonth} | ${selectCustomRadio} | ${`${cronIntervalPresets.everyMonth} `}
- `('$desc', ({ initialCronInterval, act, expectedValue }) => {
- createWrapper({ initialCronInterval });
+ describe('Default option', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('when everyday is selected, update value', async () => {
+ selectEveryWeekRadio();
+ await wrapper.vm.$nextTick();
+ expect(findCustomInput().element.value).toBe(cronIntervalPresets.everyWeek);
+
+ selectEveryDayRadio();
+ await wrapper.vm.$nextTick();
+ expect(findCustomInput().element.value).toBe(cronIntervalPresets.everyDay);
+ });
+ });
+
+ describe('Other options', () => {
+ it.each`
+ desc | initialCronInterval | act | expectedValue
+ ${'when everyweek is selected, update value'} | ${'1 2 3 4 5'} | ${selectEveryWeekRadio} | ${cronIntervalPresets.everyWeek}
+ ${'when everymonth is selected, update value'} | ${'1 2 3 4 5'} | ${selectEveryMonthRadio} | ${cronIntervalPresets.everyMonth}
+ ${'when custom is selected, value remains the same'} | ${cronIntervalPresets.everyMonth} | ${selectCustomRadio} | ${cronIntervalPresets.everyMonth}
+ `('$desc', async ({ initialCronInterval, act, expectedValue }) => {
+ createWrapper({ initialCronInterval });
+
+ act();
- act();
+ await wrapper.vm.$nextTick();
- return wrapper.vm.$nextTick().then(() => {
expect(findCustomInput().element.value).toBe(expectedValue);
});
});
});
+
describe('User actions with input field for Cron syntax', () => {
beforeEach(() => {
createWrapper();
});
- it('when editing the cron input it selects the custom radio button', () => {
+ it('when editing the cron input it selects the custom radio button', async () => {
const newValue = '0 * * * *';
+ expect(findSelectedRadioKey()).toBe(everyDayKey);
+
findCustomInput().setValue(newValue);
- expect(wrapper.vm.cronInterval).toBe(newValue);
+ await wrapper.vm.$nextTick;
+
+ expect(findSelectedRadioKey()).toBe(customKey);
});
+ });
- it('when value of input is one of the defaults, it selects the corresponding radio button', () => {
- findCustomInput().setValue(cronIntervalPresets.everyWeek);
+ describe('Edit form field', () => {
+ beforeEach(() => {
+ createWrapper({ initialCronInterval: cronIntervalNotInPreset });
+ });
- expect(wrapper.vm.cronInterval).toBe(cronIntervalPresets.everyWeek);
+ it('loads with the custom option being selected', () => {
+ expect(findSelectedRadioKey()).toBe(customKey);
});
});
});
diff --git a/spec/frontend/persistent_user_callout_spec.js b/spec/frontend/persistent_user_callout_spec.js
index db324990e71..97985ba3a07 100644
--- a/spec/frontend/persistent_user_callout_spec.js
+++ b/spec/frontend/persistent_user_callout_spec.js
@@ -43,6 +43,23 @@ describe('PersistentUserCallout', () => {
return fixture;
}
+ function createFollowLinkFixture() {
+ const fixture = document.createElement('div');
+ fixture.innerHTML = `
+ <ul>
+ <li
+ class="container"
+ data-dismiss-endpoint="${dismissEndpoint}"
+ data-feature-id="${featureName}"
+ >
+ <a class="js-follow-link" href="/somewhere-pleasant">A Link</a>
+ </li>
+ </ul>
+ `;
+
+ return fixture;
+ }
+
describe('dismiss', () => {
let button;
let mockAxios;
@@ -144,6 +161,55 @@ describe('PersistentUserCallout', () => {
});
});
+ describe('follow links', () => {
+ let link;
+ let mockAxios;
+ let persistentUserCallout;
+
+ beforeEach(() => {
+ const fixture = createFollowLinkFixture();
+ const container = fixture.querySelector('.container');
+ link = fixture.querySelector('.js-follow-link');
+ mockAxios = new MockAdapter(axios);
+
+ persistentUserCallout = new PersistentUserCallout(container);
+ jest.spyOn(persistentUserCallout.container, 'remove').mockImplementation(() => {});
+
+ delete window.location;
+ window.location = { assign: jest.fn() };
+ });
+
+ afterEach(() => {
+ mockAxios.restore();
+ });
+
+ it('uses a link to trigger callout and defers following until callout is finished', () => {
+ const { href } = link;
+ mockAxios.onPost(dismissEndpoint).replyOnce(200);
+
+ link.click();
+
+ return waitForPromises().then(() => {
+ expect(window.location.assign).toBeCalledWith(href);
+ expect(mockAxios.history.post[0].data).toBe(JSON.stringify({ feature_name: featureName }));
+ expect(persistentUserCallout.container.remove).not.toHaveBeenCalled();
+ });
+ });
+
+ it('invokes Flash when the dismiss request fails', () => {
+ mockAxios.onPost(dismissEndpoint).replyOnce(500);
+
+ link.click();
+
+ return waitForPromises().then(() => {
+ expect(window.location.assign).not.toHaveBeenCalled();
+ expect(Flash).toHaveBeenCalledWith(
+ 'An error occurred while acknowledging the notification. Refresh the page and try again.',
+ );
+ });
+ });
+ });
+
describe('factory', () => {
it('returns an instance of PersistentUserCallout with the provided container property', () => {
const fixture = createFixture();
diff --git a/spec/frontend/pipelines/blank_state_spec.js b/spec/frontend/pipelines/blank_state_spec.js
index 033bd5ccb73..bb069fdc2c8 100644
--- a/spec/frontend/pipelines/blank_state_spec.js
+++ b/spec/frontend/pipelines/blank_state_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import component from '~/pipelines/components/blank_state.vue';
+import component from '~/pipelines/components/pipelines_list/blank_state.vue';
import mountComponent from '../helpers/vue_mount_component_helper';
describe('Pipelines Blank State', () => {
diff --git a/spec/frontend/pipelines/components/dag/__snapshots__/dag_graph_spec.js.snap b/spec/frontend/pipelines/components/dag/__snapshots__/dag_graph_spec.js.snap
index 629efc6d3fa..cb5f6ff5307 100644
--- a/spec/frontend/pipelines/components/dag/__snapshots__/dag_graph_spec.js.snap
+++ b/spec/frontend/pipelines/components/dag/__snapshots__/dag_graph_spec.js.snap
@@ -3,7 +3,7 @@
exports[`The DAG graph in the basic case renders the graph svg 1`] = `
"<svg viewBox=\\"0,0,1000,540\\" width=\\"1000\\" height=\\"540\\">
<g fill=\\"none\\" stroke-opacity=\\"0.8\\">
- <g id=\\"dag-link43\\" class=\\"dag-link gl-cursor-pointer\\">
+ <g id=\\"dag-link43\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad53\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"116\\" x2=\\"361.3333333333333\\">
<stop offset=\\"0%\\" stop-color=\\"#e17223\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#83ab4a\\"></stop>
@@ -20,7 +20,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M108,129L190,129L190,129L369.3333333333333,129\\" stroke=\\"url(#dag-grad53)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip63)\\"></path>
</g>
- <g id=\\"dag-link44\\" class=\\"dag-link gl-cursor-pointer\\">
+ <g id=\\"dag-link44\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad54\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"377.3333333333333\\" x2=\\"622.6666666666666\\">
<stop offset=\\"0%\\" stop-color=\\"#83ab4a\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#6f3500\\"></stop>
@@ -37,7 +37,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M369.3333333333333,129L509.3333333333333,129L509.3333333333333,129.0000000000002L630.6666666666666,129.0000000000002\\" stroke=\\"url(#dag-grad54)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip64)\\"></path>
</g>
- <g id=\\"dag-link45\\" class=\\"dag-link gl-cursor-pointer\\">
+ <g id=\\"dag-link45\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad55\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"116\\" x2=\\"622.6666666666666\\">
<stop offset=\\"0%\\" stop-color=\\"#5772ff\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#6f3500\\"></stop>
@@ -54,7 +54,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M108,212.00000000000003L306,212.00000000000003L306,187.0000000000002L630.6666666666666,187.0000000000002\\" stroke=\\"url(#dag-grad55)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip65)\\"></path>
</g>
- <g id=\\"dag-link46\\" class=\\"dag-link gl-cursor-pointer\\">
+ <g id=\\"dag-link46\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad56\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"116\\" x2=\\"361.3333333333333\\">
<stop offset=\\"0%\\" stop-color=\\"#b24800\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#006887\\"></stop>
@@ -71,7 +71,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M108,295L338.93333333333334,295L338.93333333333334,269.9999999999998L369.3333333333333,269.9999999999998\\" stroke=\\"url(#dag-grad56)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip66)\\"></path>
</g>
- <g id=\\"dag-link47\\" class=\\"dag-link gl-cursor-pointer\\">
+ <g id=\\"dag-link47\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad57\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"116\\" x2=\\"361.3333333333333\\">
<stop offset=\\"0%\\" stop-color=\\"#25d2d2\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#487900\\"></stop>
@@ -88,7 +88,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M108,378.00000000000006L144.66666666666669,378.00000000000006L144.66666666666669,352.99999999999994L369.3333333333333,352.99999999999994\\" stroke=\\"url(#dag-grad57)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip67)\\"></path>
</g>
- <g id=\\"dag-link48\\" class=\\"dag-link gl-cursor-pointer\\">
+ <g id=\\"dag-link48\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad58\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"377.3333333333333\\" x2=\\"622.6666666666666\\">
<stop offset=\\"0%\\" stop-color=\\"#006887\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#d84280\\"></stop>
@@ -105,7 +105,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M369.3333333333333,269.9999999999998L464,269.9999999999998L464,270.0000000000001L630.6666666666666,270.0000000000001\\" stroke=\\"url(#dag-grad58)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip68)\\"></path>
</g>
- <g id=\\"dag-link49\\" class=\\"dag-link gl-cursor-pointer\\">
+ <g id=\\"dag-link49\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad59\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"377.3333333333333\\" x2=\\"622.6666666666666\\">
<stop offset=\\"0%\\" stop-color=\\"#487900\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#d84280\\"></stop>
@@ -122,7 +122,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M369.3333333333333,352.99999999999994L522,352.99999999999994L522,328.0000000000001L630.6666666666666,328.0000000000001\\" stroke=\\"url(#dag-grad59)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip69)\\"></path>
</g>
- <g id=\\"dag-link50\\" class=\\"dag-link gl-cursor-pointer\\">
+ <g id=\\"dag-link50\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad60\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"377.3333333333333\\" x2=\\"622.6666666666666\\">
<stop offset=\\"0%\\" stop-color=\\"#487900\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#3547de\\"></stop>
@@ -139,7 +139,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M369.3333333333333,410.99999999999994L580,410.99999999999994L580,411L630.6666666666666,411\\" stroke=\\"url(#dag-grad60)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip70)\\"></path>
</g>
- <g id=\\"dag-link51\\" class=\\"dag-link gl-cursor-pointer\\">
+ <g id=\\"dag-link51\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad61\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"638.6666666666666\\" x2=\\"884\\">
<stop offset=\\"0%\\" stop-color=\\"#d84280\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#006887\\"></stop>
@@ -156,7 +156,7 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</clipPath>
<path d=\\"M630.6666666666666,270.0000000000001L861.6,270.0000000000001L861.6,270.1890725105691L892,270.1890725105691\\" stroke=\\"url(#dag-grad61)\\" style=\\"stroke-linejoin: round;\\" stroke-width=\\"56\\" clip-path=\\"url(#dag-clip71)\\"></path>
</g>
- <g id=\\"dag-link52\\" class=\\"dag-link gl-cursor-pointer\\">
+ <g id=\\"dag-link52\\" class=\\"dag-link gl-transition-property-stroke-opacity gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\">
<linearGradient id=\\"dag-grad62\\" gradientUnits=\\"userSpaceOnUse\\" x1=\\"638.6666666666666\\" x2=\\"884\\">
<stop offset=\\"0%\\" stop-color=\\"#3547de\\"></stop>
<stop offset=\\"100%\\" stop-color=\\"#275600\\"></stop>
@@ -175,18 +175,18 @@ exports[`The DAG graph in the basic case renders the graph svg 1`] = `
</g>
</g>
<g>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node73\\" stroke=\\"#e17223\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"104\\" y2=\\"154.00000000000003\\"></line>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node74\\" stroke=\\"#83ab4a\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"369\\" x2=\\"369\\" y1=\\"104\\" y2=\\"154\\"></line>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node75\\" stroke=\\"#5772ff\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"187.00000000000003\\" y2=\\"237.00000000000003\\"></line>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node76\\" stroke=\\"#b24800\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"270\\" y2=\\"320.00000000000006\\"></line>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node77\\" stroke=\\"#25d2d2\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"353.00000000000006\\" y2=\\"403.0000000000001\\"></line>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node78\\" stroke=\\"#6f3500\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"630\\" x2=\\"630\\" y1=\\"104.0000000000002\\" y2=\\"212.00000000000009\\"></line>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node79\\" stroke=\\"#006887\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"369\\" x2=\\"369\\" y1=\\"244.99999999999977\\" y2=\\"294.99999999999994\\"></line>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node80\\" stroke=\\"#487900\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"369\\" x2=\\"369\\" y1=\\"327.99999999999994\\" y2=\\"436\\"></line>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node81\\" stroke=\\"#d84280\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"630\\" x2=\\"630\\" y1=\\"245.00000000000009\\" y2=\\"353\\"></line>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node82\\" stroke=\\"#3547de\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"630\\" x2=\\"630\\" y1=\\"386\\" y2=\\"436\\"></line>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node83\\" stroke=\\"#006887\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"892\\" x2=\\"892\\" y1=\\"245.18907251056908\\" y2=\\"295.1890725105691\\"></line>
- <line class=\\"dag-node gl-cursor-pointer\\" id=\\"dag-node84\\" stroke=\\"#275600\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"892\\" x2=\\"892\\" y1=\\"386\\" y2=\\"436\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node73\\" stroke=\\"#e17223\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"104\\" y2=\\"154.00000000000003\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node74\\" stroke=\\"#83ab4a\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"369\\" x2=\\"369\\" y1=\\"104\\" y2=\\"154\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node75\\" stroke=\\"#5772ff\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"187.00000000000003\\" y2=\\"237.00000000000003\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node76\\" stroke=\\"#b24800\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"270\\" y2=\\"320.00000000000006\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node77\\" stroke=\\"#25d2d2\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"108\\" x2=\\"108\\" y1=\\"353.00000000000006\\" y2=\\"403.0000000000001\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node78\\" stroke=\\"#6f3500\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"630\\" x2=\\"630\\" y1=\\"104.0000000000002\\" y2=\\"212.00000000000009\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node79\\" stroke=\\"#006887\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"369\\" x2=\\"369\\" y1=\\"244.99999999999977\\" y2=\\"294.99999999999994\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node80\\" stroke=\\"#487900\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"369\\" x2=\\"369\\" y1=\\"327.99999999999994\\" y2=\\"436\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node81\\" stroke=\\"#d84280\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"630\\" x2=\\"630\\" y1=\\"245.00000000000009\\" y2=\\"353\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node82\\" stroke=\\"#3547de\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"630\\" x2=\\"630\\" y1=\\"386\\" y2=\\"436\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node83\\" stroke=\\"#006887\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"892\\" x2=\\"892\\" y1=\\"245.18907251056908\\" y2=\\"295.1890725105691\\"></line>
+ <line class=\\"dag-node gl-transition-property-stroke gl-cursor-pointer gl-transition-duration-slow gl-transition-timing-function-ease\\" id=\\"dag-node84\\" stroke=\\"#275600\\" stroke-width=\\"16\\" stroke-linecap=\\"round\\" x1=\\"892\\" x2=\\"892\\" y1=\\"386\\" y2=\\"436\\"></line>
</g>
<g class=\\"gl-font-sm\\">
<foreignObject requiredFeatures=\\"http://www.w3.org/TR/SVG11/feature#Extensibility\\" height=\\"58.00000000000003px\\" width=\\"84\\" x=\\"8\\" y=\\"100\\" class=\\"gl-overflow-visible\\">
diff --git a/spec/frontend/pipelines/components/dag/dag_annotations_spec.js b/spec/frontend/pipelines/components/dag/dag_annotations_spec.js
new file mode 100644
index 00000000000..5747c91bee8
--- /dev/null
+++ b/spec/frontend/pipelines/components/dag/dag_annotations_spec.js
@@ -0,0 +1,112 @@
+import { shallowMount, mount } from '@vue/test-utils';
+import { GlButton } from '@gitlab/ui';
+import DagAnnotations from '~/pipelines/components/dag/dag_annotations.vue';
+import { singleNote, multiNote } from './mock_data';
+
+describe('The DAG annotations', () => {
+ let wrapper;
+
+ const getColorBlock = () => wrapper.find('[data-testid="dag-color-block"]');
+ const getAllColorBlocks = () => wrapper.findAll('[data-testid="dag-color-block"]');
+ const getTextBlock = () => wrapper.find('[data-testid="dag-note-text"]');
+ const getAllTextBlocks = () => wrapper.findAll('[data-testid="dag-note-text"]');
+ const getToggleButton = () => wrapper.find(GlButton);
+
+ const createComponent = (propsData = {}, method = shallowMount) => {
+ if (wrapper?.destroy) {
+ wrapper.destroy();
+ }
+
+ wrapper = method(DagAnnotations, {
+ propsData,
+ data() {
+ return {
+ showList: true,
+ };
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('when there is one annotation', () => {
+ const currentNote = singleNote['dag-link103'];
+
+ beforeEach(() => {
+ createComponent({ annotations: singleNote });
+ });
+
+ it('displays the color block', () => {
+ expect(getColorBlock().exists()).toBe(true);
+ });
+
+ it('displays the text block', () => {
+ expect(getTextBlock().exists()).toBe(true);
+ expect(getTextBlock().text()).toBe(`${currentNote.source.name} → ${currentNote.target.name}`);
+ });
+
+ it('does not display the list toggle link', () => {
+ expect(getToggleButton().exists()).toBe(false);
+ });
+ });
+
+ describe('when there are multiple annoataions', () => {
+ beforeEach(() => {
+ createComponent({ annotations: multiNote });
+ });
+
+ it('displays a color block for each link', () => {
+ expect(getAllColorBlocks().length).toBe(Object.keys(multiNote).length);
+ });
+
+ it('displays a text block for each link', () => {
+ expect(getAllTextBlocks().length).toBe(Object.keys(multiNote).length);
+
+ Object.values(multiNote).forEach((item, idx) => {
+ expect(
+ getAllTextBlocks()
+ .at(idx)
+ .text(),
+ ).toBe(`${item.source.name} → ${item.target.name}`);
+ });
+ });
+
+ it('displays the list toggle link', () => {
+ expect(getToggleButton().exists()).toBe(true);
+ expect(getToggleButton().text()).toBe('Hide list');
+ });
+ });
+
+ describe('the list toggle', () => {
+ beforeEach(() => {
+ createComponent({ annotations: multiNote }, mount);
+ });
+
+ describe('clicking hide', () => {
+ it('hides listed items and changes text to show', () => {
+ expect(getAllTextBlocks().length).toBe(Object.keys(multiNote).length);
+ expect(getToggleButton().text()).toBe('Hide list');
+ getToggleButton().trigger('click');
+ return wrapper.vm.$nextTick().then(() => {
+ expect(getAllTextBlocks().length).toBe(0);
+ expect(getToggleButton().text()).toBe('Show list');
+ });
+ });
+ });
+
+ describe('clicking show', () => {
+ it('shows listed items and changes text to hide', () => {
+ getToggleButton().trigger('click');
+ getToggleButton().trigger('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(getAllTextBlocks().length).toBe(Object.keys(multiNote).length);
+ expect(getToggleButton().text()).toBe('Hide list');
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/pipelines/components/dag/dag_graph_spec.js b/spec/frontend/pipelines/components/dag/dag_graph_spec.js
index 017461dfb84..e312791b01f 100644
--- a/spec/frontend/pipelines/components/dag/dag_graph_spec.js
+++ b/spec/frontend/pipelines/components/dag/dag_graph_spec.js
@@ -1,4 +1,4 @@
-import { mount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
import DagGraph from '~/pipelines/components/dag/dag_graph.vue';
import { IS_HIGHLIGHTED, LINK_SELECTOR, NODE_SELECTOR } from '~/pipelines/components/dag/constants';
import { highlightIn, highlightOut } from '~/pipelines/components/dag/interactions';
@@ -19,7 +19,7 @@ describe('The DAG graph', () => {
wrapper.destroy();
}
- wrapper = mount(DagGraph, {
+ wrapper = shallowMount(DagGraph, {
attachToDocument: true,
propsData,
data() {
diff --git a/spec/frontend/pipelines/components/dag/dag_spec.js b/spec/frontend/pipelines/components/dag/dag_spec.js
index 666b4cfaa2f..7dea6d819b9 100644
--- a/spec/frontend/pipelines/components/dag/dag_spec.js
+++ b/spec/frontend/pipelines/components/dag/dag_spec.js
@@ -2,17 +2,28 @@ import { mount, shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import waitForPromises from 'helpers/wait_for_promises';
-import { GlAlert } from '@gitlab/ui';
+import { GlAlert, GlEmptyState } from '@gitlab/ui';
import Dag from '~/pipelines/components/dag/dag.vue';
import DagGraph from '~/pipelines/components/dag/dag_graph.vue';
+import DagAnnotations from '~/pipelines/components/dag/dag_annotations.vue';
import {
+ ADD_NOTE,
+ REMOVE_NOTE,
+ REPLACE_NOTES,
DEFAULT,
PARSE_FAILURE,
LOAD_FAILURE,
UNSUPPORTED_DATA,
} from '~/pipelines/components/dag//constants';
-import { mockBaseData, tooSmallGraph, unparseableGraph } from './mock_data';
+import {
+ mockBaseData,
+ tooSmallGraph,
+ unparseableGraph,
+ graphWithoutDependencies,
+ singleNote,
+ multiNote,
+} from './mock_data';
describe('Pipeline DAG graph wrapper', () => {
let wrapper;
@@ -20,7 +31,9 @@ describe('Pipeline DAG graph wrapper', () => {
const getAlert = () => wrapper.find(GlAlert);
const getAllAlerts = () => wrapper.findAll(GlAlert);
const getGraph = () => wrapper.find(DagGraph);
+ const getNotes = () => wrapper.find(DagAnnotations);
const getErrorText = type => wrapper.vm.$options.errorTexts[type];
+ const getEmptyState = () => wrapper.find(GlEmptyState);
const dataPath = '/root/test/pipelines/90/dag.json';
@@ -30,7 +43,11 @@ describe('Pipeline DAG graph wrapper', () => {
}
wrapper = method(Dag, {
- propsData,
+ propsData: {
+ emptySvgPath: '/my-svg',
+ dagDocPath: '/my-doc',
+ ...propsData,
+ },
data() {
return {
showFailureAlert: false,
@@ -59,79 +76,153 @@ describe('Pipeline DAG graph wrapper', () => {
expect(getAlert().text()).toBe(getErrorText(DEFAULT));
expect(getGraph().exists()).toBe(false);
});
+
+ it('does not render the empty state', () => {
+ expect(getEmptyState().exists()).toBe(false);
+ });
});
describe('when there is a dataUrl', () => {
describe('but the data fetch fails', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mock.onGet(dataPath).replyOnce(500);
createComponent({ graphUrl: dataPath });
+
+ await wrapper.vm.$nextTick();
+
+ return waitForPromises();
});
it('shows the LOAD_FAILURE alert and not the graph', () => {
- return wrapper.vm
- .$nextTick()
- .then(waitForPromises)
- .then(() => {
- expect(getAlert().exists()).toBe(true);
- expect(getAlert().text()).toBe(getErrorText(LOAD_FAILURE));
- expect(getGraph().exists()).toBe(false);
- });
+ expect(getAlert().exists()).toBe(true);
+ expect(getAlert().text()).toBe(getErrorText(LOAD_FAILURE));
+ expect(getGraph().exists()).toBe(false);
+ });
+
+ it('does not render the empty state', () => {
+ expect(getEmptyState().exists()).toBe(false);
});
});
describe('the data fetch succeeds but the parse fails', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mock.onGet(dataPath).replyOnce(200, unparseableGraph);
createComponent({ graphUrl: dataPath });
+
+ await wrapper.vm.$nextTick();
+
+ return waitForPromises();
});
it('shows the PARSE_FAILURE alert and not the graph', () => {
- return wrapper.vm
- .$nextTick()
- .then(waitForPromises)
- .then(() => {
- expect(getAlert().exists()).toBe(true);
- expect(getAlert().text()).toBe(getErrorText(PARSE_FAILURE));
- expect(getGraph().exists()).toBe(false);
- });
+ expect(getAlert().exists()).toBe(true);
+ expect(getAlert().text()).toBe(getErrorText(PARSE_FAILURE));
+ expect(getGraph().exists()).toBe(false);
+ });
+
+ it('does not render the empty state', () => {
+ expect(getEmptyState().exists()).toBe(false);
});
});
describe('and the data fetch and parse succeeds', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mock.onGet(dataPath).replyOnce(200, mockBaseData);
createComponent({ graphUrl: dataPath }, mount);
+
+ await wrapper.vm.$nextTick();
+
+ return waitForPromises();
});
- it('shows the graph and not the beta alert', () => {
- return wrapper.vm
- .$nextTick()
- .then(waitForPromises)
- .then(() => {
- expect(getAllAlerts().length).toBe(1);
- expect(getAlert().text()).toContain('This feature is currently in beta.');
- expect(getGraph().exists()).toBe(true);
- });
+ it('shows the graph and the beta alert', () => {
+ expect(getAllAlerts().length).toBe(1);
+ expect(getAlert().text()).toContain('This feature is currently in beta.');
+ expect(getGraph().exists()).toBe(true);
+ });
+
+ it('does not render the empty state', () => {
+ expect(getEmptyState().exists()).toBe(false);
});
});
describe('the data fetch and parse succeeds, but the resulting graph is too small', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mock.onGet(dataPath).replyOnce(200, tooSmallGraph);
createComponent({ graphUrl: dataPath });
+
+ await wrapper.vm.$nextTick();
+
+ return waitForPromises();
});
it('shows the UNSUPPORTED_DATA alert and not the graph', () => {
- return wrapper.vm
- .$nextTick()
- .then(waitForPromises)
- .then(() => {
- expect(getAlert().exists()).toBe(true);
- expect(getAlert().text()).toBe(getErrorText(UNSUPPORTED_DATA));
- expect(getGraph().exists()).toBe(false);
- });
+ expect(getAlert().exists()).toBe(true);
+ expect(getAlert().text()).toBe(getErrorText(UNSUPPORTED_DATA));
+ expect(getGraph().exists()).toBe(false);
+ });
+
+ it('does not show the empty dag graph state', () => {
+ expect(getEmptyState().exists()).toBe(false);
+ });
+ });
+
+ describe('the data fetch succeeds but the returned data is empty', () => {
+ beforeEach(async () => {
+ mock.onGet(dataPath).replyOnce(200, graphWithoutDependencies);
+ createComponent({ graphUrl: dataPath }, mount);
+
+ await wrapper.vm.$nextTick();
+
+ return waitForPromises();
+ });
+
+ it('does not render an error alert or the graph', () => {
+ expect(getAllAlerts().length).toBe(1);
+ expect(getAlert().text()).toContain('This feature is currently in beta.');
+ expect(getGraph().exists()).toBe(false);
});
+
+ it('shows the empty dag graph state', () => {
+ expect(getEmptyState().exists()).toBe(true);
+ });
+ });
+ });
+
+ describe('annotations', () => {
+ beforeEach(async () => {
+ mock.onGet(dataPath).replyOnce(200, mockBaseData);
+ createComponent({ graphUrl: dataPath }, mount);
+
+ await wrapper.vm.$nextTick();
+
+ return waitForPromises();
+ });
+
+ it('toggles on link mouseover and mouseout', async () => {
+ const currentNote = singleNote['dag-link103'];
+
+ expect(getNotes().exists()).toBe(false);
+
+ getGraph().vm.$emit('update-annotation', { type: ADD_NOTE, data: currentNote });
+ await wrapper.vm.$nextTick();
+ expect(getNotes().exists()).toBe(true);
+
+ getGraph().vm.$emit('update-annotation', { type: REMOVE_NOTE, data: currentNote });
+ await wrapper.vm.$nextTick();
+ expect(getNotes().exists()).toBe(false);
+ });
+
+ it('toggles on node and link click', async () => {
+ expect(getNotes().exists()).toBe(false);
+
+ getGraph().vm.$emit('update-annotation', { type: REPLACE_NOTES, data: multiNote });
+ await wrapper.vm.$nextTick();
+ expect(getNotes().exists()).toBe(true);
+
+ getGraph().vm.$emit('update-annotation', { type: REPLACE_NOTES, data: {} });
+ await wrapper.vm.$nextTick();
+ expect(getNotes().exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/pipelines/components/dag/mock_data.js b/spec/frontend/pipelines/components/dag/mock_data.js
index 5de8697170a..3b39b9cd21c 100644
--- a/spec/frontend/pipelines/components/dag/mock_data.js
+++ b/spec/frontend/pipelines/components/dag/mock_data.js
@@ -83,6 +83,46 @@ export const tooSmallGraph = {
],
};
+export const graphWithoutDependencies = {
+ stages: [
+ {
+ name: 'test',
+ groups: [
+ {
+ name: 'jest',
+ size: 2,
+ jobs: [{ name: 'jest 1/2' }, { name: 'jest 2/2' }],
+ },
+ {
+ name: 'rspec',
+ size: 1,
+ jobs: [{ name: 'rspec' }],
+ },
+ ],
+ },
+ {
+ name: 'fixtures',
+ groups: [
+ {
+ name: 'frontend fixtures',
+ size: 1,
+ jobs: [{ name: 'frontend fixtures' }],
+ },
+ ],
+ },
+ {
+ name: 'un-needed',
+ groups: [
+ {
+ name: 'un-needed',
+ size: 1,
+ jobs: [{ name: 'un-needed' }],
+ },
+ ],
+ },
+ ],
+};
+
export const unparseableGraph = [
{
name: 'test',
@@ -388,3 +428,43 @@ export const parsedData = {
},
],
};
+
+export const singleNote = {
+ 'dag-link103': {
+ uid: 'dag-link103',
+ source: {
+ name: 'canary_a',
+ color: '#b31756',
+ },
+ target: {
+ name: 'production_a',
+ color: '#b24800',
+ },
+ },
+};
+
+export const multiNote = {
+ ...singleNote,
+ 'dag-link104': {
+ uid: 'dag-link104',
+ source: {
+ name: 'build_a',
+ color: '#e17223',
+ },
+ target: {
+ name: 'test_c',
+ color: '#006887',
+ },
+ },
+ 'dag-link105': {
+ uid: 'dag-link105',
+ source: {
+ name: 'test_c',
+ color: '#006887',
+ },
+ target: {
+ name: 'post_test_c',
+ color: '#3547de',
+ },
+ },
+};
diff --git a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
index bdc807fcbfe..add7b56845e 100644
--- a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
@@ -2,7 +2,7 @@ import Api from '~/api';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
-import PipelinesFilteredSearch from '~/pipelines/components/pipelines_filtered_search.vue';
+import PipelinesFilteredSearch from '~/pipelines/components/pipelines_list/pipelines_filtered_search.vue';
import { users, mockSearch, branches, tags } from '../mock_data';
import { GlFilteredSearch } from '@gitlab/ui';
diff --git a/spec/frontend/pipelines/empty_state_spec.js b/spec/frontend/pipelines/empty_state_spec.js
index f12950b8fce..79356664834 100644
--- a/spec/frontend/pipelines/empty_state_spec.js
+++ b/spec/frontend/pipelines/empty_state_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import emptyStateComp from '~/pipelines/components/empty_state.vue';
+import emptyStateComp from '~/pipelines/components/pipelines_list/empty_state.vue';
import mountComponent from '../helpers/vue_mount_component_helper';
describe('Pipelines Empty State', () => {
diff --git a/spec/frontend/pipelines/graph/job_item_spec.js b/spec/frontend/pipelines/graph/job_item_spec.js
index da777466e3e..2c5e7a1f6e9 100644
--- a/spec/frontend/pipelines/graph/job_item_spec.js
+++ b/spec/frontend/pipelines/graph/job_item_spec.js
@@ -5,6 +5,8 @@ import JobItem from '~/pipelines/components/graph/job_item.vue';
describe('pipeline graph job item', () => {
let wrapper;
+ const findJobWithoutLink = () => wrapper.find('[data-testid="job-without-link"]');
+
const createWrapper = propsData => {
wrapper = mount(JobItem, {
propsData,
@@ -57,7 +59,7 @@ describe('pipeline graph job item', () => {
});
describe('name without link', () => {
- it('it should render status and name', () => {
+ beforeEach(() => {
createWrapper({
job: {
id: 4257,
@@ -71,13 +73,22 @@ describe('pipeline graph job item', () => {
has_details: false,
},
},
+ cssClassJobName: 'css-class-job-name',
+ jobHovered: 'test',
});
+ });
+ it('it should render status and name', () => {
expect(wrapper.find('.ci-status-icon-success').exists()).toBe(true);
expect(wrapper.find('a').exists()).toBe(false);
expect(trimText(wrapper.find('.ci-status-text').text())).toEqual(mockJob.name);
});
+
+ it('should apply hover class and provided class name', () => {
+ expect(findJobWithoutLink().classes()).toContain('gl-inset-border-1-blue-500');
+ expect(findJobWithoutLink().classes()).toContain('css-class-job-name');
+ });
});
describe('action icon', () => {
diff --git a/spec/frontend/pipelines/graph/linked_pipeline_spec.js b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
index cf78aa3ef71..133d5695afb 100644
--- a/spec/frontend/pipelines/graph/linked_pipeline_spec.js
+++ b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
@@ -11,7 +11,10 @@ const invalidTriggeredPipelineId = mockPipeline.project.id + 5;
describe('Linked pipeline', () => {
let wrapper;
+
const findButton = () => wrapper.find('button');
+ const findPipelineLabel = () => wrapper.find('[data-testid="downstream-pipeline-label"]');
+ const findLinkedPipeline = () => wrapper.find({ ref: 'linkedPipeline' });
const createWrapper = propsData => {
wrapper = mount(LinkedPipelineComponent, {
@@ -69,6 +72,8 @@ describe('Linked pipeline', () => {
it('should correctly compute the tooltip text', () => {
expect(wrapper.vm.tooltipText).toContain(mockPipeline.project.name);
expect(wrapper.vm.tooltipText).toContain(mockPipeline.details.status.label);
+ expect(wrapper.vm.tooltipText).toContain(mockPipeline.source_job.name);
+ expect(wrapper.vm.tooltipText).toContain(mockPipeline.id);
});
it('should render the tooltip text as the title attribute', () => {
@@ -83,9 +88,8 @@ describe('Linked pipeline', () => {
expect(wrapper.find('.js-linked-pipeline-loading').exists()).toBe(false);
});
- it('should not display child label when pipeline project id is not the same as triggered pipeline project id', () => {
- const labelContainer = wrapper.find('.parent-child-label-container');
- expect(labelContainer.exists()).toBe(false);
+ it('should display multi-project label when pipeline project id is not the same as triggered pipeline project id', () => {
+ expect(findPipelineLabel().text()).toBe('Multi-project');
});
});
@@ -103,17 +107,17 @@ describe('Linked pipeline', () => {
it('parent/child label container should exist', () => {
createWrapper(downstreamProps);
- expect(wrapper.find('.parent-child-label-container').exists()).toBe(true);
+ expect(findPipelineLabel().exists()).toBe(true);
});
it('should display child label when pipeline project id is the same as triggered pipeline project id', () => {
createWrapper(downstreamProps);
- expect(wrapper.find('.parent-child-label-container').text()).toContain('Child');
+ expect(findPipelineLabel().exists()).toBe(true);
});
it('should display parent label when pipeline project id is the same as triggered_by pipeline project id', () => {
createWrapper(upstreamProps);
- expect(wrapper.find('.parent-child-label-container').text()).toContain('Parent');
+ expect(findPipelineLabel().exists()).toBe(true);
});
});
@@ -133,7 +137,7 @@ describe('Linked pipeline', () => {
});
});
- describe('on click', () => {
+ describe('on click/hover', () => {
const props = {
pipeline: mockPipeline,
projectId: validTriggeredPipelineId,
@@ -160,5 +164,15 @@ describe('Linked pipeline', () => {
'js-linked-pipeline-34993051',
]);
});
+
+ it('should emit downstreamHovered with job name on mouseover', () => {
+ findLinkedPipeline().trigger('mouseover');
+ expect(wrapper.emitted().downstreamHovered).toStrictEqual([['trigger_job']]);
+ });
+
+ it('should emit downstreamHovered with empty string on mouseleave', () => {
+ findLinkedPipeline().trigger('mouseleave');
+ expect(wrapper.emitted().downstreamHovered).toStrictEqual([['']]);
+ });
});
});
diff --git a/spec/frontend/pipelines/graph/linked_pipelines_mock_data.js b/spec/frontend/pipelines/graph/linked_pipelines_mock_data.js
index 3e9c0814403..5756a666ff3 100644
--- a/spec/frontend/pipelines/graph/linked_pipelines_mock_data.js
+++ b/spec/frontend/pipelines/graph/linked_pipelines_mock_data.js
@@ -14,6 +14,9 @@ export default {
active: false,
coverage: null,
source: 'push',
+ source_job: {
+ name: 'trigger_job',
+ },
created_at: '2018-06-05T11:31:30.452Z',
updated_at: '2018-10-31T16:35:31.305Z',
path: '/gitlab-org/gitlab-runner/pipelines/23211253',
@@ -381,6 +384,9 @@ export default {
active: false,
coverage: null,
source: 'pipeline',
+ source_job: {
+ name: 'trigger_job',
+ },
path: '/gitlab-com/gitlab-docs/pipelines/34993051',
details: {
status: {
@@ -889,6 +895,9 @@ export default {
active: false,
coverage: null,
source: 'pipeline',
+ source_job: {
+ name: 'trigger_job',
+ },
path: '/gitlab-com/gitlab-docs/pipelines/34993051',
details: {
status: {
@@ -1402,6 +1411,9 @@ export default {
active: false,
coverage: null,
source: 'pipeline',
+ source_job: {
+ name: 'trigger_job',
+ },
path: '/gitlab-com/gitlab-docs/pipelines/34993051',
details: {
status: {
@@ -1912,6 +1924,9 @@ export default {
active: false,
coverage: null,
source: 'pipeline',
+ source_job: {
+ name: 'trigger_job',
+ },
path: '/gitlab-com/gitlab-docs/pipelines/34993051',
details: {
status: {
@@ -2412,6 +2427,9 @@ export default {
active: false,
coverage: null,
source: 'push',
+ source_job: {
+ name: 'trigger_job',
+ },
created_at: '2019-01-06T17:48:37.599Z',
updated_at: '2019-01-06T17:48:38.371Z',
path: '/h5bp/html5-boilerplate/pipelines/26',
@@ -3743,6 +3761,9 @@ export default {
active: false,
coverage: null,
source: 'push',
+ source_job: {
+ name: 'trigger_job',
+ },
path: '/gitlab-org/gitlab-test/pipelines/4',
details: {
status: {
diff --git a/spec/frontend/pipelines/nav_controls_spec.js b/spec/frontend/pipelines/nav_controls_spec.js
index 6d28da0ea2a..139d53881c8 100644
--- a/spec/frontend/pipelines/nav_controls_spec.js
+++ b/spec/frontend/pipelines/nav_controls_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import navControlsComp from '~/pipelines/components/nav_controls.vue';
+import navControlsComp from '~/pipelines/components/pipelines_list/nav_controls.vue';
import mountComponent from '../helpers/vue_mount_component_helper';
describe('Pipelines Nav Controls', () => {
diff --git a/spec/frontend/pipelines/pipeline_triggerer_spec.js b/spec/frontend/pipelines/pipeline_triggerer_spec.js
index a8eec274487..6fd9a143d82 100644
--- a/spec/frontend/pipelines/pipeline_triggerer_spec.js
+++ b/spec/frontend/pipelines/pipeline_triggerer_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
-import pipelineTriggerer from '~/pipelines/components/pipeline_triggerer.vue';
+import pipelineTriggerer from '~/pipelines/components/pipelines_list/pipeline_triggerer.vue';
describe('Pipelines Triggerer', () => {
let wrapper;
diff --git a/spec/frontend/pipelines/pipeline_url_spec.js b/spec/frontend/pipelines/pipeline_url_spec.js
index 70b94f2c8e1..0bcc3f96f7c 100644
--- a/spec/frontend/pipelines/pipeline_url_spec.js
+++ b/spec/frontend/pipelines/pipeline_url_spec.js
@@ -1,108 +1,140 @@
import $ from 'jquery';
import { trimText } from 'helpers/text_helper';
import { shallowMount } from '@vue/test-utils';
-import PipelineUrlComponent from '~/pipelines/components/pipeline_url.vue';
+import PipelineUrlComponent from '~/pipelines/components/pipelines_list/pipeline_url.vue';
$.fn.popover = () => {};
describe('Pipeline Url Component', () => {
let wrapper;
+ const findPipelineUrlLink = () => wrapper.find('[data-testid="pipeline-url-link"]');
+ const findScheduledTag = () => wrapper.find('[data-testid="pipeline-url-scheduled"]');
+ const findLatestTag = () => wrapper.find('[data-testid="pipeline-url-latest"]');
+ const findYamlTag = () => wrapper.find('[data-testid="pipeline-url-yaml"]');
+ const findFailureTag = () => wrapper.find('[data-testid="pipeline-url-failure"]');
+ const findAutoDevopsTag = () => wrapper.find('[data-testid="pipeline-url-autodevops"]');
+ const findStuckTag = () => wrapper.find('[data-testid="pipeline-url-stuck"]');
+ const findDetachedTag = () => wrapper.find('[data-testid="pipeline-url-detached"]');
+
+ const defaultProps = {
+ pipeline: {
+ id: 1,
+ path: 'foo',
+ flags: {},
+ },
+ autoDevopsHelpPath: 'foo',
+ pipelineScheduleUrl: 'foo',
+ };
+
const createComponent = props => {
wrapper = shallowMount(PipelineUrlComponent, {
- propsData: props,
+ propsData: { ...defaultProps, ...props },
});
};
afterEach(() => {
wrapper.destroy();
+ wrapper = null;
});
it('should render a table cell', () => {
+ createComponent();
+
+ expect(wrapper.attributes('class')).toContain('table-section');
+ });
+
+ it('should render a link the provided path and id', () => {
+ createComponent();
+
+ expect(findPipelineUrlLink().attributes('href')).toBe('foo');
+
+ expect(findPipelineUrlLink().text()).toBe('#1');
+ });
+
+ it('should render the stuck tag when flag is provided', () => {
createComponent({
pipeline: {
- id: 1,
- path: 'foo',
- flags: {},
+ flags: {
+ stuck: true,
+ },
},
- autoDevopsHelpPath: 'foo',
});
- expect(wrapper.attributes('class')).toContain('table-section');
+ expect(findStuckTag().text()).toContain('stuck');
});
- it('should render a link the provided path and id', () => {
+ it('should render latest tag when flag is provided', () => {
createComponent({
pipeline: {
- id: 1,
- path: 'foo',
- flags: {},
+ flags: {
+ latest: true,
+ },
},
- autoDevopsHelpPath: 'foo',
});
- expect(wrapper.find('.js-pipeline-url-link').attributes('href')).toBe('foo');
-
- expect(wrapper.find('.js-pipeline-url-link span').text()).toBe('#1');
+ expect(findLatestTag().text()).toContain('latest');
});
- it('should render latest, yaml invalid, merge request, and stuck flags when provided', () => {
+ it('should render a yaml badge when it is invalid', () => {
createComponent({
pipeline: {
- id: 1,
- path: 'foo',
flags: {
- latest: true,
yaml_errors: true,
- stuck: true,
- merge_request_pipeline: true,
- detached_merge_request_pipeline: true,
},
},
- autoDevopsHelpPath: 'foo',
});
- expect(wrapper.find('.js-pipeline-url-latest').text()).toContain('latest');
-
- expect(wrapper.find('.js-pipeline-url-yaml').text()).toContain('yaml invalid');
+ expect(findYamlTag().text()).toContain('yaml invalid');
+ });
- expect(wrapper.find('.js-pipeline-url-stuck').text()).toContain('stuck');
+ it('should render an autodevops badge when flag is provided', () => {
+ createComponent({
+ pipeline: {
+ flags: {
+ auto_devops: true,
+ },
+ },
+ });
- expect(wrapper.find('.js-pipeline-url-detached').text()).toContain('detached');
+ expect(trimText(findAutoDevopsTag().text())).toBe('Auto DevOps');
});
- it('should render a badge for autodevops', () => {
+ it('should render a detached badge when flag is provided', () => {
createComponent({
pipeline: {
- id: 1,
- path: 'foo',
flags: {
- latest: true,
- yaml_errors: true,
- stuck: true,
- auto_devops: true,
+ detached_merge_request_pipeline: true,
},
},
- autoDevopsHelpPath: 'foo',
});
- expect(trimText(wrapper.find('.js-pipeline-url-autodevops').text())).toEqual('Auto DevOps');
+ expect(findDetachedTag().text()).toContain('detached');
});
it('should render error badge when pipeline has a failure reason set', () => {
createComponent({
pipeline: {
- id: 1,
- path: 'foo',
flags: {
failure_reason: true,
},
failure_reason: 'some reason',
},
- autoDevopsHelpPath: 'foo',
});
- expect(wrapper.find('.js-pipeline-url-failure').text()).toContain('error');
- expect(wrapper.find('.js-pipeline-url-failure').attributes('title')).toContain('some reason');
+ expect(findFailureTag().text()).toContain('error');
+ expect(findFailureTag().attributes('title')).toContain('some reason');
+ });
+
+ it('should render scheduled badge when pipeline was triggered by a schedule', () => {
+ createComponent({
+ pipeline: {
+ flags: {},
+ source: 'schedule',
+ },
+ });
+
+ expect(findScheduledTag().exists()).toBe(true);
+ expect(findScheduledTag().text()).toContain('Scheduled');
});
});
diff --git a/spec/frontend/pipelines/pipelines_actions_spec.js b/spec/frontend/pipelines/pipelines_actions_spec.js
index 5e8d21660de..aef54d94974 100644
--- a/spec/frontend/pipelines/pipelines_actions_spec.js
+++ b/spec/frontend/pipelines/pipelines_actions_spec.js
@@ -2,7 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'spec/test_constants';
import axios from '~/lib/utils/axios_utils';
-import PipelinesActions from '~/pipelines/components/pipelines_actions.vue';
+import PipelinesActions from '~/pipelines/components/pipelines_list/pipelines_actions.vue';
import { GlDeprecatedButton } from '@gitlab/ui';
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
import waitForPromises from 'helpers/wait_for_promises';
diff --git a/spec/frontend/pipelines/pipelines_artifacts_spec.js b/spec/frontend/pipelines/pipelines_artifacts_spec.js
index a93cc8a62ab..512205c3fc3 100644
--- a/spec/frontend/pipelines/pipelines_artifacts_spec.js
+++ b/spec/frontend/pipelines/pipelines_artifacts_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import PipelineArtifacts from '~/pipelines/components/pipelines_artifacts.vue';
+import PipelineArtifacts from '~/pipelines/components/pipelines_list/pipelines_artifacts.vue';
import { GlLink } from '@gitlab/ui';
describe('Pipelines Artifacts dropdown', () => {
diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js
index 0eeaef01a2d..66446b9aa1d 100644
--- a/spec/frontend/pipelines/pipelines_spec.js
+++ b/spec/frontend/pipelines/pipelines_spec.js
@@ -3,7 +3,7 @@ import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import waitForPromises from 'helpers/wait_for_promises';
-import PipelinesComponent from '~/pipelines/components/pipelines.vue';
+import PipelinesComponent from '~/pipelines/components/pipelines_list/pipelines.vue';
import Store from '~/pipelines/stores/pipelines_store';
import { pipelineWithStages, stageReply, users, mockSearch, branches } from './mock_data';
import { RAW_TEXT_WARNING } from '~/pipelines/constants';
@@ -343,12 +343,8 @@ describe('Pipelines', () => {
});
it('should render navigation tabs', () => {
- expect(wrapper.find('.js-pipelines-tab-pending').text()).toContain('Pending');
-
expect(wrapper.find('.js-pipelines-tab-all').text()).toContain('All');
- expect(wrapper.find('.js-pipelines-tab-running').text()).toContain('Running');
-
expect(wrapper.find('.js-pipelines-tab-finished').text()).toContain('Finished');
expect(wrapper.find('.js-pipelines-tab-branches').text()).toContain('Branches');
@@ -452,8 +448,6 @@ describe('Pipelines', () => {
it('returns default tabs', () => {
expect(wrapper.vm.tabs).toEqual([
{ name: 'All', scope: 'all', count: undefined, isActive: true },
- { name: 'Pending', scope: 'pending', count: undefined, isActive: false },
- { name: 'Running', scope: 'running', count: undefined, isActive: false },
{ name: 'Finished', scope: 'finished', count: undefined, isActive: false },
{ name: 'Branches', scope: 'branches', isActive: false },
{ name: 'Tags', scope: 'tags', isActive: false },
@@ -462,11 +456,11 @@ describe('Pipelines', () => {
});
describe('emptyTabMessage', () => {
- it('returns message with scope', () => {
- wrapper.vm.scope = 'pending';
+ it('returns message with finished scope', () => {
+ wrapper.vm.scope = 'finished';
return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.emptyTabMessage).toEqual('There are currently no pending pipelines.');
+ expect(wrapper.vm.emptyTabMessage).toEqual('There are currently no finished pipelines.');
});
});
diff --git a/spec/frontend/pipelines/pipelines_table_row_spec.js b/spec/frontend/pipelines/pipelines_table_row_spec.js
index 3d564c8758c..9901f476f1b 100644
--- a/spec/frontend/pipelines/pipelines_table_row_spec.js
+++ b/spec/frontend/pipelines/pipelines_table_row_spec.js
@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
-import PipelinesTableRowComponent from '~/pipelines/components/pipelines_table_row.vue';
+import PipelinesTableRowComponent from '~/pipelines/components/pipelines_list/pipelines_table_row.vue';
import eventHub from '~/pipelines/event_hub';
describe('Pipelines Table Row', () => {
diff --git a/spec/frontend/pipelines/pipelines_table_spec.js b/spec/frontend/pipelines/pipelines_table_spec.js
index b0ab250dd16..c7d104bbde8 100644
--- a/spec/frontend/pipelines/pipelines_table_spec.js
+++ b/spec/frontend/pipelines/pipelines_table_spec.js
@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
-import PipelinesTable from '~/pipelines/components/pipelines_table.vue';
+import PipelinesTable from '~/pipelines/components/pipelines_list/pipelines_table.vue';
describe('Pipelines Table', () => {
let pipeline;
diff --git a/spec/frontend/pipelines/stage_spec.js b/spec/frontend/pipelines/stage_spec.js
index 6aa041bcb7f..547f8994ca5 100644
--- a/spec/frontend/pipelines/stage_spec.js
+++ b/spec/frontend/pipelines/stage_spec.js
@@ -1,7 +1,7 @@
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
-import StageComponent from '~/pipelines/components/stage.vue';
+import StageComponent from '~/pipelines/components/pipelines_list/stage.vue';
import eventHub from '~/pipelines/event_hub';
import { stageReply } from './mock_data';
import waitForPromises from 'helpers/wait_for_promises';
diff --git a/spec/frontend/pipelines/test_reports/stores/actions_spec.js b/spec/frontend/pipelines/test_reports/stores/actions_spec.js
index 56148361e0a..d4647c55a53 100644
--- a/spec/frontend/pipelines/test_reports/stores/actions_spec.js
+++ b/spec/frontend/pipelines/test_reports/stores/actions_spec.js
@@ -14,31 +14,100 @@ describe('Actions TestReports Store', () => {
let state;
const testReports = getJSONFixture('pipelines/test_report.json');
+ const summary = { total_count: 1 };
- const endpoint = `${TEST_HOST}/test_reports.json`;
+ const fullReportEndpoint = `${TEST_HOST}/test_reports.json`;
+ const summaryEndpoint = `${TEST_HOST}/test_reports/summary.json`;
const defaultState = {
- endpoint,
+ fullReportEndpoint,
+ summaryEndpoint,
testReports: {},
- selectedSuite: {},
+ selectedSuite: null,
+ useBuildSummaryReport: false,
};
beforeEach(() => {
mock = new MockAdapter(axios);
- state = defaultState;
+ state = { ...defaultState };
});
afterEach(() => {
mock.restore();
});
- describe('fetch reports', () => {
+ describe('fetch report summary', () => {
beforeEach(() => {
- mock.onGet(`${TEST_HOST}/test_reports.json`).replyOnce(200, testReports, {});
+ mock.onGet(summaryEndpoint).replyOnce(200, summary, {});
+ });
+
+ describe('when useBuildSummaryReport in state is true', () => {
+ it('sets testReports and shows tests', done => {
+ testAction(
+ actions.fetchSummary,
+ null,
+ { ...state, useBuildSummaryReport: true },
+ [{ type: types.SET_SUMMARY, payload: summary }],
+ [{ type: 'toggleLoading' }, { type: 'toggleLoading' }],
+ done,
+ );
+ });
+
+ it('should create flash on API error', done => {
+ testAction(
+ actions.fetchSummary,
+ null,
+ {
+ summaryEndpoint: null,
+ useBuildSummaryReport: true,
+ },
+ [],
+ [{ type: 'toggleLoading' }, { type: 'toggleLoading' }],
+ () => {
+ expect(createFlash).toHaveBeenCalled();
+ done();
+ },
+ );
+ });
+ });
+
+ describe('when useBuildSummaryReport in state is false', () => {
+ it('sets testReports and shows tests', done => {
+ testAction(
+ actions.fetchSummary,
+ null,
+ state,
+ [{ type: types.SET_SUMMARY, payload: summary }],
+ [],
+ done,
+ );
+ });
+
+ it('should create flash on API error', done => {
+ testAction(
+ actions.fetchSummary,
+ null,
+ {
+ summaryEndpoint: null,
+ },
+ [],
+ [],
+ () => {
+ expect(createFlash).toHaveBeenCalled();
+ done();
+ },
+ );
+ });
+ });
+ });
+
+ describe('fetch full report', () => {
+ beforeEach(() => {
+ mock.onGet(fullReportEndpoint).replyOnce(200, testReports, {});
});
it('sets testReports and shows tests', done => {
testAction(
- actions.fetchReports,
+ actions.fetchFullReport,
null,
state,
[{ type: types.SET_REPORTS, payload: testReports }],
@@ -49,10 +118,10 @@ describe('Actions TestReports Store', () => {
it('should create flash on API error', done => {
testAction(
- actions.fetchReports,
+ actions.fetchFullReport,
null,
{
- endpoint: null,
+ fullReportEndpoint: null,
},
[],
[{ type: 'toggleLoading' }, { type: 'toggleLoading' }],
@@ -64,28 +133,28 @@ describe('Actions TestReports Store', () => {
});
});
- describe('set selected suite', () => {
- const selectedSuite = testReports.test_suites[0];
+ describe('set selected suite index', () => {
+ it('sets selectedSuiteIndex', done => {
+ const selectedSuiteIndex = 0;
- it('sets selectedSuite', done => {
testAction(
- actions.setSelectedSuite,
- selectedSuite,
- state,
- [{ type: types.SET_SELECTED_SUITE, payload: selectedSuite }],
+ actions.setSelectedSuiteIndex,
+ selectedSuiteIndex,
+ { ...state, hasFullReport: true },
+ [{ type: types.SET_SELECTED_SUITE_INDEX, payload: selectedSuiteIndex }],
[],
done,
);
});
});
- describe('remove selected suite', () => {
- it('sets selectedSuite to {}', done => {
+ describe('remove selected suite index', () => {
+ it('sets selectedSuiteIndex to null', done => {
testAction(
- actions.removeSelectedSuite,
+ actions.removeSelectedSuiteIndex,
{},
state,
- [{ type: types.SET_SELECTED_SUITE, payload: {} }],
+ [{ type: types.SET_SELECTED_SUITE_INDEX, payload: null }],
[],
done,
);
diff --git a/spec/frontend/pipelines/test_reports/stores/getters_spec.js b/spec/frontend/pipelines/test_reports/stores/getters_spec.js
index 011a7e68908..ca9ebb54138 100644
--- a/spec/frontend/pipelines/test_reports/stores/getters_spec.js
+++ b/spec/frontend/pipelines/test_reports/stores/getters_spec.js
@@ -9,12 +9,12 @@ describe('Getters TestReports Store', () => {
const defaultState = {
testReports,
- selectedSuite: testReports.test_suites[0],
+ selectedSuiteIndex: 0,
};
const emptyState = {
testReports: {},
- selectedSuite: {},
+ selectedSuite: null,
};
beforeEach(() => {
@@ -47,6 +47,17 @@ describe('Getters TestReports Store', () => {
});
});
+ describe('getSelectedSuite', () => {
+ it('should return the selected suite', () => {
+ setupState();
+
+ const selectedSuite = getters.getSelectedSuite(state);
+ const expected = testReports.test_suites[state.selectedSuiteIndex];
+
+ expect(selectedSuite).toEqual(expected);
+ });
+ });
+
describe('getSuiteTests', () => {
it('should return the test cases inside the suite', () => {
setupState();
diff --git a/spec/frontend/pipelines/test_reports/stores/mutations_spec.js b/spec/frontend/pipelines/test_reports/stores/mutations_spec.js
index a0eb93c4e6b..f4cc5c4bc5d 100644
--- a/spec/frontend/pipelines/test_reports/stores/mutations_spec.js
+++ b/spec/frontend/pipelines/test_reports/stores/mutations_spec.js
@@ -10,21 +10,13 @@ describe('Mutations TestReports Store', () => {
const defaultState = {
endpoint: '',
testReports: {},
- selectedSuite: {},
+ selectedSuite: null,
isLoading: false,
+ hasFullReport: false,
};
beforeEach(() => {
- mockState = defaultState;
- });
-
- describe('set endpoint', () => {
- it('should set endpoint', () => {
- const expectedState = { ...mockState, endpoint: 'foo' };
- mutations[types.SET_ENDPOINT](mockState, 'foo');
-
- expect(mockState.endpoint).toEqual(expectedState.endpoint);
- });
+ mockState = { ...defaultState };
});
describe('set reports', () => {
@@ -33,15 +25,25 @@ describe('Mutations TestReports Store', () => {
mutations[types.SET_REPORTS](mockState, testReports);
expect(mockState.testReports).toEqual(expectedState.testReports);
+ expect(mockState.hasFullReport).toBe(true);
+ });
+ });
+
+ describe('set selected suite index', () => {
+ it('should set selectedSuiteIndex', () => {
+ const selectedSuiteIndex = 0;
+ mutations[types.SET_SELECTED_SUITE_INDEX](mockState, selectedSuiteIndex);
+
+ expect(mockState.selectedSuiteIndex).toEqual(selectedSuiteIndex);
});
});
- describe('set selected suite', () => {
- it('should set selectedSuite', () => {
- const selectedSuite = testReports.test_suites[0];
- mutations[types.SET_SELECTED_SUITE](mockState, selectedSuite);
+ describe('set summary', () => {
+ it('should set summary', () => {
+ const summary = { total_count: 1 };
+ mutations[types.SET_SUMMARY](mockState, summary);
- expect(mockState.selectedSuite).toEqual(selectedSuite);
+ expect(mockState.testReports).toEqual(summary);
});
});
diff --git a/spec/frontend/pipelines/test_reports/test_reports_spec.js b/spec/frontend/pipelines/test_reports/test_reports_spec.js
index cc86ba6d46d..ef0bcffabe3 100644
--- a/spec/frontend/pipelines/test_reports/test_reports_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_reports_spec.js
@@ -1,8 +1,13 @@
import Vuex from 'vuex';
-import { shallowMount } from '@vue/test-utils';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
import { getJSONFixture } from 'helpers/fixtures';
import TestReports from '~/pipelines/components/test_reports/test_reports.vue';
-import * as actions from '~/pipelines/stores/test_reports/actions';
+import TestSummary from '~/pipelines/components/test_reports/test_summary.vue';
+import TestSummaryTable from '~/pipelines/components/test_reports/test_summary_table.vue';
+import * as getters from '~/pipelines/stores/test_reports/getters';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
describe('Test reports app', () => {
let wrapper;
@@ -13,20 +18,31 @@ describe('Test reports app', () => {
const loadingSpinner = () => wrapper.find('.js-loading-spinner');
const testsDetail = () => wrapper.find('.js-tests-detail');
const noTestsToShow = () => wrapper.find('.js-no-tests-to-show');
+ const testSummary = () => wrapper.find(TestSummary);
+ const testSummaryTable = () => wrapper.find(TestSummaryTable);
+
+ const actionSpies = {
+ fetchFullReport: jest.fn(),
+ fetchSummary: jest.fn(),
+ setSelectedSuiteIndex: jest.fn(),
+ removeSelectedSuiteIndex: jest.fn(),
+ };
const createComponent = (state = {}) => {
store = new Vuex.Store({
state: {
isLoading: false,
- selectedSuite: {},
+ selectedSuiteIndex: null,
testReports,
...state,
},
- actions,
+ actions: actionSpies,
+ getters,
});
wrapper = shallowMount(TestReports, {
store,
+ localVue,
});
};
@@ -34,6 +50,16 @@ describe('Test reports app', () => {
wrapper.destroy();
});
+ describe('when component is created', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('should call fetchSummary', () => {
+ expect(actionSpies.fetchSummary).toHaveBeenCalled();
+ });
+ });
+
describe('when loading', () => {
beforeEach(() => createComponent({ isLoading: true }));
@@ -63,4 +89,41 @@ describe('Test reports app', () => {
expect(wrapper.vm.showTests).toBeTruthy();
});
});
+
+ describe('when a suite is clicked', () => {
+ describe('when the full test report has already been received', () => {
+ beforeEach(() => {
+ createComponent({ hasFullReport: true });
+ testSummaryTable().vm.$emit('row-click', 0);
+ });
+
+ it('should only call setSelectedSuiteIndex', () => {
+ expect(actionSpies.setSelectedSuiteIndex).toHaveBeenCalled();
+ expect(actionSpies.fetchFullReport).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when the full test report has not been received', () => {
+ beforeEach(() => {
+ createComponent({ hasFullReport: false });
+ testSummaryTable().vm.$emit('row-click', 0);
+ });
+
+ it('should call setSelectedSuiteIndex and fetchFullReport', () => {
+ expect(actionSpies.setSelectedSuiteIndex).toHaveBeenCalled();
+ expect(actionSpies.fetchFullReport).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('when clicking back to summary', () => {
+ beforeEach(() => {
+ createComponent({ selectedSuiteIndex: 0 });
+ testSummary().vm.$emit('on-back-click');
+ });
+
+ it('should call removeSelectedSuiteIndex', () => {
+ expect(actionSpies.removeSelectedSuiteIndex).toHaveBeenCalled();
+ });
+ });
});
diff --git a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
index a5b093cf769..65bffe7039a 100644
--- a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
@@ -1,11 +1,14 @@
import Vuex from 'vuex';
-import { shallowMount } from '@vue/test-utils';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
import { getJSONFixture } from 'helpers/fixtures';
import SuiteTable from '~/pipelines/components/test_reports/test_suite_table.vue';
import * as getters from '~/pipelines/stores/test_reports/getters';
import { TestStatus } from '~/pipelines/constants';
import skippedTestCases from './mock_data';
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
describe('Test reports suite table', () => {
let wrapper;
let store;
@@ -25,13 +28,17 @@ describe('Test reports suite table', () => {
const createComponent = (suite = testSuite) => {
store = new Vuex.Store({
state: {
- selectedSuite: suite,
+ testReports: {
+ test_suites: [suite],
+ },
+ selectedSuiteIndex: 0,
},
getters,
});
wrapper = shallowMount(SuiteTable, {
store,
+ localVue,
});
};
diff --git a/spec/frontend/pipelines/test_reports/test_summary_spec.js b/spec/frontend/pipelines/test_reports/test_summary_spec.js
index 8f041e46472..79be6c168cf 100644
--- a/spec/frontend/pipelines/test_reports/test_summary_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_summary_spec.js
@@ -60,7 +60,7 @@ describe('Test reports summary', () => {
});
it('displays the correct total', () => {
- expect(totalTests().text()).toBe('4 jobs');
+ expect(totalTests().text()).toBe('4 tests');
});
it('displays the correct failure count', () => {
diff --git a/spec/frontend/pipelines/time_ago_spec.js b/spec/frontend/pipelines/time_ago_spec.js
index 1bd16182d47..04934fb93b0 100644
--- a/spec/frontend/pipelines/time_ago_spec.js
+++ b/spec/frontend/pipelines/time_ago_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import TimeAgo from '~/pipelines/components/time_ago.vue';
+import TimeAgo from '~/pipelines/components/pipelines_list/time_ago.vue';
describe('Timeago component', () => {
let wrapper;
diff --git a/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js
index 1a85221581e..650dd8a1def 100644
--- a/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js
@@ -1,7 +1,7 @@
import Api from '~/api';
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import PipelineBranchNameToken from '~/pipelines/components/tokens/pipeline_branch_name_token.vue';
+import PipelineBranchNameToken from '~/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue';
import { branches, mockBranchesAfterMap } from '../mock_data';
describe('Pipeline Branch Name Token', () => {
diff --git a/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js
index ee3694868a5..096e4cd97f6 100644
--- a/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js
@@ -1,6 +1,6 @@
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import PipelineStatusToken from '~/pipelines/components/tokens/pipeline_status_token.vue';
+import PipelineStatusToken from '~/pipelines/components/pipelines_list/tokens/pipeline_status_token.vue';
describe('Pipeline Status Token', () => {
let wrapper;
diff --git a/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js
index 9fecc9412b7..15b283dc2ff 100644
--- a/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js
@@ -1,7 +1,7 @@
import Api from '~/api';
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import PipelineTagNameToken from '~/pipelines/components/tokens/pipeline_tag_name_token.vue';
+import PipelineTagNameToken from '~/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue';
import { tags, mockTagsAfterMap } from '../mock_data';
describe('Pipeline Branch Name Token', () => {
diff --git a/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js
index 98de4f40c51..0b5cf2e202b 100644
--- a/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js
@@ -1,7 +1,7 @@
import Api from '~/api';
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import PipelineTriggerAuthorToken from '~/pipelines/components/tokens/pipeline_trigger_author_token.vue';
+import PipelineTriggerAuthorToken from '~/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue';
import { users } from '../mock_data';
describe('Pipeline Trigger Author Token', () => {
diff --git a/spec/frontend/polyfills/element_spec.js b/spec/frontend/polyfills/element_spec.js
deleted file mode 100644
index 64ce248ca44..00000000000
--- a/spec/frontend/polyfills/element_spec.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import '~/commons/polyfills/element';
-
-describe('Element polyfills', () => {
- let testContext;
-
- beforeEach(() => {
- testContext = {};
- });
-
- beforeEach(() => {
- testContext.element = document.createElement('ul');
- });
-
- describe('matches', () => {
- it('returns true if element matches the selector', () => {
- expect(testContext.element.matches('ul')).toBeTruthy();
- });
-
- it("returns false if element doesn't match the selector", () => {
- expect(testContext.element.matches('.not-an-element')).toBeFalsy();
- });
- });
-
- describe('closest', () => {
- beforeEach(() => {
- testContext.childElement = document.createElement('li');
- testContext.element.appendChild(testContext.childElement);
- });
-
- it('returns the closest parent that matches the selector', () => {
- expect(testContext.childElement.closest('ul').toString()).toBe(
- testContext.element.toString(),
- );
- });
-
- it('returns itself if it matches the selector', () => {
- expect(testContext.childElement.closest('li').toString()).toBe(
- testContext.childElement.toString(),
- );
- });
-
- it('returns undefined if nothing matches the selector', () => {
- expect(testContext.childElement.closest('.no-an-element')).toBeFalsy();
- });
- });
-});
diff --git a/spec/frontend/projects/commits/store/actions_spec.js b/spec/frontend/projects/commits/store/actions_spec.js
index c9945e1cc27..886224252ad 100644
--- a/spec/frontend/projects/commits/store/actions_spec.js
+++ b/spec/frontend/projects/commits/store/actions_spec.js
@@ -45,7 +45,7 @@ describe('Project commits actions', () => {
describe('fetchAuthors', () => {
it('dispatches request/receive', () => {
- const path = '/autocomplete/users.json';
+ const path = '/-/autocomplete/users.json';
state.projectId = '8';
const data = [{ id: 1 }];
@@ -60,7 +60,7 @@ describe('Project commits actions', () => {
});
it('dispatches request/receive on error', () => {
- const path = '/autocomplete/users.json';
+ const path = '/-/autocomplete/users.json';
mock.onGet(path).replyOnce(500);
testAction(actions.fetchAuthors, null, state, [], [{ type: 'receiveAuthorsError' }]);
diff --git a/spec/frontend/projects/components/__snapshots__/remove_modal_spec.js.snap b/spec/frontend/projects/components/__snapshots__/remove_modal_spec.js.snap
new file mode 100644
index 00000000000..4d5b6c56a34
--- /dev/null
+++ b/spec/frontend/projects/components/__snapshots__/remove_modal_spec.js.snap
@@ -0,0 +1,126 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Project remove modal initialized matches the snapshot 1`] = `
+<form
+ action="some/path"
+ method="post"
+>
+ <input
+ name="_method"
+ type="hidden"
+ value="delete"
+ />
+
+ <input
+ name="authenticity_token"
+ type="hidden"
+ />
+
+ <b-button-stub
+ class="[object Object]"
+ event="click"
+ role="button"
+ routertag="a"
+ size="md"
+ tabindex="0"
+ tag="button"
+ type="button"
+ variant="danger"
+ >
+ <!---->
+
+ <!---->
+
+ <span
+ class="gl-button-text"
+ >
+ Remove project
+ </span>
+ </b-button-stub>
+
+ <b-modal-stub
+ canceltitle="Cancel"
+ cancelvariant="secondary"
+ footerclass="bg-gray-light gl-p-5"
+ headerclosecontent="&times;"
+ headercloselabel="Close"
+ id="remove-project-modal"
+ ignoreenforcefocusselector=""
+ lazy="true"
+ modalclass="gl-modal,"
+ oktitle="OK"
+ okvariant="danger"
+ size="sm"
+ title=""
+ titletag="h4"
+ >
+
+ <div>
+ <p
+ class="gl-text-red-500 gl-font-weight-bold"
+ >
+ This can lead to data loss.
+ </p>
+
+ <p
+ class="gl-mb-0"
+ >
+ This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention.
+ </p>
+
+ <p>
+ <gl-sprintf-stub
+ message="Please type %{phrase_code} to proceed or close this modal to cancel."
+ />
+ </p>
+
+ <gl-form-input-stub
+ id="confirm_name_input"
+ name="confirm_name_input"
+ type="text"
+ />
+ </div>
+
+ <template />
+
+ <template>
+ Confirmation required
+ </template>
+
+ <template />
+
+ <template />
+
+ <template />
+
+ <template>
+ <div
+ class="gl-w-full gl-display-flex gl-just-content-start gl-m-0"
+ >
+ <b-button-stub
+ class="[object Object]"
+ disabled="true"
+ event="click"
+ routertag="a"
+ size="md"
+ tag="button"
+ type="button"
+ variant="danger"
+ >
+ <!---->
+
+ <!---->
+
+ <span
+ class="gl-button-text"
+ >
+
+ Confirm
+
+ </span>
+ </b-button-stub>
+ </div>
+ </template>
+ </b-modal-stub>
+</form>
+`;
diff --git a/spec/frontend/projects/components/remove_modal_spec.js b/spec/frontend/projects/components/remove_modal_spec.js
new file mode 100644
index 00000000000..339aee65b99
--- /dev/null
+++ b/spec/frontend/projects/components/remove_modal_spec.js
@@ -0,0 +1,62 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlButton, GlModal } from '@gitlab/ui';
+import ProjectRemoveModal from '~/projects/components/remove_modal.vue';
+
+describe('Project remove modal', () => {
+ let wrapper;
+
+ const findFormElement = () => wrapper.find('form').element;
+ const findConfirmButton = () => wrapper.find(GlModal).find(GlButton);
+
+ const defaultProps = {
+ formPath: 'some/path',
+ confirmPhrase: 'foo',
+ warningMessage: 'This can lead to data loss.',
+ };
+
+ const createComponent = (data = {}) => {
+ wrapper = shallowMount(ProjectRemoveModal, {
+ propsData: defaultProps,
+ data: () => data,
+ stubs: {
+ GlButton,
+ GlModal,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('initialized', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('matches the snapshot', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ describe('user input matches the confirmPhrase', () => {
+ beforeEach(() => {
+ createComponent({ userInput: defaultProps.confirmPhrase });
+ });
+
+ it('the confirm button is not dislabled', () => {
+ expect(findConfirmButton().attributes('disabled')).toBe(undefined);
+ });
+
+ describe('and when the confirmation button is clicked', () => {
+ beforeEach(() => {
+ findConfirmButton().vm.$emit('click');
+ });
+
+ it('submits the form element', () => {
+ expect(findFormElement().submit).toHaveBeenCalled();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/projects/pipelines/charts/components/__snapshots__/pipelines_area_chart_spec.js.snap b/spec/frontend/projects/pipelines/charts/components/__snapshots__/pipelines_area_chart_spec.js.snap
index f280ecaa0bc..d68e009f46e 100644
--- a/spec/frontend/projects/pipelines/charts/components/__snapshots__/pipelines_area_chart_spec.js.snap
+++ b/spec/frontend/projects/pipelines/charts/components/__snapshots__/pipelines_area_chart_spec.js.snap
@@ -2,7 +2,7 @@
exports[`PipelinesAreaChart matches the snapshot 1`] = `
<div
- class="prepend-top-default"
+ class="gl-mt-3"
>
<p>
Some title
diff --git a/spec/frontend/projects/project_new_spec.js b/spec/frontend/projects/project_new_spec.js
index 7c6ff90aff6..7aafbd33fc8 100644
--- a/spec/frontend/projects/project_new_spec.js
+++ b/spec/frontend/projects/project_new_spec.js
@@ -1,5 +1,6 @@
import $ from 'jquery';
import projectNew from '~/projects/project_new';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('New Project', () => {
let $projectImportUrl;
@@ -33,7 +34,7 @@ describe('New Project', () => {
});
describe('deriveProjectPathFromUrl', () => {
- const dummyImportUrl = `${gl.TEST_HOST}/dummy/import/url.git`;
+ const dummyImportUrl = `${TEST_HOST}/dummy/import/url.git`;
beforeEach(() => {
projectNew.bindEvents();
diff --git a/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js b/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js
new file mode 100644
index 00000000000..4c873bdfd60
--- /dev/null
+++ b/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js
@@ -0,0 +1,226 @@
+import { shallowMount, mount } from '@vue/test-utils';
+import AxiosMockAdapter from 'axios-mock-adapter';
+import waitForPromises from 'helpers/wait_for_promises';
+import ServiceDeskRoot from '~/projects/settings_service_desk/components/service_desk_root.vue';
+import axios from '~/lib/utils/axios_utils';
+import httpStatusCodes from '~/lib/utils/http_status';
+
+describe('ServiceDeskRoot', () => {
+ const endpoint = '/gitlab-org/gitlab-test/service_desk';
+ const initialIncomingEmail = 'servicedeskaddress@example.com';
+ let axiosMock;
+ let wrapper;
+ let spy;
+
+ beforeEach(() => {
+ axiosMock = new AxiosMockAdapter(axios);
+ });
+
+ afterEach(() => {
+ axiosMock.restore();
+ wrapper.destroy();
+ if (spy) {
+ spy.mockRestore();
+ }
+ });
+
+ it('fetches incoming email when there is no incoming email provided', () => {
+ axiosMock.onGet(endpoint).replyOnce(httpStatusCodes.OK);
+
+ wrapper = shallowMount(ServiceDeskRoot, {
+ propsData: {
+ initialIsEnabled: true,
+ initialIncomingEmail: '',
+ endpoint,
+ },
+ });
+
+ return wrapper.vm
+ .$nextTick()
+ .then(waitForPromises)
+ .then(() => {
+ expect(axiosMock.history.get).toHaveLength(1);
+ });
+ });
+
+ it('does not fetch incoming email when there is an incoming email provided', () => {
+ axiosMock.onGet(endpoint).replyOnce(httpStatusCodes.OK);
+
+ wrapper = shallowMount(ServiceDeskRoot, {
+ propsData: {
+ initialIsEnabled: true,
+ initialIncomingEmail,
+ endpoint,
+ },
+ });
+
+ return wrapper.vm
+ .$nextTick()
+ .then(waitForPromises)
+ .then(() => {
+ expect(axiosMock.history.get).toHaveLength(0);
+ });
+ });
+
+ it('shows an error message when incoming email is not fetched correctly', () => {
+ axiosMock.onGet(endpoint).networkError();
+
+ wrapper = shallowMount(ServiceDeskRoot, {
+ propsData: {
+ initialIsEnabled: true,
+ initialIncomingEmail: '',
+ endpoint,
+ },
+ });
+
+ return wrapper.vm
+ .$nextTick()
+ .then(waitForPromises)
+ .then(() => {
+ expect(wrapper.html()).toContain(
+ 'An error occurred while fetching the Service Desk address.',
+ );
+ });
+ });
+
+ it('sends a request to toggle service desk off when the toggle is clicked from the on state', () => {
+ axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK);
+
+ spy = jest.spyOn(axios, 'put');
+
+ wrapper = mount(ServiceDeskRoot, {
+ propsData: {
+ initialIsEnabled: true,
+ initialIncomingEmail,
+ endpoint,
+ },
+ });
+
+ wrapper.find('button.gl-toggle').trigger('click');
+
+ return wrapper.vm
+ .$nextTick()
+ .then(waitForPromises)
+ .then(() => {
+ expect(spy).toHaveBeenCalledWith(endpoint, { service_desk_enabled: false });
+ });
+ });
+
+ it('sends a request to toggle service desk on when the toggle is clicked from the off state', () => {
+ axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK);
+
+ spy = jest.spyOn(axios, 'put');
+
+ wrapper = mount(ServiceDeskRoot, {
+ propsData: {
+ initialIsEnabled: false,
+ initialIncomingEmail: '',
+ endpoint,
+ },
+ });
+
+ wrapper.find('button.gl-toggle').trigger('click');
+
+ return wrapper.vm.$nextTick(() => {
+ expect(spy).toHaveBeenCalledWith(endpoint, { service_desk_enabled: true });
+ });
+ });
+
+ it('shows an error message when there is an issue toggling service desk on', () => {
+ axiosMock.onPut(endpoint).networkError();
+
+ wrapper = mount(ServiceDeskRoot, {
+ propsData: {
+ initialIsEnabled: false,
+ initialIncomingEmail: '',
+ endpoint,
+ },
+ });
+
+ wrapper.find('button.gl-toggle').trigger('click');
+
+ return wrapper.vm
+ .$nextTick()
+ .then(waitForPromises)
+ .then(() => {
+ expect(wrapper.html()).toContain('An error occurred while enabling Service Desk.');
+ });
+ });
+
+ it('sends a request to update template when the "Save template" button is clicked', () => {
+ axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK);
+
+ spy = jest.spyOn(axios, 'put');
+
+ wrapper = mount(ServiceDeskRoot, {
+ propsData: {
+ initialIsEnabled: true,
+ endpoint,
+ initialIncomingEmail,
+ selectedTemplate: 'Bug',
+ outgoingName: 'GitLab Support Bot',
+ templates: ['Bug', 'Documentation'],
+ projectKey: 'key',
+ },
+ });
+
+ wrapper.find('button.btn-success').trigger('click');
+
+ return wrapper.vm.$nextTick(() => {
+ expect(spy).toHaveBeenCalledWith(endpoint, {
+ issue_template_key: 'Bug',
+ outgoing_name: 'GitLab Support Bot',
+ project_key: 'key',
+ service_desk_enabled: true,
+ });
+ });
+ });
+
+ it('saves the template when the "Save template" button is clicked', () => {
+ axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK);
+
+ wrapper = mount(ServiceDeskRoot, {
+ propsData: {
+ initialIsEnabled: true,
+ endpoint,
+ initialIncomingEmail,
+ selectedTemplate: 'Bug',
+ templates: ['Bug', 'Documentation'],
+ },
+ });
+
+ wrapper.find('button.btn-success').trigger('click');
+
+ return wrapper.vm
+ .$nextTick()
+ .then(waitForPromises)
+ .then(() => {
+ expect(wrapper.html()).toContain('Template was successfully saved.');
+ });
+ });
+
+ it('shows an error message when there is an issue saving the template', () => {
+ axiosMock.onPut(endpoint).networkError();
+
+ wrapper = mount(ServiceDeskRoot, {
+ propsData: {
+ initialIsEnabled: true,
+ endpoint,
+ initialIncomingEmail,
+ selectedTemplate: 'Bug',
+ templates: ['Bug', 'Documentation'],
+ },
+ });
+
+ wrapper.find('button.btn-success').trigger('click');
+
+ return wrapper.vm
+ .$nextTick()
+ .then(waitForPromises)
+ .then(() => {
+ expect(wrapper.html()).toContain(
+ 'An error occurred while saving the template. Please check if the template exists.',
+ );
+ });
+ });
+});
diff --git a/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js b/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js
new file mode 100644
index 00000000000..7fe310aa400
--- /dev/null
+++ b/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js
@@ -0,0 +1,234 @@
+import { shallowMount, mount } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
+import eventHub from '~/projects/settings_service_desk/event_hub';
+import ServiceDeskSetting from '~/projects/settings_service_desk/components/service_desk_setting.vue';
+
+describe('ServiceDeskSetting', () => {
+ let wrapper;
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ const findTemplateDropdown = () => wrapper.find('#service-desk-template-select');
+
+ describe('when isEnabled=true', () => {
+ describe('only isEnabled', () => {
+ describe('as project admin', () => {
+ beforeEach(() => {
+ wrapper = shallowMount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: true,
+ },
+ });
+ });
+
+ it('should see activation checkbox', () => {
+ expect(wrapper.contains('#service-desk-checkbox')).toBe(true);
+ });
+
+ it('should see main panel with the email info', () => {
+ expect(wrapper.contains('#incoming-email-describer')).toBe(true);
+ });
+
+ it('should see loading spinner and not the incoming email', () => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.contains('.incoming-email')).toBe(false);
+ });
+ });
+ });
+
+ describe('service desk toggle', () => {
+ it('emits an event to turn on Service Desk when clicked', () => {
+ const eventSpy = jest.fn();
+ eventHub.$on('serviceDeskEnabledCheckboxToggled', eventSpy);
+
+ wrapper = mount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: false,
+ },
+ });
+
+ wrapper.find('#service-desk-checkbox').trigger('click');
+
+ expect(eventSpy).toHaveBeenCalledWith(true);
+
+ eventHub.$off('serviceDeskEnabledCheckboxToggled', eventSpy);
+ eventSpy.mockRestore();
+ });
+ });
+
+ describe('with incomingEmail', () => {
+ const incomingEmail = 'foo@bar.com';
+
+ beforeEach(() => {
+ wrapper = mount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: true,
+ incomingEmail,
+ },
+ });
+ });
+
+ it('should see email and not the loading spinner', () => {
+ expect(wrapper.find('.incoming-email').element.value).toEqual(incomingEmail);
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ });
+
+ it('renders a copy to clipboard button', () => {
+ expect(wrapper.contains('.qa-clipboard-button')).toBe(true);
+ expect(wrapper.find('.qa-clipboard-button').element.dataset.clipboardText).toBe(
+ incomingEmail,
+ );
+ });
+ });
+
+ describe('templates dropdown', () => {
+ it('renders a dropdown to choose a template', () => {
+ wrapper = shallowMount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: true,
+ },
+ });
+
+ expect(wrapper.contains('#service-desk-template-select')).toBe(true);
+ });
+
+ it('renders a dropdown with a default value of ""', () => {
+ wrapper = mount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: true,
+ },
+ });
+
+ expect(findTemplateDropdown().element.value).toEqual('');
+ });
+
+ it('renders a dropdown with a value of "Bug" when it is the initial value', () => {
+ const templates = ['Bug', 'Documentation', 'Security release'];
+
+ wrapper = mount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: true,
+ initialSelectedTemplate: 'Bug',
+ templates,
+ },
+ });
+
+ expect(findTemplateDropdown().element.value).toEqual('Bug');
+ });
+
+ it('renders a dropdown with no options when the project has no templates', () => {
+ wrapper = mount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: true,
+ templates: [],
+ },
+ });
+
+ // The dropdown by default has one empty option
+ expect(findTemplateDropdown().element.children).toHaveLength(1);
+ });
+
+ it('renders a dropdown with options when the project has templates', () => {
+ const templates = ['Bug', 'Documentation', 'Security release'];
+ wrapper = mount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: true,
+ templates,
+ },
+ });
+
+ // An empty-named template is prepended so the user can select no template
+ const expectedTemplates = [''].concat(templates);
+
+ const dropdown = findTemplateDropdown();
+ const dropdownList = Array.from(dropdown.element.children).map(option => option.innerText);
+
+ expect(dropdown.element.children).toHaveLength(expectedTemplates.length);
+ expect(dropdownList.includes('Bug')).toEqual(true);
+ expect(dropdownList.includes('Documentation')).toEqual(true);
+ expect(dropdownList.includes('Security release')).toEqual(true);
+ });
+ });
+ });
+
+ describe('save button', () => {
+ it('renders a save button to save a template', () => {
+ wrapper = mount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: true,
+ },
+ });
+
+ expect(wrapper.find('button.btn-success').text()).toContain('Save template');
+ });
+
+ it('emits a save event with the chosen template when the save button is clicked', () => {
+ const eventSpy = jest.fn();
+ eventHub.$on('serviceDeskTemplateSave', eventSpy);
+
+ wrapper = mount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: true,
+ initialSelectedTemplate: 'Bug',
+ initialOutgoingName: 'GitLab Support Bot',
+ initialProjectKey: 'key',
+ },
+ });
+
+ wrapper.find('button.btn-success').trigger('click');
+
+ expect(eventSpy).toHaveBeenCalledWith({
+ selectedTemplate: 'Bug',
+ outgoingName: 'GitLab Support Bot',
+ projectKey: 'key',
+ });
+
+ eventHub.$off('serviceDeskTemplateSave', eventSpy);
+ eventSpy.mockRestore();
+ });
+ });
+
+ describe('when isEnabled=false', () => {
+ beforeEach(() => {
+ wrapper = shallowMount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: false,
+ },
+ });
+ });
+
+ it('does not render email panel', () => {
+ expect(wrapper.contains('#incoming-email-describer')).toBe(false);
+ });
+
+ it('does not render template dropdown', () => {
+ expect(wrapper.contains('#service-desk-template-select')).toBe(false);
+ });
+
+ it('does not render template save button', () => {
+ expect(wrapper.contains('button.btn-success')).toBe(false);
+ });
+
+ it('emits an event to turn on Service Desk when the toggle is clicked', () => {
+ const eventSpy = jest.fn();
+ eventHub.$on('serviceDeskEnabledCheckboxToggled', eventSpy);
+
+ wrapper = mount(ServiceDeskSetting, {
+ propsData: {
+ isEnabled: true,
+ },
+ });
+
+ wrapper.find('#service-desk-checkbox').trigger('click');
+
+ expect(eventSpy).toHaveBeenCalledWith(false);
+
+ eventHub.$off('serviceDeskEnabledCheckboxToggled', eventSpy);
+ eventSpy.mockRestore();
+ });
+ });
+});
diff --git a/spec/frontend/projects/settings_service_desk/services/service_desk_service_spec.js b/spec/frontend/projects/settings_service_desk/services/service_desk_service_spec.js
new file mode 100644
index 00000000000..f9e4d55245a
--- /dev/null
+++ b/spec/frontend/projects/settings_service_desk/services/service_desk_service_spec.js
@@ -0,0 +1,129 @@
+import AxiosMockAdapter from 'axios-mock-adapter';
+import ServiceDeskService from '~/projects/settings_service_desk/services/service_desk_service';
+import axios from '~/lib/utils/axios_utils';
+import httpStatusCodes from '~/lib/utils/http_status';
+
+describe('ServiceDeskService', () => {
+ const endpoint = `/gitlab-org/gitlab-test/service_desk`;
+ const dummyResponse = { message: 'Dummy response' };
+ const errorMessage = 'Network Error';
+ let axiosMock;
+ let service;
+
+ beforeEach(() => {
+ axiosMock = new AxiosMockAdapter(axios);
+ service = new ServiceDeskService(endpoint);
+ });
+
+ afterEach(() => {
+ axiosMock.restore();
+ });
+
+ describe('fetchIncomingEmail', () => {
+ it('makes a request to fetch incoming email', () => {
+ axiosMock.onGet(endpoint).replyOnce(httpStatusCodes.OK, dummyResponse);
+
+ return service.fetchIncomingEmail().then(response => {
+ expect(response.data).toEqual(dummyResponse);
+ });
+ });
+
+ it('fails on error response', () => {
+ axiosMock.onGet(endpoint).networkError();
+
+ return service.fetchIncomingEmail().catch(error => {
+ expect(error.message).toBe(errorMessage);
+ });
+ });
+ });
+
+ describe('toggleServiceDesk', () => {
+ it('makes a request to set service desk', () => {
+ axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK, dummyResponse);
+
+ return service.toggleServiceDesk(true).then(response => {
+ expect(response.data).toEqual(dummyResponse);
+ });
+ });
+
+ it('fails on error response', () => {
+ axiosMock.onPut(endpoint).networkError();
+
+ return service.toggleServiceDesk(true).catch(error => {
+ expect(error.message).toBe(errorMessage);
+ });
+ });
+
+ it('makes a request with the expected body', () => {
+ axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK, dummyResponse);
+
+ const spy = jest.spyOn(axios, 'put');
+
+ service.toggleServiceDesk(true);
+
+ expect(spy).toHaveBeenCalledWith(endpoint, {
+ service_desk_enabled: true,
+ });
+
+ spy.mockRestore();
+ });
+ });
+
+ describe('updateTemplate', () => {
+ it('makes a request to update template', () => {
+ axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK, dummyResponse);
+
+ return service
+ .updateTemplate(
+ {
+ selectedTemplate: 'Bug',
+ outgoingName: 'GitLab Support Bot',
+ },
+ true,
+ )
+ .then(response => {
+ expect(response.data).toEqual(dummyResponse);
+ });
+ });
+
+ it('fails on error response', () => {
+ axiosMock.onPut(endpoint).networkError();
+
+ return service
+ .updateTemplate(
+ {
+ selectedTemplate: 'Bug',
+ outgoingName: 'GitLab Support Bot',
+ },
+ true,
+ )
+ .catch(error => {
+ expect(error.message).toBe(errorMessage);
+ });
+ });
+
+ it('makes a request with the expected body', () => {
+ axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK, dummyResponse);
+
+ const spy = jest.spyOn(axios, 'put');
+
+ service.updateTemplate(
+ {
+ selectedTemplate: 'Bug',
+ outgoingName: 'GitLab Support Bot',
+ projectKey: 'key',
+ },
+ true,
+ );
+
+ expect(spy).toHaveBeenCalledWith(endpoint, {
+ issue_template_key: 'Bug',
+ outgoing_name: 'GitLab Support Bot',
+ project_key: 'key',
+ service_desk_enabled: true,
+ });
+
+ spy.mockRestore();
+ });
+ });
+});
diff --git a/spec/frontend/ref/components/ref_selector_spec.js b/spec/frontend/ref/components/ref_selector_spec.js
new file mode 100644
index 00000000000..2688e4b3428
--- /dev/null
+++ b/spec/frontend/ref/components/ref_selector_spec.js
@@ -0,0 +1,532 @@
+import Vuex from 'vuex';
+import { mount, createLocalVue } from '@vue/test-utils';
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import { GlLoadingIcon, GlSearchBoxByType, GlNewDropdownItem, GlIcon } from '@gitlab/ui';
+import { trimText } from 'helpers/text_helper';
+import { sprintf } from '~/locale';
+import RefSelector from '~/ref/components/ref_selector.vue';
+import { X_TOTAL_HEADER, DEFAULT_I18N } from '~/ref/constants';
+import createStore from '~/ref/stores/';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('Ref selector component', () => {
+ const fixtures = {
+ branches: getJSONFixture('api/branches/branches.json'),
+ tags: getJSONFixture('api/tags/tags.json'),
+ commit: getJSONFixture('api/commits/commit.json'),
+ };
+
+ const projectId = '8';
+
+ let wrapper;
+ let branchesApiCallSpy;
+ let tagsApiCallSpy;
+ let commitApiCallSpy;
+
+ const createComponent = () => {
+ wrapper = mount(RefSelector, {
+ propsData: {
+ projectId,
+ value: '',
+ },
+ listeners: {
+ // simulate a parent component v-model binding
+ input: selectedRef => {
+ wrapper.setProps({ value: selectedRef });
+ },
+ },
+ stubs: {
+ GlSearchBoxByType: true,
+ },
+ localVue,
+ store: createStore(),
+ });
+ };
+
+ beforeEach(() => {
+ const mock = new MockAdapter(axios);
+ gon.api_version = 'v4';
+
+ branchesApiCallSpy = jest
+ .fn()
+ .mockReturnValue([200, fixtures.branches, { [X_TOTAL_HEADER]: '123' }]);
+ tagsApiCallSpy = jest.fn().mockReturnValue([200, fixtures.tags, { [X_TOTAL_HEADER]: '456' }]);
+ commitApiCallSpy = jest.fn().mockReturnValue([200, fixtures.commit]);
+
+ mock
+ .onGet(`/api/v4/projects/${projectId}/repository/branches`)
+ .reply(config => branchesApiCallSpy(config));
+ mock
+ .onGet(`/api/v4/projects/${projectId}/repository/tags`)
+ .reply(config => tagsApiCallSpy(config));
+ mock
+ .onGet(new RegExp(`/api/v4/projects/${projectId}/repository/commits/.*`))
+ .reply(config => commitApiCallSpy(config));
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ //
+ // Finders
+ //
+ const findButtonContent = () => wrapper.find('[data-testid="button-content"]');
+
+ const findNoResults = () => wrapper.find('[data-testid="no-results"]');
+
+ const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+
+ const findBranchesSection = () => wrapper.find('[data-testid="branches-section"]');
+ const findBranchDropdownItems = () => findBranchesSection().findAll(GlNewDropdownItem);
+ const findFirstBranchDropdownItem = () => findBranchDropdownItems().at(0);
+
+ const findTagsSection = () => wrapper.find('[data-testid="tags-section"]');
+ const findTagDropdownItems = () => findTagsSection().findAll(GlNewDropdownItem);
+ const findFirstTagDropdownItem = () => findTagDropdownItems().at(0);
+
+ const findCommitsSection = () => wrapper.find('[data-testid="commits-section"]');
+ const findCommitDropdownItems = () => findCommitsSection().findAll(GlNewDropdownItem);
+ const findFirstCommitDropdownItem = () => findCommitDropdownItems().at(0);
+
+ //
+ // Expecters
+ //
+ const branchesSectionContainsErrorMessage = () => {
+ const branchesSection = findBranchesSection();
+
+ return branchesSection.text().includes(DEFAULT_I18N.branchesErrorMessage);
+ };
+
+ const tagsSectionContainsErrorMessage = () => {
+ const tagsSection = findTagsSection();
+
+ return tagsSection.text().includes(DEFAULT_I18N.tagsErrorMessage);
+ };
+
+ const commitsSectionContainsErrorMessage = () => {
+ const commitsSection = findCommitsSection();
+
+ return commitsSection.text().includes(DEFAULT_I18N.commitsErrorMessage);
+ };
+
+ //
+ // Convenience methods
+ //
+ const updateQuery = newQuery => {
+ wrapper.find(GlSearchBoxByType).vm.$emit('input', newQuery);
+ };
+
+ const selectFirstBranch = () => {
+ findFirstBranchDropdownItem().vm.$emit('click');
+ };
+
+ const selectFirstTag = () => {
+ findFirstTagDropdownItem().vm.$emit('click');
+ };
+
+ const selectFirstCommit = () => {
+ findFirstCommitDropdownItem().vm.$emit('click');
+ };
+
+ const waitForRequests = ({ andClearMocks } = { andClearMocks: false }) =>
+ axios.waitForAll().then(() => {
+ if (andClearMocks) {
+ branchesApiCallSpy.mockClear();
+ tagsApiCallSpy.mockClear();
+ commitApiCallSpy.mockClear();
+ }
+ });
+
+ describe('initialization behavior', () => {
+ beforeEach(createComponent);
+
+ it('initializes the dropdown with branches and tags when mounted', () => {
+ return waitForRequests().then(() => {
+ expect(branchesApiCallSpy).toHaveBeenCalledTimes(1);
+ expect(tagsApiCallSpy).toHaveBeenCalledTimes(1);
+ expect(commitApiCallSpy).not.toHaveBeenCalled();
+ });
+ });
+
+ it('shows a spinner while network requests are in progress', () => {
+ expect(findLoadingIcon().exists()).toBe(true);
+
+ return waitForRequests().then(() => {
+ expect(findLoadingIcon().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('post-initialization behavior', () => {
+ describe('when the search query is updated', () => {
+ beforeEach(() => {
+ createComponent();
+
+ return waitForRequests({ andClearMocks: true });
+ });
+
+ it('requeries the endpoints when the search query is updated', () => {
+ updateQuery('v1.2.3');
+
+ return waitForRequests().then(() => {
+ expect(branchesApiCallSpy).toHaveBeenCalledTimes(1);
+ expect(tagsApiCallSpy).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ it("does not make a call to the commit endpoint if the query doesn't look like a SHA", () => {
+ updateQuery('not a sha');
+
+ return waitForRequests().then(() => {
+ expect(commitApiCallSpy).not.toHaveBeenCalled();
+ });
+ });
+
+ it('searches for a commit if the query could potentially be a SHA', () => {
+ updateQuery('abcdef');
+
+ return waitForRequests().then(() => {
+ expect(commitApiCallSpy).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('when no results are found', () => {
+ beforeEach(() => {
+ branchesApiCallSpy = jest.fn().mockReturnValue([200, [], { [X_TOTAL_HEADER]: '0' }]);
+ tagsApiCallSpy = jest.fn().mockReturnValue([200, [], { [X_TOTAL_HEADER]: '0' }]);
+ commitApiCallSpy = jest.fn().mockReturnValue([404]);
+
+ createComponent();
+
+ return waitForRequests();
+ });
+
+ describe('when the search query is empty', () => {
+ it('renders a "no results" message', () => {
+ expect(findNoResults().text()).toBe(DEFAULT_I18N.noResults);
+ });
+ });
+
+ describe('when the search query is not empty', () => {
+ const query = 'hello';
+
+ beforeEach(() => {
+ updateQuery(query);
+
+ return waitForRequests();
+ });
+
+ it('renders a "no results" message that includes the search query', () => {
+ expect(findNoResults().text()).toBe(sprintf(DEFAULT_I18N.noResultsWithQuery, { query }));
+ });
+ });
+ });
+
+ describe('branches', () => {
+ describe('when the branches search returns results', () => {
+ beforeEach(() => {
+ createComponent();
+
+ return waitForRequests();
+ });
+
+ it('renders the branches section in the dropdown', () => {
+ expect(findBranchesSection().exists()).toBe(true);
+ });
+
+ it('renders the "Branches" heading with a total number indicator', () => {
+ expect(
+ findBranchesSection()
+ .find('[data-testid="section-header"]')
+ .text(),
+ ).toBe('Branches 123');
+ });
+
+ it("does not render an error message in the branches section's body", () => {
+ expect(branchesSectionContainsErrorMessage()).toBe(false);
+ });
+
+ it('renders each non-default branch as a selectable item', () => {
+ const dropdownItems = findBranchDropdownItems();
+
+ fixtures.branches.forEach((b, i) => {
+ if (!b.default) {
+ expect(dropdownItems.at(i).text()).toBe(b.name);
+ }
+ });
+ });
+
+ it('renders the default branch as a selectable item with a "default" badge', () => {
+ const dropdownItems = findBranchDropdownItems();
+
+ const defaultBranch = fixtures.branches.find(b => b.default);
+ const defaultBranchIndex = fixtures.branches.indexOf(defaultBranch);
+
+ expect(trimText(dropdownItems.at(defaultBranchIndex).text())).toBe(
+ `${defaultBranch.name} default`,
+ );
+ });
+ });
+
+ describe('when the branches search returns no results', () => {
+ beforeEach(() => {
+ branchesApiCallSpy = jest.fn().mockReturnValue([200, [], { [X_TOTAL_HEADER]: '0' }]);
+
+ createComponent();
+
+ return waitForRequests();
+ });
+
+ it('does not render the branches section in the dropdown', () => {
+ expect(findBranchesSection().exists()).toBe(false);
+ });
+ });
+
+ describe('when the branches search returns an error', () => {
+ beforeEach(() => {
+ branchesApiCallSpy = jest.fn().mockReturnValue([500]);
+
+ createComponent();
+
+ return waitForRequests();
+ });
+
+ it('renders the branches section in the dropdown', () => {
+ expect(findBranchesSection().exists()).toBe(true);
+ });
+
+ it("renders an error message in the branches section's body", () => {
+ expect(branchesSectionContainsErrorMessage()).toBe(true);
+ });
+ });
+ });
+
+ describe('tags', () => {
+ describe('when the tags search returns results', () => {
+ beforeEach(() => {
+ createComponent();
+
+ return waitForRequests();
+ });
+
+ it('renders the tags section in the dropdown', () => {
+ expect(findTagsSection().exists()).toBe(true);
+ });
+
+ it('renders the "Tags" heading with a total number indicator', () => {
+ expect(
+ findTagsSection()
+ .find('[data-testid="section-header"]')
+ .text(),
+ ).toBe('Tags 456');
+ });
+
+ it("does not render an error message in the tags section's body", () => {
+ expect(tagsSectionContainsErrorMessage()).toBe(false);
+ });
+
+ it('renders each tag as a selectable item', () => {
+ const dropdownItems = findTagDropdownItems();
+
+ fixtures.tags.forEach((t, i) => {
+ expect(dropdownItems.at(i).text()).toBe(t.name);
+ });
+ });
+ });
+
+ describe('when the tags search returns no results', () => {
+ beforeEach(() => {
+ tagsApiCallSpy = jest.fn().mockReturnValue([200, [], { [X_TOTAL_HEADER]: '0' }]);
+
+ createComponent();
+
+ return waitForRequests();
+ });
+
+ it('does not render the tags section in the dropdown', () => {
+ expect(findTagsSection().exists()).toBe(false);
+ });
+ });
+
+ describe('when the tags search returns an error', () => {
+ beforeEach(() => {
+ tagsApiCallSpy = jest.fn().mockReturnValue([500]);
+
+ createComponent();
+
+ return waitForRequests();
+ });
+
+ it('renders the tags section in the dropdown', () => {
+ expect(findTagsSection().exists()).toBe(true);
+ });
+
+ it("renders an error message in the tags section's body", () => {
+ expect(tagsSectionContainsErrorMessage()).toBe(true);
+ });
+ });
+ });
+
+ describe('commits', () => {
+ describe('when the commit search returns results', () => {
+ beforeEach(() => {
+ createComponent();
+
+ updateQuery('abcd1234');
+
+ return waitForRequests();
+ });
+
+ it('renders the commit section in the dropdown', () => {
+ expect(findCommitsSection().exists()).toBe(true);
+ });
+
+ it('renders the "Commits" heading with a total number indicator', () => {
+ expect(
+ findCommitsSection()
+ .find('[data-testid="section-header"]')
+ .text(),
+ ).toBe('Commits 1');
+ });
+
+ it("does not render an error message in the comits section's body", () => {
+ expect(commitsSectionContainsErrorMessage()).toBe(false);
+ });
+
+ it('renders each commit as a selectable item with the short SHA and commit title', () => {
+ const dropdownItems = findCommitDropdownItems();
+
+ const { commit } = fixtures;
+
+ expect(dropdownItems.at(0).text()).toBe(`${commit.short_id} ${commit.title}`);
+ });
+ });
+
+ describe('when the commit search returns no results (i.e. a 404)', () => {
+ beforeEach(() => {
+ commitApiCallSpy = jest.fn().mockReturnValue([404]);
+
+ createComponent();
+
+ updateQuery('abcd1234');
+
+ return waitForRequests();
+ });
+
+ it('does not render the commits section in the dropdown', () => {
+ expect(findCommitsSection().exists()).toBe(false);
+ });
+ });
+
+ describe('when the commit search returns an error (other than a 404)', () => {
+ beforeEach(() => {
+ commitApiCallSpy = jest.fn().mockReturnValue([500]);
+
+ createComponent();
+
+ updateQuery('abcd1234');
+
+ return waitForRequests();
+ });
+
+ it('renders the commits section in the dropdown', () => {
+ expect(findCommitsSection().exists()).toBe(true);
+ });
+
+ it("renders an error message in the commits section's body", () => {
+ expect(commitsSectionContainsErrorMessage()).toBe(true);
+ });
+ });
+ });
+
+ describe('selection', () => {
+ beforeEach(() => {
+ createComponent();
+
+ updateQuery(fixtures.commit.short_id);
+
+ return waitForRequests();
+ });
+
+ it('renders a checkmark by the selected item', () => {
+ expect(findFirstBranchDropdownItem().find(GlIcon).element).toHaveClass(
+ 'gl-visibility-hidden',
+ );
+
+ selectFirstBranch();
+
+ return localVue.nextTick().then(() => {
+ expect(findFirstBranchDropdownItem().find(GlIcon).element).not.toHaveClass(
+ 'gl-visibility-hidden',
+ );
+ });
+ });
+
+ describe('when a branch is seleceted', () => {
+ it("displays the branch name in the dropdown's button", () => {
+ expect(findButtonContent().text()).toBe(DEFAULT_I18N.noRefSelected);
+
+ selectFirstBranch();
+
+ return localVue.nextTick().then(() => {
+ expect(findButtonContent().text()).toBe(fixtures.branches[0].name);
+ });
+ });
+
+ it("updates the v-model binding with the branch's name", () => {
+ expect(wrapper.vm.value).toEqual('');
+
+ selectFirstBranch();
+
+ expect(wrapper.vm.value).toEqual(fixtures.branches[0].name);
+ });
+ });
+
+ describe('when a tag is seleceted', () => {
+ it("displays the tag name in the dropdown's button", () => {
+ expect(findButtonContent().text()).toBe(DEFAULT_I18N.noRefSelected);
+
+ selectFirstTag();
+
+ return localVue.nextTick().then(() => {
+ expect(findButtonContent().text()).toBe(fixtures.tags[0].name);
+ });
+ });
+
+ it("updates the v-model binding with the tag's name", () => {
+ expect(wrapper.vm.value).toEqual('');
+
+ selectFirstTag();
+
+ expect(wrapper.vm.value).toEqual(fixtures.tags[0].name);
+ });
+ });
+
+ describe('when a commit is selected', () => {
+ it("displays the full SHA in the dropdown's button", () => {
+ expect(findButtonContent().text()).toBe(DEFAULT_I18N.noRefSelected);
+
+ selectFirstCommit();
+
+ return localVue.nextTick().then(() => {
+ expect(findButtonContent().text()).toBe(fixtures.commit.id);
+ });
+ });
+
+ it("updates the v-model binding with the commit's full SHA", () => {
+ expect(wrapper.vm.value).toEqual('');
+
+ selectFirstCommit();
+
+ expect(wrapper.vm.value).toEqual(fixtures.commit.id);
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/ref/stores/actions_spec.js b/spec/frontend/ref/stores/actions_spec.js
new file mode 100644
index 00000000000..32966354c95
--- /dev/null
+++ b/spec/frontend/ref/stores/actions_spec.js
@@ -0,0 +1,180 @@
+import testAction from 'helpers/vuex_action_helper';
+import createState from '~/ref/stores/state';
+import * as actions from '~/ref/stores/actions';
+import * as types from '~/ref/stores/mutation_types';
+
+let mockBranchesReturnValue;
+let mockTagsReturnValue;
+let mockCommitReturnValue;
+
+jest.mock('~/api', () => ({
+ // `__esModule: true` is required when mocking modules with default exports:
+ // https://jestjs.io/docs/en/jest-object#jestmockmodulename-factory-options
+ __esModule: true,
+ default: {
+ branches: () => mockBranchesReturnValue,
+ tags: () => mockTagsReturnValue,
+ commit: () => mockCommitReturnValue,
+ },
+}));
+
+describe('Ref selector Vuex store actions', () => {
+ let state;
+
+ beforeEach(() => {
+ state = createState();
+ });
+
+ describe('setProjectId', () => {
+ it(`commits ${types.SET_PROJECT_ID} with the new project ID`, () => {
+ const projectId = '4';
+ testAction(actions.setProjectId, projectId, state, [
+ { type: types.SET_PROJECT_ID, payload: projectId },
+ ]);
+ });
+ });
+
+ describe('setSelectedRef', () => {
+ it(`commits ${types.SET_SELECTED_REF} with the new selected ref name`, () => {
+ const selectedRef = 'v1.2.3';
+ testAction(actions.setSelectedRef, selectedRef, state, [
+ { type: types.SET_SELECTED_REF, payload: selectedRef },
+ ]);
+ });
+ });
+
+ describe('search', () => {
+ it(`commits ${types.SET_QUERY} with the new search query`, () => {
+ const query = 'hello';
+ testAction(
+ actions.search,
+ query,
+ state,
+ [{ type: types.SET_QUERY, payload: query }],
+ [{ type: 'searchBranches' }, { type: 'searchTags' }, { type: 'searchCommits' }],
+ );
+ });
+ });
+
+ describe('searchBranches', () => {
+ describe('when the search is successful', () => {
+ const branchesApiResponse = { data: [{ name: 'my-feature-branch' }] };
+
+ beforeEach(() => {
+ mockBranchesReturnValue = Promise.resolve(branchesApiResponse);
+ });
+
+ it(`commits ${types.REQUEST_START}, ${types.RECEIVE_BRANCHES_SUCCESS} with the response from the API, and ${types.REQUEST_FINISH}`, () => {
+ return testAction(actions.searchBranches, undefined, state, [
+ { type: types.REQUEST_START },
+ { type: types.RECEIVE_BRANCHES_SUCCESS, payload: branchesApiResponse },
+ { type: types.REQUEST_FINISH },
+ ]);
+ });
+ });
+
+ describe('when the search fails', () => {
+ const error = new Error('Something went wrong!');
+
+ beforeEach(() => {
+ mockBranchesReturnValue = Promise.reject(error);
+ });
+
+ it(`commits ${types.REQUEST_START}, ${types.RECEIVE_BRANCHES_ERROR} with the error object, and ${types.REQUEST_FINISH}`, () => {
+ return testAction(actions.searchBranches, undefined, state, [
+ { type: types.REQUEST_START },
+ { type: types.RECEIVE_BRANCHES_ERROR, payload: error },
+ { type: types.REQUEST_FINISH },
+ ]);
+ });
+ });
+ });
+
+ describe('searchTags', () => {
+ describe('when the search is successful', () => {
+ const tagsApiResponse = { data: [{ name: 'v1.2.3' }] };
+
+ beforeEach(() => {
+ mockTagsReturnValue = Promise.resolve(tagsApiResponse);
+ });
+
+ it(`commits ${types.REQUEST_START}, ${types.RECEIVE_TAGS_SUCCESS} with the response from the API, and ${types.REQUEST_FINISH}`, () => {
+ return testAction(actions.searchTags, undefined, state, [
+ { type: types.REQUEST_START },
+ { type: types.RECEIVE_TAGS_SUCCESS, payload: tagsApiResponse },
+ { type: types.REQUEST_FINISH },
+ ]);
+ });
+ });
+
+ describe('when the search fails', () => {
+ const error = new Error('Something went wrong!');
+
+ beforeEach(() => {
+ mockTagsReturnValue = Promise.reject(error);
+ });
+
+ it(`commits ${types.REQUEST_START}, ${types.RECEIVE_TAGS_ERROR} with the error object, and ${types.REQUEST_FINISH}`, () => {
+ return testAction(actions.searchTags, undefined, state, [
+ { type: types.REQUEST_START },
+ { type: types.RECEIVE_TAGS_ERROR, payload: error },
+ { type: types.REQUEST_FINISH },
+ ]);
+ });
+ });
+ });
+
+ describe('searchCommits', () => {
+ describe('when the search query potentially matches a commit SHA', () => {
+ beforeEach(() => {
+ state.isQueryPossiblyASha = true;
+ });
+
+ describe('when the search is successful', () => {
+ const commitApiResponse = { data: [{ id: 'abcd1234' }] };
+
+ beforeEach(() => {
+ mockCommitReturnValue = Promise.resolve(commitApiResponse);
+ });
+
+ it(`commits ${types.REQUEST_START}, ${types.RECEIVE_COMMITS_SUCCESS} with the response from the API, and ${types.REQUEST_FINISH}`, () => {
+ return testAction(actions.searchCommits, undefined, state, [
+ { type: types.REQUEST_START },
+ { type: types.RECEIVE_COMMITS_SUCCESS, payload: commitApiResponse },
+ { type: types.REQUEST_FINISH },
+ ]);
+ });
+ });
+
+ describe('when the search fails', () => {
+ const error = new Error('Something went wrong!');
+
+ beforeEach(() => {
+ mockCommitReturnValue = Promise.reject(error);
+ });
+
+ describe('when the search query might match a commit SHA', () => {
+ it(`commits ${types.REQUEST_START}, ${types.RECEIVE_COMMITS_ERROR} with the error object, and ${types.REQUEST_FINISH}`, () => {
+ return testAction(actions.searchCommits, undefined, state, [
+ { type: types.REQUEST_START },
+ { type: types.RECEIVE_COMMITS_ERROR, payload: error },
+ { type: types.REQUEST_FINISH },
+ ]);
+ });
+ });
+ });
+ });
+
+ describe('when the search query will not match a commit SHA', () => {
+ beforeEach(() => {
+ state.isQueryPossiblyASha = false;
+ });
+
+ it(`commits ${types.RESET_COMMIT_MATCHES}`, () => {
+ return testAction(actions.searchCommits, undefined, state, [
+ { type: types.RESET_COMMIT_MATCHES },
+ ]);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/ref/stores/getters_spec.js b/spec/frontend/ref/stores/getters_spec.js
new file mode 100644
index 00000000000..49d74e5b9e4
--- /dev/null
+++ b/spec/frontend/ref/stores/getters_spec.js
@@ -0,0 +1,36 @@
+import * as getters from '~/ref/stores/getters';
+
+describe('Ref selector Vuex store getters', () => {
+ describe('isQueryPossiblyASha', () => {
+ it.each`
+ query | isPossiblyASha
+ ${'abcd'} | ${true}
+ ${'ABCD'} | ${true}
+ ${'0123456789abcdef0123456789abcdef01234567'} | ${true}
+ ${'0123456789abcdef0123456789abcdef012345678'} | ${false}
+ ${'abc'} | ${false}
+ ${'ghij'} | ${false}
+ ${' abcd'} | ${false}
+ ${''} | ${false}
+ ${null} | ${false}
+ ${undefined} | ${false}
+ `(
+ 'returns true when the query potentially refers to a commit SHA',
+ ({ query, isPossiblyASha }) => {
+ expect(getters.isQueryPossiblyASha({ query })).toBe(isPossiblyASha);
+ },
+ );
+ });
+
+ describe('isLoading', () => {
+ it.each`
+ requestCount | isLoading
+ ${2} | ${true}
+ ${1} | ${true}
+ ${0} | ${false}
+ ${-1} | ${false}
+ `('returns true when at least one request is in progress', ({ requestCount, isLoading }) => {
+ expect(getters.isLoading({ requestCount })).toBe(isLoading);
+ });
+ });
+});
diff --git a/spec/frontend/ref/stores/mutations_spec.js b/spec/frontend/ref/stores/mutations_spec.js
new file mode 100644
index 00000000000..78117436c33
--- /dev/null
+++ b/spec/frontend/ref/stores/mutations_spec.js
@@ -0,0 +1,274 @@
+import createState from '~/ref/stores/state';
+import mutations from '~/ref/stores/mutations';
+import * as types from '~/ref/stores/mutation_types';
+import { X_TOTAL_HEADER } from '~/ref/constants';
+
+describe('Ref selector Vuex store mutations', () => {
+ let state;
+
+ beforeEach(() => {
+ state = createState();
+ });
+
+ describe('initial state', () => {
+ it('is created with the correct structure and initial values', () => {
+ expect(state).toEqual({
+ projectId: null,
+
+ query: '',
+ matches: {
+ branches: {
+ list: [],
+ totalCount: 0,
+ error: null,
+ },
+ tags: {
+ list: [],
+ totalCount: 0,
+ error: null,
+ },
+ commits: {
+ list: [],
+ totalCount: 0,
+ error: null,
+ },
+ },
+ selectedRef: null,
+ requestCount: 0,
+ });
+ });
+ });
+
+ describe(`${types.SET_PROJECT_ID}`, () => {
+ it('updates the project ID', () => {
+ const newProjectId = '4';
+ mutations[types.SET_PROJECT_ID](state, newProjectId);
+
+ expect(state.projectId).toBe(newProjectId);
+ });
+ });
+
+ describe(`${types.SET_SELECTED_REF}`, () => {
+ it('updates the selected ref', () => {
+ const newSelectedRef = 'my-feature-branch';
+ mutations[types.SET_SELECTED_REF](state, newSelectedRef);
+
+ expect(state.selectedRef).toBe(newSelectedRef);
+ });
+ });
+
+ describe(`${types.SET_QUERY}`, () => {
+ it('updates the search query', () => {
+ const newQuery = 'hello';
+ mutations[types.SET_QUERY](state, newQuery);
+
+ expect(state.query).toBe(newQuery);
+ });
+ });
+
+ describe(`${types.REQUEST_START}`, () => {
+ it('increments requestCount by 1', () => {
+ mutations[types.REQUEST_START](state);
+ expect(state.requestCount).toBe(1);
+
+ mutations[types.REQUEST_START](state);
+ expect(state.requestCount).toBe(2);
+
+ mutations[types.REQUEST_START](state);
+ expect(state.requestCount).toBe(3);
+ });
+ });
+
+ describe(`${types.REQUEST_FINISH}`, () => {
+ it('decrements requestCount by 1', () => {
+ state.requestCount = 3;
+
+ mutations[types.REQUEST_FINISH](state);
+ expect(state.requestCount).toBe(2);
+
+ mutations[types.REQUEST_FINISH](state);
+ expect(state.requestCount).toBe(1);
+
+ mutations[types.REQUEST_FINISH](state);
+ expect(state.requestCount).toBe(0);
+ });
+ });
+
+ describe(`${types.RECEIVE_BRANCHES_SUCCESS}`, () => {
+ it('updates state.matches.branches based on the provided API response', () => {
+ const response = {
+ data: [
+ {
+ name: 'master',
+ default: true,
+
+ // everything except "name" and "default" should be stripped
+ merged: false,
+ protected: true,
+ },
+ {
+ name: 'my-feature-branch',
+ default: false,
+ },
+ ],
+ headers: {
+ [X_TOTAL_HEADER]: 37,
+ },
+ };
+
+ mutations[types.RECEIVE_BRANCHES_SUCCESS](state, response);
+
+ expect(state.matches.branches).toEqual({
+ list: [
+ {
+ name: 'master',
+ default: true,
+ },
+ {
+ name: 'my-feature-branch',
+ default: false,
+ },
+ ],
+ totalCount: 37,
+ error: null,
+ });
+ });
+ });
+
+ describe(`${types.RECEIVE_BRANCHES_ERROR}`, () => {
+ it('updates state.matches.branches to an empty state with the error object', () => {
+ const error = new Error('Something went wrong!');
+
+ state.matches.branches = {
+ list: [{ name: 'my-feature-branch' }],
+ totalCount: 1,
+ error: null,
+ };
+
+ mutations[types.RECEIVE_BRANCHES_ERROR](state, error);
+
+ expect(state.matches.branches).toEqual({
+ list: [],
+ totalCount: 0,
+ error,
+ });
+ });
+ });
+
+ describe(`${types.RECEIVE_REQUEST_TAGS_SUCCESS}`, () => {
+ it('updates state.matches.tags based on the provided API response', () => {
+ const response = {
+ data: [
+ {
+ name: 'v1.2',
+
+ // everything except "name" should be stripped
+ target: '2695effb5807a22ff3d138d593fd856244e155e7',
+ },
+ ],
+ headers: {
+ [X_TOTAL_HEADER]: 23,
+ },
+ };
+
+ mutations[types.RECEIVE_TAGS_SUCCESS](state, response);
+
+ expect(state.matches.tags).toEqual({
+ list: [
+ {
+ name: 'v1.2',
+ },
+ ],
+ totalCount: 23,
+ error: null,
+ });
+ });
+ });
+
+ describe(`${types.RECEIVE_TAGS_ERROR}`, () => {
+ it('updates state.matches.tags to an empty state with the error object', () => {
+ const error = new Error('Something went wrong!');
+
+ state.matches.tags = {
+ list: [{ name: 'v1.2' }],
+ totalCount: 1,
+ error: null,
+ };
+
+ mutations[types.RECEIVE_TAGS_ERROR](state, error);
+
+ expect(state.matches.tags).toEqual({
+ list: [],
+ totalCount: 0,
+ error,
+ });
+ });
+ });
+
+ describe(`${types.RECEIVE_COMMITS_SUCCESS}`, () => {
+ it('updates state.matches.commits based on the provided API response', () => {
+ const response = {
+ data: {
+ id: '2695effb5807a22ff3d138d593fd856244e155e7',
+ short_id: '2695effb580',
+ title: 'Initial commit',
+
+ // everything except "id", "short_id", and "title" should be stripped
+ author_name: 'Example User',
+ },
+ };
+
+ mutations[types.RECEIVE_COMMITS_SUCCESS](state, response);
+
+ expect(state.matches.commits).toEqual({
+ list: [
+ {
+ name: '2695effb580',
+ value: '2695effb5807a22ff3d138d593fd856244e155e7',
+ subtitle: 'Initial commit',
+ },
+ ],
+ totalCount: 1,
+ error: null,
+ });
+ });
+ });
+
+ describe(`${types.RECEIVE_COMMITS_ERROR}`, () => {
+ it('updates state.matches.commits to an empty state with the error object', () => {
+ const error = new Error('Something went wrong!');
+
+ state.matches.commits = {
+ list: [{ name: 'abcd0123' }],
+ totalCount: 1,
+ error: null,
+ };
+
+ mutations[types.RECEIVE_COMMITS_ERROR](state, error);
+
+ expect(state.matches.commits).toEqual({
+ list: [],
+ totalCount: 0,
+ error,
+ });
+ });
+ });
+
+ describe(`${types.RESET_COMMIT_MATCHES}`, () => {
+ it('resets the commit results back to their original (empty) state', () => {
+ state.matches.commits = {
+ list: [{ name: 'abcd0123' }],
+ totalCount: 1,
+ error: null,
+ };
+
+ mutations[types.RESET_COMMIT_MATCHES](state);
+
+ expect(state.matches.commits).toEqual({
+ list: [],
+ totalCount: 0,
+ error: null,
+ });
+ });
+ });
+});
diff --git a/spec/frontend/registry/explorer/components/delete_button_spec.js b/spec/frontend/registry/explorer/components/delete_button_spec.js
new file mode 100644
index 00000000000..bb0fe81117a
--- /dev/null
+++ b/spec/frontend/registry/explorer/components/delete_button_spec.js
@@ -0,0 +1,73 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlButton } from '@gitlab/ui';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import component from '~/registry/explorer/components/delete_button.vue';
+
+describe('delete_button', () => {
+ let wrapper;
+
+ const defaultProps = {
+ title: 'Foo title',
+ tooltipTitle: 'Bar tooltipTitle',
+ };
+
+ const findButton = () => wrapper.find(GlButton);
+
+ const mountComponent = props => {
+ wrapper = shallowMount(component, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('tooltip', () => {
+ it('the title is controlled by tooltipTitle prop', () => {
+ mountComponent();
+ const tooltip = getBinding(wrapper.element, 'gl-tooltip');
+ expect(tooltip).toBeDefined();
+ expect(tooltip.value.title).toBe(defaultProps.tooltipTitle);
+ });
+
+ it('is disabled when tooltipTitle is disabled', () => {
+ mountComponent({ tooltipDisabled: true });
+ const tooltip = getBinding(wrapper.element, 'gl-tooltip');
+ expect(tooltip.value.disabled).toBe(true);
+ });
+
+ describe('button', () => {
+ it('exists', () => {
+ mountComponent();
+ expect(findButton().exists()).toBe(true);
+ });
+
+ it('has the correct props/attributes bound', () => {
+ mountComponent({ disabled: true });
+ expect(findButton().attributes()).toMatchObject({
+ 'aria-label': 'Foo title',
+ category: 'secondary',
+ icon: 'remove',
+ title: 'Foo title',
+ variant: 'danger',
+ disabled: 'true',
+ });
+ });
+
+ it('emits a delete event', () => {
+ mountComponent();
+ expect(wrapper.emitted('delete')).toEqual(undefined);
+ findButton().vm.$emit('click');
+ expect(wrapper.emitted('delete')).toEqual([[]]);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/registry/explorer/components/details_page/details_row_spec.js b/spec/frontend/registry/explorer/components/details_page/details_row_spec.js
new file mode 100644
index 00000000000..95b8e18d677
--- /dev/null
+++ b/spec/frontend/registry/explorer/components/details_page/details_row_spec.js
@@ -0,0 +1,43 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlIcon } from '@gitlab/ui';
+import component from '~/registry/explorer/components/details_page/details_row.vue';
+
+describe('DetailsRow', () => {
+ let wrapper;
+
+ const findIcon = () => wrapper.find(GlIcon);
+ const findDefaultSlot = () => wrapper.find('[data-testid="default-slot"]');
+
+ const mountComponent = () => {
+ wrapper = shallowMount(component, {
+ propsData: {
+ icon: 'clock',
+ },
+ slots: {
+ default: '<div data-testid="default-slot"></div>',
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('contains an icon', () => {
+ mountComponent();
+ expect(findIcon().exists()).toBe(true);
+ });
+
+ it('icon has the correct props', () => {
+ mountComponent();
+ expect(findIcon().props()).toMatchObject({
+ name: 'clock',
+ });
+ });
+
+ it('has a default slot', () => {
+ mountComponent();
+ expect(findDefaultSlot().exists()).toBe(true);
+ });
+});
diff --git a/spec/frontend/registry/explorer/components/details_page/empty_tags_state.js b/spec/frontend/registry/explorer/components/details_page/empty_tags_state_spec.js
index da80c75a26a..09afd9d2d84 100644
--- a/spec/frontend/registry/explorer/components/details_page/empty_tags_state.js
+++ b/spec/frontend/registry/explorer/components/details_page/empty_tags_state_spec.js
@@ -29,7 +29,7 @@ describe('EmptyTagsState component', () => {
it('contains gl-empty-state', () => {
mountComponent();
- expect(findEmptyState().exist()).toBe(true);
+ expect(findEmptyState().exists()).toBe(true);
});
it('has the correct props', () => {
diff --git a/spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js b/spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js
new file mode 100644
index 00000000000..9e876d6d8a3
--- /dev/null
+++ b/spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js
@@ -0,0 +1,330 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlFormCheckbox, GlSprintf, GlIcon } from '@gitlab/ui';
+
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import component from '~/registry/explorer/components/details_page/tags_list_row.vue';
+import DeleteButton from '~/registry/explorer/components/delete_button.vue';
+import DetailsRow from '~/registry/explorer/components/details_page/details_row.vue';
+import {
+ REMOVE_TAG_BUTTON_TITLE,
+ REMOVE_TAG_BUTTON_DISABLE_TOOLTIP,
+ MISSING_MANIFEST_WARNING_TOOLTIP,
+ NOT_AVAILABLE_TEXT,
+ NOT_AVAILABLE_SIZE,
+} from '~/registry/explorer/constants/index';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+
+import { tagsListResponse } from '../../mock_data';
+import { ListItem } from '../../stubs';
+
+describe('tags list row', () => {
+ let wrapper;
+ const [tag] = [...tagsListResponse.data];
+
+ const defaultProps = { tag, isDesktop: true, index: 0 };
+
+ const findCheckbox = () => wrapper.find(GlFormCheckbox);
+ const findName = () => wrapper.find('[data-testid="name"]');
+ const findSize = () => wrapper.find('[data-testid="size"]');
+ const findTime = () => wrapper.find('[data-testid="time"]');
+ const findShortRevision = () => wrapper.find('[data-testid="digest"]');
+ const findClipboardButton = () => wrapper.find(ClipboardButton);
+ const findDeleteButton = () => wrapper.find(DeleteButton);
+ const findTimeAgoTooltip = () => wrapper.find(TimeAgoTooltip);
+ const findDetailsRows = () => wrapper.findAll(DetailsRow);
+ const findPublishedDateDetail = () => wrapper.find('[data-testid="published-date-detail"]');
+ const findManifestDetail = () => wrapper.find('[data-testid="manifest-detail"]');
+ const findConfigurationDetail = () => wrapper.find('[data-testid="configuration-detail"]');
+ const findWarningIcon = () => wrapper.find(GlIcon);
+
+ const mountComponent = (propsData = defaultProps) => {
+ wrapper = shallowMount(component, {
+ stubs: {
+ GlSprintf,
+ ListItem,
+ DetailsRow,
+ },
+ propsData,
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('checkbox', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findCheckbox().exists()).toBe(true);
+ });
+
+ it("does not exist when the row can't be deleted", () => {
+ const customTag = { ...tag, destroy_path: '' };
+
+ mountComponent({ ...defaultProps, tag: customTag });
+
+ expect(findCheckbox().exists()).toBe(false);
+ });
+
+ it('is disabled when the digest is missing', () => {
+ mountComponent({ tag: { ...tag, digest: null } });
+ expect(findCheckbox().attributes('disabled')).toBe('true');
+ });
+
+ it('is wired to the selected prop', () => {
+ mountComponent({ ...defaultProps, selected: true });
+
+ expect(findCheckbox().attributes('checked')).toBe('true');
+ });
+
+ it('when changed emit a select event', () => {
+ mountComponent();
+
+ findCheckbox().vm.$emit('change');
+
+ expect(wrapper.emitted('select')).toEqual([[]]);
+ });
+ });
+
+ describe('tag name', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findName().exists()).toBe(true);
+ });
+
+ it('has the correct text', () => {
+ mountComponent();
+
+ expect(findName().text()).toBe(tag.name);
+ });
+
+ it('has a tooltip', () => {
+ mountComponent();
+
+ const tooltip = getBinding(findName().element, 'gl-tooltip');
+
+ expect(tooltip.value.title).toBe(tag.name);
+ });
+
+ it('on mobile has mw-s class', () => {
+ mountComponent({ ...defaultProps, isDesktop: false });
+
+ expect(findName().classes('mw-s')).toBe(true);
+ });
+ });
+
+ describe('clipboard button', () => {
+ it('exist if tag.location exist', () => {
+ mountComponent();
+
+ expect(findClipboardButton().exists()).toBe(true);
+ });
+
+ it('is hidden if tag does not have a location', () => {
+ mountComponent({ ...defaultProps, tag: { ...tag, location: null } });
+
+ expect(findClipboardButton().exists()).toBe(false);
+ });
+
+ it('has the correct props/attributes', () => {
+ mountComponent();
+
+ expect(findClipboardButton().attributes()).toMatchObject({
+ text: 'location',
+ title: 'location',
+ });
+ });
+ });
+
+ describe('warning icon', () => {
+ it('is normally hidden', () => {
+ mountComponent();
+
+ expect(findWarningIcon().exists()).toBe(false);
+ });
+
+ it('is shown when the tag is broken', () => {
+ mountComponent({ tag: { ...tag, digest: null } });
+
+ expect(findWarningIcon().exists()).toBe(true);
+ });
+
+ it('has an appropriate tooltip', () => {
+ mountComponent({ tag: { ...tag, digest: null } });
+
+ const tooltip = getBinding(findWarningIcon().element, 'gl-tooltip');
+ expect(tooltip.value.title).toBe(MISSING_MANIFEST_WARNING_TOOLTIP);
+ });
+ });
+
+ describe('size', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findSize().exists()).toBe(true);
+ });
+
+ it('contains the total_size and layers', () => {
+ mountComponent({ ...defaultProps, tag: { ...tag, total_size: 1024 } });
+
+ expect(findSize().text()).toMatchInterpolatedText('1.00 KiB · 10 layers');
+ });
+
+ it('when total_size is missing', () => {
+ mountComponent();
+
+ expect(findSize().text()).toMatchInterpolatedText(`${NOT_AVAILABLE_SIZE} · 10 layers`);
+ });
+
+ it('when layers are missing', () => {
+ mountComponent({ ...defaultProps, tag: { ...tag, total_size: 1024, layers: null } });
+
+ expect(findSize().text()).toMatchInterpolatedText('1.00 KiB');
+ });
+
+ it('when there is 1 layer', () => {
+ mountComponent({ ...defaultProps, tag: { ...tag, layers: 1 } });
+
+ expect(findSize().text()).toMatchInterpolatedText(`${NOT_AVAILABLE_SIZE} · 1 layer`);
+ });
+ });
+
+ describe('time', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findTime().exists()).toBe(true);
+ });
+
+ it('has the correct text', () => {
+ mountComponent();
+
+ expect(findTime().text()).toBe('Published');
+ });
+
+ it('contains time_ago_tooltip component', () => {
+ mountComponent();
+
+ expect(findTimeAgoTooltip().exists()).toBe(true);
+ });
+
+ it('pass the correct props to time ago tooltip', () => {
+ mountComponent();
+
+ expect(findTimeAgoTooltip().attributes()).toMatchObject({ time: tag.created_at });
+ });
+ });
+
+ describe('digest', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findShortRevision().exists()).toBe(true);
+ });
+
+ it('has the correct text', () => {
+ mountComponent();
+
+ expect(findShortRevision().text()).toMatchInterpolatedText('Digest: 1ab51d5');
+ });
+
+ it(`displays ${NOT_AVAILABLE_TEXT} when digest is missing`, () => {
+ mountComponent({ tag: { ...tag, digest: null } });
+
+ expect(findShortRevision().text()).toMatchInterpolatedText(`Digest: ${NOT_AVAILABLE_TEXT}`);
+ });
+ });
+
+ describe('delete button', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findDeleteButton().exists()).toBe(true);
+ });
+
+ it('has the correct props/attributes', () => {
+ mountComponent();
+
+ expect(findDeleteButton().attributes()).toMatchObject({
+ title: REMOVE_TAG_BUTTON_TITLE,
+ tooltiptitle: REMOVE_TAG_BUTTON_DISABLE_TOOLTIP,
+ tooltipdisabled: 'true',
+ });
+ });
+
+ it.each`
+ destroy_path | digest
+ ${'foo'} | ${null}
+ ${null} | ${'foo'}
+ ${null} | ${null}
+ `(
+ 'is disabled when destroy_path is $destroy_path and digest is $digest',
+ ({ destroy_path, digest }) => {
+ mountComponent({ ...defaultProps, tag: { ...tag, destroy_path, digest } });
+
+ expect(findDeleteButton().attributes('disabled')).toBe('true');
+ },
+ );
+
+ it('delete event emits delete', () => {
+ mountComponent();
+
+ findDeleteButton().vm.$emit('delete');
+
+ expect(wrapper.emitted('delete')).toEqual([[]]);
+ });
+ });
+
+ describe('details rows', () => {
+ describe('when the tag has a digest', () => {
+ beforeEach(() => {
+ mountComponent();
+
+ return wrapper.vm.$nextTick();
+ });
+
+ it('has 3 details rows', () => {
+ expect(findDetailsRows().length).toBe(3);
+ });
+
+ describe.each`
+ name | finderFunction | text | icon | clipboard
+ ${'published date detail'} | ${findPublishedDateDetail} | ${'Published to the bar image repository at 10:23 GMT+0000 on 2020-06-29'} | ${'clock'} | ${false}
+ ${'manifest detail'} | ${findManifestDetail} | ${'Manifest digest: sha256:1ab51d519f574b636ae7788051c60239334ae8622a9fd82a0cf7bae7786dfd5c'} | ${'log'} | ${true}
+ ${'configuration detail'} | ${findConfigurationDetail} | ${'Configuration digest: sha256:b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43'} | ${'cloud-gear'} | ${true}
+ `('$name details row', ({ finderFunction, text, icon, clipboard }) => {
+ it(`has ${text} as text`, () => {
+ expect(finderFunction().text()).toMatchInterpolatedText(text);
+ });
+
+ it(`has the ${icon} icon`, () => {
+ expect(finderFunction().props('icon')).toBe(icon);
+ });
+
+ it(`is ${clipboard} that clipboard button exist`, () => {
+ expect(
+ finderFunction()
+ .find(ClipboardButton)
+ .exists(),
+ ).toBe(clipboard);
+ });
+ });
+ });
+
+ describe('when the tag does not have a digest', () => {
+ it('hides the details rows', async () => {
+ mountComponent({ tag: { ...tag, digest: null } });
+
+ await wrapper.vm.$nextTick();
+ expect(findDetailsRows().length).toBe(0);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/registry/explorer/components/details_page/tags_list_spec.js b/spec/frontend/registry/explorer/components/details_page/tags_list_spec.js
new file mode 100644
index 00000000000..1f560753476
--- /dev/null
+++ b/spec/frontend/registry/explorer/components/details_page/tags_list_spec.js
@@ -0,0 +1,146 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlButton } from '@gitlab/ui';
+import component from '~/registry/explorer/components/details_page/tags_list.vue';
+import TagsListRow from '~/registry/explorer/components/details_page/tags_list_row.vue';
+import { TAGS_LIST_TITLE, REMOVE_TAGS_BUTTON_TITLE } from '~/registry/explorer/constants/index';
+import { tagsListResponse } from '../../mock_data';
+
+describe('Tags List', () => {
+ let wrapper;
+ const tags = [...tagsListResponse.data];
+ const readOnlyTags = tags.map(t => ({ ...t, destroy_path: undefined }));
+
+ const findTagsListRow = () => wrapper.findAll(TagsListRow);
+ const findDeleteButton = () => wrapper.find(GlButton);
+ const findListTitle = () => wrapper.find('[data-testid="list-title"]');
+
+ const mountComponent = (propsData = { tags, isDesktop: true }) => {
+ wrapper = shallowMount(component, {
+ propsData,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('List title', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findListTitle().exists()).toBe(true);
+ });
+
+ it('has the correct text', () => {
+ mountComponent();
+
+ expect(findListTitle().text()).toBe(TAGS_LIST_TITLE);
+ });
+ });
+
+ describe('delete button', () => {
+ it.each`
+ inputTags | isDesktop | isVisible
+ ${tags} | ${true} | ${true}
+ ${tags} | ${false} | ${false}
+ ${readOnlyTags} | ${true} | ${false}
+ ${readOnlyTags} | ${false} | ${false}
+ `(
+ 'is $isVisible that delete button exists when tags is $inputTags and isDesktop is $isDesktop',
+ ({ inputTags, isDesktop, isVisible }) => {
+ mountComponent({ tags: inputTags, isDesktop });
+
+ expect(findDeleteButton().exists()).toBe(isVisible);
+ },
+ );
+
+ it('has the correct text', () => {
+ mountComponent();
+
+ expect(findDeleteButton().text()).toBe(REMOVE_TAGS_BUTTON_TITLE);
+ });
+
+ it('has the correct props', () => {
+ mountComponent();
+
+ expect(findDeleteButton().attributes()).toMatchObject({
+ category: 'secondary',
+ variant: 'danger',
+ });
+ });
+
+ it('is disabled when no item is selected', () => {
+ mountComponent();
+
+ expect(findDeleteButton().attributes('disabled')).toBe('true');
+ });
+
+ it('is enabled when at least one item is selected', async () => {
+ mountComponent();
+ findTagsListRow()
+ .at(0)
+ .vm.$emit('select');
+ await wrapper.vm.$nextTick();
+ expect(findDeleteButton().attributes('disabled')).toBe(undefined);
+ });
+
+ it('click event emits a deleted event with selected items', () => {
+ mountComponent();
+ findTagsListRow()
+ .at(0)
+ .vm.$emit('select');
+
+ findDeleteButton().vm.$emit('click');
+ expect(wrapper.emitted('delete')).toEqual([[{ centos6: true }]]);
+ });
+ });
+
+ describe('list rows', () => {
+ it('one row exist for each tag', () => {
+ mountComponent();
+
+ expect(findTagsListRow()).toHaveLength(tags.length);
+ });
+
+ it('the correct props are bound to it', () => {
+ mountComponent();
+
+ const rows = findTagsListRow();
+
+ expect(rows.at(0).attributes()).toMatchObject({
+ first: 'true',
+ isdesktop: 'true',
+ });
+
+ // The list has only two tags and for some reasons .at(-1) does not work
+ expect(rows.at(1).attributes()).toMatchObject({
+ last: 'true',
+ isdesktop: 'true',
+ });
+ });
+
+ describe('events', () => {
+ it('select event update the selected items', async () => {
+ mountComponent();
+ findTagsListRow()
+ .at(0)
+ .vm.$emit('select');
+ await wrapper.vm.$nextTick();
+ expect(
+ findTagsListRow()
+ .at(0)
+ .attributes('selected'),
+ ).toBe('true');
+ });
+
+ it('delete event emit a delete event', () => {
+ mountComponent();
+ findTagsListRow()
+ .at(0)
+ .vm.$emit('delete');
+ expect(wrapper.emitted('delete')).toEqual([[{ centos6: true }]]);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/registry/explorer/components/details_page/tags_table_spec.js b/spec/frontend/registry/explorer/components/details_page/tags_table_spec.js
deleted file mode 100644
index a60a362dcfe..00000000000
--- a/spec/frontend/registry/explorer/components/details_page/tags_table_spec.js
+++ /dev/null
@@ -1,286 +0,0 @@
-import { mount } from '@vue/test-utils';
-import stubChildren from 'helpers/stub_children';
-import component from '~/registry/explorer/components/details_page/tags_table.vue';
-import { tagsListResponse } from '../../mock_data';
-
-describe('tags_table', () => {
- let wrapper;
- const tags = [...tagsListResponse.data];
-
- const findMainCheckbox = () => wrapper.find('[data-testid="mainCheckbox"]');
- const findFirstRowItem = testid => wrapper.find(`[data-testid="${testid}"]`);
- const findBulkDeleteButton = () => wrapper.find('[data-testid="bulkDeleteButton"]');
- const findAllDeleteButtons = () => wrapper.findAll('[data-testid="singleDeleteButton"]');
- const findAllCheckboxes = () => wrapper.findAll('[data-testid="rowCheckbox"]');
- const findCheckedCheckboxes = () => findAllCheckboxes().filter(c => c.attributes('checked'));
- const findFirsTagColumn = () => wrapper.find('.js-tag-column');
- const findFirstTagNameText = () => wrapper.find('[data-testid="rowNameText"]');
-
- const findLoaderSlot = () => wrapper.find('[data-testid="loaderSlot"]');
- const findEmptySlot = () => wrapper.find('[data-testid="emptySlot"]');
-
- const mountComponent = (propsData = { tags, isDesktop: true }) => {
- wrapper = mount(component, {
- stubs: {
- ...stubChildren(component),
- GlTable: false,
- },
- propsData,
- slots: {
- loader: '<div data-testid="loaderSlot"></div>',
- empty: '<div data-testid="emptySlot"></div>',
- },
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- it.each([
- 'rowCheckbox',
- 'rowName',
- 'rowShortRevision',
- 'rowSize',
- 'rowTime',
- 'singleDeleteButton',
- ])('%s exist in the table', element => {
- mountComponent();
-
- expect(findFirstRowItem(element).exists()).toBe(true);
- });
-
- describe('header checkbox', () => {
- it('exists', () => {
- mountComponent();
- expect(findMainCheckbox().exists()).toBe(true);
- });
-
- it('if selected selects all the rows', () => {
- mountComponent();
- findMainCheckbox().vm.$emit('change');
- return wrapper.vm.$nextTick().then(() => {
- expect(findMainCheckbox().attributes('checked')).toBeTruthy();
- expect(findCheckedCheckboxes()).toHaveLength(tags.length);
- });
- });
-
- it('if deselect deselects all the row', () => {
- mountComponent();
- findMainCheckbox().vm.$emit('change');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- expect(findMainCheckbox().attributes('checked')).toBeTruthy();
- findMainCheckbox().vm.$emit('change');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findMainCheckbox().attributes('checked')).toBe(undefined);
- expect(findCheckedCheckboxes()).toHaveLength(0);
- });
- });
- });
-
- describe('row checkbox', () => {
- beforeEach(() => {
- mountComponent();
- });
-
- it('selecting and deselecting the checkbox works as intended', () => {
- findFirstRowItem('rowCheckbox').vm.$emit('change');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- expect(wrapper.vm.selectedItems).toEqual([tags[0].name]);
- expect(findFirstRowItem('rowCheckbox').attributes('checked')).toBeTruthy();
- findFirstRowItem('rowCheckbox').vm.$emit('change');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(wrapper.vm.selectedItems.length).toBe(0);
- expect(findFirstRowItem('rowCheckbox').attributes('checked')).toBe(undefined);
- });
- });
- });
-
- describe('header delete button', () => {
- beforeEach(() => {
- mountComponent();
- });
-
- it('exists', () => {
- expect(findBulkDeleteButton().exists()).toBe(true);
- });
-
- it('is disabled if no item is selected', () => {
- expect(findBulkDeleteButton().attributes('disabled')).toBe('true');
- });
-
- it('is enabled if at least one item is selected', () => {
- expect(findBulkDeleteButton().attributes('disabled')).toBe('true');
- findFirstRowItem('rowCheckbox').vm.$emit('change');
- return wrapper.vm.$nextTick().then(() => {
- expect(findBulkDeleteButton().attributes('disabled')).toBeFalsy();
- });
- });
-
- describe('on click', () => {
- it('when one item is selected', () => {
- findFirstRowItem('rowCheckbox').vm.$emit('change');
- findBulkDeleteButton().vm.$emit('click');
- expect(wrapper.emitted('delete')).toEqual([[['centos6']]]);
- });
-
- it('when multiple items are selected', () => {
- findMainCheckbox().vm.$emit('change');
- findBulkDeleteButton().vm.$emit('click');
-
- expect(wrapper.emitted('delete')).toEqual([[tags.map(t => t.name)]]);
- });
- });
- });
-
- describe('row delete button', () => {
- beforeEach(() => {
- mountComponent();
- });
-
- it('exists', () => {
- expect(
- findAllDeleteButtons()
- .at(0)
- .exists(),
- ).toBe(true);
- });
-
- it('is disabled if the item has no destroy_path', () => {
- expect(
- findAllDeleteButtons()
- .at(1)
- .attributes('disabled'),
- ).toBe('true');
- });
-
- it('on click', () => {
- findAllDeleteButtons()
- .at(0)
- .vm.$emit('click');
-
- expect(wrapper.emitted('delete')).toEqual([[['centos6']]]);
- });
- });
-
- describe('name cell', () => {
- it('tag column has a tooltip with the tag name', () => {
- mountComponent();
- expect(findFirstTagNameText().attributes('title')).toBe(tagsListResponse.data[0].name);
- });
-
- describe('on desktop viewport', () => {
- beforeEach(() => {
- mountComponent();
- });
-
- it('table header has class w-25', () => {
- expect(findFirsTagColumn().classes()).toContain('w-25');
- });
-
- it('tag column has the mw-m class', () => {
- expect(findFirstRowItem('rowName').classes()).toContain('mw-m');
- });
- });
-
- describe('on mobile viewport', () => {
- beforeEach(() => {
- mountComponent({ tags, isDesktop: false });
- });
-
- it('table header does not have class w-25', () => {
- expect(findFirsTagColumn().classes()).not.toContain('w-25');
- });
-
- it('tag column has the gl-justify-content-end class', () => {
- expect(findFirstRowItem('rowName').classes()).toContain('gl-justify-content-end');
- });
- });
- });
-
- describe('last updated cell', () => {
- let timeCell;
-
- beforeEach(() => {
- mountComponent();
- timeCell = findFirstRowItem('rowTime');
- });
-
- it('displays the time in string format', () => {
- expect(timeCell.text()).toBe('2 years ago');
- });
-
- it('has a tooltip timestamp', () => {
- expect(timeCell.attributes('title')).toBe('Sep 19, 2017 1:45pm GMT+0000');
- });
- });
-
- describe('empty state slot', () => {
- describe('when the table is empty', () => {
- beforeEach(() => {
- mountComponent({ tags: [], isDesktop: true });
- });
-
- it('does not show table rows', () => {
- expect(findFirstTagNameText().exists()).toBe(false);
- });
-
- it('has the empty state slot', () => {
- expect(findEmptySlot().exists()).toBe(true);
- });
- });
-
- describe('when the table is not empty', () => {
- beforeEach(() => {
- mountComponent({ tags, isDesktop: true });
- });
-
- it('does show table rows', () => {
- expect(findFirstTagNameText().exists()).toBe(true);
- });
-
- it('does not show the empty state', () => {
- expect(findEmptySlot().exists()).toBe(false);
- });
- });
- });
-
- describe('loader slot', () => {
- describe('when the data is loading', () => {
- beforeEach(() => {
- mountComponent({ isLoading: true, tags });
- });
-
- it('show the loader', () => {
- expect(findLoaderSlot().exists()).toBe(true);
- });
-
- it('does not show the table rows', () => {
- expect(findFirstTagNameText().exists()).toBe(false);
- });
- });
-
- describe('when the data is not loading', () => {
- beforeEach(() => {
- mountComponent({ isLoading: false, tags });
- });
-
- it('does not show the loader', () => {
- expect(findLoaderSlot().exists()).toBe(false);
- });
-
- it('shows the table rows', () => {
- expect(findFirstTagNameText().exists()).toBe(true);
- });
- });
- });
-});
diff --git a/spec/frontend/registry/explorer/components/list_item_spec.js b/spec/frontend/registry/explorer/components/list_item_spec.js
new file mode 100644
index 00000000000..f244627a8c3
--- /dev/null
+++ b/spec/frontend/registry/explorer/components/list_item_spec.js
@@ -0,0 +1,156 @@
+import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import component from '~/registry/explorer/components/list_item.vue';
+
+describe('list item', () => {
+ let wrapper;
+
+ const findLeftActionSlot = () => wrapper.find('[data-testid="left-action"]');
+ const findLeftPrimarySlot = () => wrapper.find('[data-testid="left-primary"]');
+ const findLeftSecondarySlot = () => wrapper.find('[data-testid="left-secondary"]');
+ const findRightPrimarySlot = () => wrapper.find('[data-testid="right-primary"]');
+ const findRightSecondarySlot = () => wrapper.find('[data-testid="right-secondary"]');
+ const findRightActionSlot = () => wrapper.find('[data-testid="right-action"]');
+ const findDetailsSlot = name => wrapper.find(`[data-testid="${name}"]`);
+ const findToggleDetailsButton = () => wrapper.find(GlButton);
+
+ const mountComponent = (propsData, slots) => {
+ wrapper = shallowMount(component, {
+ propsData,
+ slots: {
+ 'left-action': '<div data-testid="left-action" />',
+ 'left-primary': '<div data-testid="left-primary" />',
+ 'left-secondary': '<div data-testid="left-secondary" />',
+ 'right-primary': '<div data-testid="right-primary" />',
+ 'right-secondary': '<div data-testid="right-secondary" />',
+ 'right-action': '<div data-testid="right-action" />',
+ ...slots,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it.each`
+ slotName | finderFunction
+ ${'left-primary'} | ${findLeftPrimarySlot}
+ ${'left-secondary'} | ${findLeftSecondarySlot}
+ ${'right-primary'} | ${findRightPrimarySlot}
+ ${'right-secondary'} | ${findRightSecondarySlot}
+ ${'left-action'} | ${findLeftActionSlot}
+ ${'right-action'} | ${findRightActionSlot}
+ `('has a $slotName slot', ({ finderFunction }) => {
+ mountComponent();
+
+ expect(finderFunction().exists()).toBe(true);
+ });
+
+ describe.each`
+ slotNames
+ ${['details_foo']}
+ ${['details_foo', 'details_bar']}
+ ${['details_foo', 'details_bar', 'details_baz']}
+ `('$slotNames details slots', ({ slotNames }) => {
+ const slotMocks = slotNames.reduce((acc, current) => {
+ acc[current] = `<div data-testid="${current}" />`;
+ return acc;
+ }, {});
+
+ it('are visible when details is shown', async () => {
+ mountComponent({}, slotMocks);
+
+ await wrapper.vm.$nextTick();
+ findToggleDetailsButton().vm.$emit('click');
+
+ await wrapper.vm.$nextTick();
+ slotNames.forEach(name => {
+ expect(findDetailsSlot(name).exists()).toBe(true);
+ });
+ });
+ it('are not visible when details are not shown', () => {
+ mountComponent({}, slotMocks);
+
+ slotNames.forEach(name => {
+ expect(findDetailsSlot(name).exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('details toggle button', () => {
+ it('is visible when at least one details slot exists', async () => {
+ mountComponent({}, { details_foo: '<span></span>' });
+ await wrapper.vm.$nextTick();
+ expect(findToggleDetailsButton().exists()).toBe(true);
+ });
+
+ it('is hidden without details slot', () => {
+ mountComponent();
+ expect(findToggleDetailsButton().exists()).toBe(false);
+ });
+ });
+
+ describe('disabled prop', () => {
+ it('when true applies disabled-content class', () => {
+ mountComponent({ disabled: true });
+
+ expect(wrapper.classes('disabled-content')).toBe(true);
+ });
+
+ it('when false does not apply disabled-content class', () => {
+ mountComponent({ disabled: false });
+
+ expect(wrapper.classes('disabled-content')).toBe(false);
+ });
+ });
+
+ describe('first prop', () => {
+ it('when is true displays a double top border', () => {
+ mountComponent({ first: true });
+
+ expect(wrapper.classes('gl-border-t-2')).toBe(true);
+ });
+
+ it('when is false display a single top border', () => {
+ mountComponent({ first: false });
+
+ expect(wrapper.classes('gl-border-t-1')).toBe(true);
+ });
+ });
+
+ describe('last prop', () => {
+ it('when is true displays a double bottom border', () => {
+ mountComponent({ last: true });
+
+ expect(wrapper.classes('gl-border-b-2')).toBe(true);
+ });
+
+ it('when is false display a single bottom border', () => {
+ mountComponent({ last: false });
+
+ expect(wrapper.classes('gl-border-b-1')).toBe(true);
+ });
+ });
+
+ describe('selected prop', () => {
+ it('when true applies the selected border and background', () => {
+ mountComponent({ selected: true });
+
+ expect(wrapper.classes()).toEqual(
+ expect.arrayContaining(['gl-bg-blue-50', 'gl-border-blue-200']),
+ );
+ expect(wrapper.classes()).toEqual(expect.not.arrayContaining(['gl-border-gray-100']));
+ });
+
+ it('when false applies the default border', () => {
+ mountComponent({ selected: false });
+
+ expect(wrapper.classes()).toEqual(
+ expect.not.arrayContaining(['gl-bg-blue-50', 'gl-border-blue-200']),
+ );
+ expect(wrapper.classes()).toEqual(expect.arrayContaining(['gl-border-gray-100']));
+ });
+ });
+});
diff --git a/spec/frontend/registry/explorer/components/list_page/__snapshots__/group_empty_state_spec.js.snap b/spec/frontend/registry/explorer/components/list_page/__snapshots__/group_empty_state_spec.js.snap
index 3761369c944..a8412e2bde9 100644
--- a/spec/frontend/registry/explorer/components/list_page/__snapshots__/group_empty_state_spec.js.snap
+++ b/spec/frontend/registry/explorer/components/list_page/__snapshots__/group_empty_state_spec.js.snap
@@ -2,13 +2,10 @@
exports[`Registry Group Empty state to match the default snapshot 1`] = `
<div
- class="container-message"
svg-path="foo"
title="There are no container images available in this group"
>
- <p
- class="js-no-container-images-text"
- >
+ <p>
With the Container Registry, every project can have its own space to store its Docker images. Push at least one Docker image in one of this group's projects in order to show up here.
<gl-link-stub
href="baz"
diff --git a/spec/frontend/registry/explorer/components/list_page/__snapshots__/project_empty_state_spec.js.snap b/spec/frontend/registry/explorer/components/list_page/__snapshots__/project_empty_state_spec.js.snap
index d8ec9c3ca4d..8413e17c7b2 100644
--- a/spec/frontend/registry/explorer/components/list_page/__snapshots__/project_empty_state_spec.js.snap
+++ b/spec/frontend/registry/explorer/components/list_page/__snapshots__/project_empty_state_spec.js.snap
@@ -2,13 +2,10 @@
exports[`Registry Project Empty state to match the default snapshot 1`] = `
<div
- class="container-message"
svg-path="bazFoo"
title="There are no container images stored for this project"
>
- <p
- class="js-no-container-images-text"
- >
+ <p>
With the Container Registry, every project can have its own space to store its Docker images.
<gl-link-stub
href="baz"
@@ -22,9 +19,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
CLI Commands
</h5>
- <p
- class="js-not-logged-in-to-registry-text"
- >
+ <p>
If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have
<gl-link-stub
href="barBaz"
@@ -42,78 +37,50 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
instead of a password.
</p>
- <div
- class="input-group append-bottom-10"
+ <gl-form-input-group-stub
+ class="gl-mb-4"
+ predefinedoptions="[object Object]"
+ value=""
>
- <input
- class="form-control monospace"
- readonly="readonly"
+ <gl-form-input-stub
+ class="gl-font-monospace!"
+ readonly=""
type="text"
+ value="docker login bar"
/>
-
- <span
- class="input-group-append"
- >
- <clipboard-button-stub
- class="input-group-text"
- cssclass="btn-default"
- text="docker login bar"
- title="Copy login command"
- tooltipplacement="top"
- />
- </span>
- </div>
+ </gl-form-input-group-stub>
- <p />
-
- <p>
+ <p
+ class="gl-mb-4"
+ >
You can add an image to this registry with the following commands:
</p>
- <div
- class="input-group append-bottom-10"
+ <gl-form-input-group-stub
+ class="gl-mb-4 "
+ predefinedoptions="[object Object]"
+ value=""
>
- <input
- class="form-control monospace"
- readonly="readonly"
+ <gl-form-input-stub
+ class="gl-font-monospace!"
+ readonly=""
type="text"
+ value="docker build -t foo ."
/>
-
- <span
- class="input-group-append"
- >
- <clipboard-button-stub
- class="input-group-text"
- cssclass="btn-default"
- text="docker build -t foo ."
- title="Copy build command"
- tooltipplacement="top"
- />
- </span>
- </div>
+ </gl-form-input-group-stub>
- <div
- class="input-group"
+ <gl-form-input-group-stub
+ predefinedoptions="[object Object]"
+ value=""
>
- <input
- class="form-control monospace"
- readonly="readonly"
+ <gl-form-input-stub
+ class="gl-font-monospace!"
+ readonly=""
type="text"
+ value="docker push foo"
/>
-
- <span
- class="input-group-append"
- >
- <clipboard-button-stub
- class="input-group-text"
- cssclass="btn-default"
- text="docker push foo"
- title="Copy push command"
- tooltipplacement="top"
- />
- </span>
- </div>
+ </gl-form-input-group-stub>
</div>
`;
diff --git a/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js b/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js
index 78de35ae1dc..aaeaaf00748 100644
--- a/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js
+++ b/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js
@@ -1,11 +1,14 @@
import { shallowMount } from '@vue/test-utils';
import { GlIcon, GlSprintf } from '@gitlab/ui';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-import Component from '~/registry/explorer/components/list_page/image_list_row.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import Component from '~/registry/explorer/components/list_page/image_list_row.vue';
+import ListItem from '~/registry/explorer/components/list_item.vue';
+import DeleteButton from '~/registry/explorer/components/delete_button.vue';
import {
ROW_SCHEDULED_FOR_DELETION,
LIST_DELETE_BUTTON_DISABLED,
+ REMOVE_REPOSITORY_LABEL,
} from '~/registry/explorer/constants';
import { RouterLink } from '../../stubs';
import { imagesListResponse } from '../../mock_data';
@@ -13,10 +16,10 @@ import { imagesListResponse } from '../../mock_data';
describe('Image List Row', () => {
let wrapper;
const item = imagesListResponse.data[0];
- const findDeleteBtn = () => wrapper.find('[data-testid="deleteImageButton"]');
+
const findDetailsLink = () => wrapper.find('[data-testid="detailsLink"]');
const findTagsCount = () => wrapper.find('[data-testid="tagsCount"]');
- const findDeleteButtonWrapper = () => wrapper.find('[data-testid="deleteButtonWrapper"]');
+ const findDeleteBtn = () => wrapper.find(DeleteButton);
const findClipboardButton = () => wrapper.find(ClipboardButton);
const mountComponent = props => {
@@ -24,6 +27,7 @@ describe('Image List Row', () => {
stubs: {
RouterLink,
GlSprintf,
+ ListItem,
},
propsData: {
item,
@@ -72,29 +76,24 @@ describe('Image List Row', () => {
});
});
- describe('delete button wrapper', () => {
- it('has a tooltip', () => {
- mountComponent();
- const tooltip = getBinding(findDeleteButtonWrapper().element, 'gl-tooltip');
- expect(tooltip).toBeDefined();
- expect(tooltip.value.title).toBe(LIST_DELETE_BUTTON_DISABLED);
- });
- it('tooltip is enabled when destroy_path is falsy', () => {
- mountComponent({ item: { ...item, destroy_path: null } });
- const tooltip = getBinding(findDeleteButtonWrapper().element, 'gl-tooltip');
- expect(tooltip.value.disabled).toBeFalsy();
- });
- });
-
describe('delete button', () => {
it('exists', () => {
mountComponent();
expect(findDeleteBtn().exists()).toBe(true);
});
+ it('has the correct props', () => {
+ mountComponent();
+ expect(findDeleteBtn().attributes()).toMatchObject({
+ title: REMOVE_REPOSITORY_LABEL,
+ tooltipdisabled: `${Boolean(item.destroy_path)}`,
+ tooltiptitle: LIST_DELETE_BUTTON_DISABLED,
+ });
+ });
+
it('emits a delete event', () => {
mountComponent();
- findDeleteBtn().vm.$emit('click');
+ findDeleteBtn().vm.$emit('delete');
expect(wrapper.emitted('delete')).toEqual([[item]]);
});
diff --git a/spec/frontend/registry/explorer/mock_data.js b/spec/frontend/registry/explorer/mock_data.js
index e2b33826503..a7ffed4c9fd 100644
--- a/spec/frontend/registry/explorer/mock_data.js
+++ b/spec/frontend/registry/explorer/mock_data.js
@@ -70,9 +70,10 @@ export const tagsListResponse = {
size: 19,
layers: 10,
location: 'location',
- path: 'bar',
- created_at: 1505828744434,
+ path: 'bar:centos6',
+ created_at: '2020-06-29T10:23:51.766+00:00',
destroy_path: 'path',
+ digest: 'sha256:1ab51d519f574b636ae7788051c60239334ae8622a9fd82a0cf7bae7786dfd5c',
},
{
name: 'test-tag',
@@ -80,9 +81,10 @@ export const tagsListResponse = {
short_revision: 'b969de599',
size: 19,
layers: 10,
- path: 'foo',
+ path: 'foo:test-tag',
location: 'location-2',
- created_at: 1505828744434,
+ created_at: '2020-06-29T10:23:51.766+00:00',
+ digest: 'sha256:1ab51d519f574b636ae7788051c60239334ae8622a9fd82a0cf7bae7736dfd5c',
},
],
headers,
diff --git a/spec/frontend/registry/explorer/pages/details_spec.js b/spec/frontend/registry/explorer/pages/details_spec.js
index b7e01cad9bc..9bc0bae5c23 100644
--- a/spec/frontend/registry/explorer/pages/details_spec.js
+++ b/spec/frontend/registry/explorer/pages/details_spec.js
@@ -5,6 +5,7 @@ import component from '~/registry/explorer/pages/details.vue';
import DeleteAlert from '~/registry/explorer/components/details_page/delete_alert.vue';
import DetailsHeader from '~/registry/explorer/components/details_page/details_header.vue';
import TagsLoader from '~/registry/explorer/components/details_page/tags_loader.vue';
+import TagsList from '~/registry/explorer/components/details_page/tags_list.vue';
import EmptyTagsState from '~/registry/explorer/components/details_page/empty_tags_state.vue';
import { createStore } from '~/registry/explorer/stores/';
import {
@@ -15,7 +16,7 @@ import {
} from '~/registry/explorer/stores/mutation_types/';
import { tagsListResponse } from '../mock_data';
-import { TagsTable, DeleteModal } from '../stubs';
+import { DeleteModal } from '../stubs';
describe('Details Page', () => {
let wrapper;
@@ -25,18 +26,23 @@ describe('Details Page', () => {
const findDeleteModal = () => wrapper.find(DeleteModal);
const findPagination = () => wrapper.find(GlPagination);
const findTagsLoader = () => wrapper.find(TagsLoader);
- const findTagsTable = () => wrapper.find(TagsTable);
+ const findTagsList = () => wrapper.find(TagsList);
const findDeleteAlert = () => wrapper.find(DeleteAlert);
const findDetailsHeader = () => wrapper.find(DetailsHeader);
const findEmptyTagsState = () => wrapper.find(EmptyTagsState);
const routeId = window.btoa(JSON.stringify({ name: 'foo', tags_path: 'bar' }));
+ const tagsArrayToSelectedTags = tags =>
+ tags.reduce((acc, c) => {
+ acc[c.name] = true;
+ return acc;
+ }, {});
+
const mountComponent = options => {
wrapper = shallowMount(component, {
store,
stubs: {
- TagsTable,
DeleteModal,
},
mocks: {
@@ -66,15 +72,18 @@ describe('Details Page', () => {
describe('when isLoading is true', () => {
beforeEach(() => {
- mountComponent();
store.commit(SET_MAIN_LOADING, true);
- return wrapper.vm.$nextTick();
+ mountComponent();
});
afterEach(() => store.commit(SET_MAIN_LOADING, false));
- it('binds isLoading to tags-table', () => {
- expect(findTagsTable().props('isLoading')).toBe(true);
+ it('shows the loader', () => {
+ expect(findTagsLoader().exists()).toBe(true);
+ });
+
+ it('does not show the list', () => {
+ expect(findTagsList().exists()).toBe(false);
});
it('does not show pagination', () => {
@@ -82,8 +91,9 @@ describe('Details Page', () => {
});
});
- describe('table slots', () => {
+ describe('when the list of tags is empty', () => {
beforeEach(() => {
+ store.commit(SET_TAGS_LIST_SUCCESS, []);
mountComponent();
});
@@ -91,32 +101,37 @@ describe('Details Page', () => {
expect(findEmptyTagsState().exists()).toBe(true);
});
- it('has a skeleton loader', () => {
- expect(findTagsLoader().exists()).toBe(true);
+ it('does not show the loader', () => {
+ expect(findTagsLoader().exists()).toBe(false);
+ });
+
+ it('does not show the list', () => {
+ expect(findTagsList().exists()).toBe(false);
});
});
- describe('table', () => {
+ describe('list', () => {
beforeEach(() => {
mountComponent();
});
it('exists', () => {
- expect(findTagsTable().exists()).toBe(true);
+ expect(findTagsList().exists()).toBe(true);
});
it('has the correct props bound', () => {
- expect(findTagsTable().props()).toMatchObject({
+ expect(findTagsList().props()).toMatchObject({
isDesktop: true,
- isLoading: false,
tags: store.state.tags,
});
});
describe('deleteEvent', () => {
describe('single item', () => {
+ let tagToBeDeleted;
beforeEach(() => {
- findTagsTable().vm.$emit('delete', [store.state.tags[0].name]);
+ [tagToBeDeleted] = store.state.tags;
+ findTagsList().vm.$emit('delete', { [tagToBeDeleted.name]: true });
});
it('open the modal', () => {
@@ -124,7 +139,7 @@ describe('Details Page', () => {
});
it('maps the selection to itemToBeDeleted', () => {
- expect(wrapper.vm.itemsToBeDeleted).toEqual([store.state.tags[0]]);
+ expect(wrapper.vm.itemsToBeDeleted).toEqual([tagToBeDeleted]);
});
it('tracks a single delete event', () => {
@@ -136,7 +151,7 @@ describe('Details Page', () => {
describe('multiple items', () => {
beforeEach(() => {
- findTagsTable().vm.$emit('delete', store.state.tags.map(t => t.name));
+ findTagsList().vm.$emit('delete', tagsArrayToSelectedTags(store.state.tags));
});
it('open the modal', () => {
@@ -202,7 +217,7 @@ describe('Details Page', () => {
describe('when one item is selected to be deleted', () => {
beforeEach(() => {
mountComponent();
- findTagsTable().vm.$emit('delete', [store.state.tags[0].name]);
+ findTagsList().vm.$emit('delete', { [store.state.tags[0].name]: true });
});
it('dispatch requestDeleteTag with the right parameters', () => {
@@ -217,7 +232,7 @@ describe('Details Page', () => {
describe('when more than one item is selected to be deleted', () => {
beforeEach(() => {
mountComponent();
- findTagsTable().vm.$emit('delete', store.state.tags.map(t => t.name));
+ findTagsList().vm.$emit('delete', tagsArrayToSelectedTags(store.state.tags));
});
it('dispatch requestDeleteTags with the right parameters', () => {
diff --git a/spec/frontend/registry/explorer/stubs.js b/spec/frontend/registry/explorer/stubs.js
index d3518c36c82..8f95fce2867 100644
--- a/spec/frontend/registry/explorer/stubs.js
+++ b/spec/frontend/registry/explorer/stubs.js
@@ -1,5 +1,5 @@
-import RealTagsTable from '~/registry/explorer/components/details_page/tags_table.vue';
import RealDeleteModal from '~/registry/explorer/components/details_page/delete_modal.vue';
+import RealListItem from '~/registry/explorer/components/list_item.vue';
export const GlModal = {
template: '<div><slot name="modal-title"></slot><slot></slot><slot name="modal-ok"></slot></div>',
@@ -18,11 +18,6 @@ export const RouterLink = {
props: ['to'],
};
-export const TagsTable = {
- props: RealTagsTable.props,
- template: `<div><slot name="empty"></slot><slot name="loader"></slot></div>`,
-};
-
export const DeleteModal = {
template: '<div></div>',
methods: {
@@ -35,3 +30,13 @@ export const GlSkeletonLoader = {
template: `<div><slot></slot></div>`,
props: ['width', 'height'],
};
+
+export const ListItem = {
+ ...RealListItem,
+ data() {
+ return {
+ detailsSlots: [],
+ isDetailsShown: true,
+ };
+ },
+};
diff --git a/spec/frontend/registry/settings/components/__snapshots__/registry_settings_app_spec.js.snap b/spec/frontend/registry/settings/components/__snapshots__/registry_settings_app_spec.js.snap
index 966acdf52be..11393c89d06 100644
--- a/spec/frontend/registry/settings/components/__snapshots__/registry_settings_app_spec.js.snap
+++ b/spec/frontend/registry/settings/components/__snapshots__/registry_settings_app_spec.js.snap
@@ -2,24 +2,6 @@
exports[`Registry Settings App renders 1`] = `
<div>
- <p>
-
- Tag expiration policy is designed to:
-
- </p>
-
- <ul>
- <li>
- Keep and protect the images that matter most.
- </li>
-
- <li>
-
- Automatically remove extra images that aren't designed to be kept.
-
- </li>
- </ul>
-
<settings-form-stub />
</div>
`;
diff --git a/spec/frontend/registry/settings/components/registry_settings_app_spec.js b/spec/frontend/registry/settings/components/registry_settings_app_spec.js
index 95f784c9727..9551ee72e51 100644
--- a/spec/frontend/registry/settings/components/registry_settings_app_spec.js
+++ b/spec/frontend/registry/settings/components/registry_settings_app_spec.js
@@ -5,6 +5,11 @@ import SettingsForm from '~/registry/settings/components/settings_form.vue';
import { createStore } from '~/registry/settings/store/';
import { SET_SETTINGS, SET_INITIAL_STATE } from '~/registry/settings/store/mutation_types';
import { FETCH_SETTINGS_ERROR_MESSAGE } from '~/registry/shared/constants';
+import {
+ UNAVAILABLE_FEATURE_INTRO_TEXT,
+ UNAVAILABLE_USER_FEATURE_TEXT,
+} from '~/registry/settings/constants';
+
import { stringifiedFormOptions } from '../../shared/mock_data';
describe('Registry Settings App', () => {
@@ -68,10 +73,8 @@ describe('Registry Settings App', () => {
it('shows an alert', () => {
const text = findAlert().text();
- expect(text).toContain(
- 'The Container Registry tag expiration and retention policies for this project have not been enabled.',
- );
- expect(text).toContain('Please contact your administrator.');
+ expect(text).toContain(UNAVAILABLE_FEATURE_INTRO_TEXT);
+ expect(text).toContain(UNAVAILABLE_USER_FEATURE_TEXT);
});
describe('an admin is visiting the page', () => {
diff --git a/spec/frontend/registry/settings/components/settings_form_spec.js b/spec/frontend/registry/settings/components/settings_form_spec.js
index 2b3e529b283..9b9ca92270c 100644
--- a/spec/frontend/registry/settings/components/settings_form_spec.js
+++ b/spec/frontend/registry/settings/components/settings_form_spec.js
@@ -7,6 +7,7 @@ import {
UPDATE_SETTINGS_ERROR_MESSAGE,
UPDATE_SETTINGS_SUCCESS_MESSAGE,
} from '~/registry/shared/constants';
+import waitForPromises from 'helpers/wait_for_promises';
import { stringifiedFormOptions } from '../../shared/mock_data';
describe('Settings Form', () => {
@@ -36,12 +37,17 @@ describe('Settings Form', () => {
const findSaveButton = () => wrapper.find({ ref: 'save-button' });
const findLoadingIcon = (parent = wrapper) => parent.find(GlLoadingIcon);
- const mountComponent = () => {
+ const mountComponent = (data = {}) => {
wrapper = shallowMount(component, {
stubs: {
GlCard,
GlLoadingIcon,
},
+ data() {
+ return {
+ ...data,
+ };
+ },
mocks: {
$toast: {
show: jest.fn(),
@@ -55,7 +61,6 @@ describe('Settings Form', () => {
store = createStore();
store.dispatch('setInitialState', stringifiedFormOptions);
dispatchSpy = jest.spyOn(store, 'dispatch');
- mountComponent();
jest.spyOn(Tracking, 'event');
});
@@ -63,20 +68,30 @@ describe('Settings Form', () => {
wrapper.destroy();
});
+ describe('data binding', () => {
+ it('v-model change update the settings property', () => {
+ mountComponent();
+ findFields().vm.$emit('input', { newValue: 'foo' });
+ expect(dispatchSpy).toHaveBeenCalledWith('updateSettings', { settings: 'foo' });
+ });
+
+ it('v-model change update the api error property', () => {
+ const apiErrors = { baz: 'bar' };
+ mountComponent({ apiErrors });
+ expect(findFields().props('apiErrors')).toEqual(apiErrors);
+ findFields().vm.$emit('input', { newValue: 'foo', modified: 'baz' });
+ expect(findFields().props('apiErrors')).toEqual({});
+ });
+ });
+
describe('form', () => {
let form;
beforeEach(() => {
+ mountComponent();
form = findForm();
dispatchSpy.mockReturnValue();
});
- describe('data binding', () => {
- it('v-model change update the settings property', () => {
- findFields().vm.$emit('input', 'foo');
- expect(dispatchSpy).toHaveBeenCalledWith('updateSettings', { settings: 'foo' });
- });
- });
-
describe('form reset event', () => {
beforeEach(() => {
form.trigger('reset');
@@ -108,24 +123,40 @@ describe('Settings Form', () => {
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'submit_form', trackingPayload);
});
- it('show a success toast when submit succeed', () => {
+ it('show a success toast when submit succeed', async () => {
dispatchSpy.mockResolvedValue();
form.trigger('submit');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_SUCCESS_MESSAGE, {
- type: 'success',
- });
+ await waitForPromises();
+ expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_SUCCESS_MESSAGE, {
+ type: 'success',
});
});
- it('show an error toast when submit fails', () => {
- dispatchSpy.mockRejectedValue();
- form.trigger('submit');
- return wrapper.vm.$nextTick().then(() => {
+ describe('when submit fails', () => {
+ it('shows an error', async () => {
+ dispatchSpy.mockRejectedValue({ response: {} });
+ form.trigger('submit');
+ await waitForPromises();
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_ERROR_MESSAGE, {
type: 'error',
});
});
+
+ it('parses the error messages', async () => {
+ dispatchSpy.mockRejectedValue({
+ response: {
+ data: {
+ message: {
+ foo: 'bar',
+ 'container_expiration_policy.name': ['baz'],
+ },
+ },
+ },
+ });
+ form.trigger('submit');
+ await waitForPromises();
+ expect(findFields().props('apiErrors')).toEqual({ name: 'baz' });
+ });
});
});
});
@@ -134,6 +165,7 @@ describe('Settings Form', () => {
describe('cancel button', () => {
beforeEach(() => {
store.commit('SET_SETTINGS', { foo: 'bar' });
+ mountComponent();
});
it('has type reset', () => {
@@ -165,6 +197,7 @@ describe('Settings Form', () => {
describe('when isLoading is true', () => {
beforeEach(() => {
store.commit('TOGGLE_LOADING');
+ mountComponent();
});
afterEach(() => {
store.commit('TOGGLE_LOADING');
diff --git a/spec/frontend/registry/shared/components/__snapshots__/expiration_policy_fields_spec.js.snap b/spec/frontend/registry/shared/components/__snapshots__/expiration_policy_fields_spec.js.snap
index a9034b81d2f..69953fb5e03 100644
--- a/spec/frontend/registry/shared/components/__snapshots__/expiration_policy_fields_spec.js.snap
+++ b/spec/frontend/registry/shared/components/__snapshots__/expiration_policy_fields_spec.js.snap
@@ -2,32 +2,30 @@
exports[`Expiration Policy Form renders 1`] = `
<div
- class="lh-2"
+ class="gl-line-height-20"
>
<gl-form-group-stub
id="expiration-policy-toggle-group"
- label="Expiration policy:"
+ label="Cleanup policy:"
label-align="right"
label-cols="3"
label-for="expiration-policy-toggle"
>
<div
- class="d-flex align-items-start"
+ class="gl-display-flex"
>
<gl-toggle-stub
id="expiration-policy-toggle"
- labeloff="Toggle Status: OFF"
- labelon="Toggle Status: ON"
- labelposition="hidden"
+ labelposition="top"
/>
<span
- class="mb-2 ml-1 lh-2"
+ class="gl-mb-3 gl-ml-3 gl-line-height-20"
>
- Docker tag expiration policy is
<strong>
- disabled
+ Disabled
</strong>
+ - Tags matching the patterns defined below will be scheduled for deletion
</span>
</div>
</gl-form-group-stub>
@@ -116,7 +114,6 @@ exports[`Expiration Policy Form renders 1`] = `
<gl-form-group-stub
id="expiration-policy-name-matching-group"
- invalid-feedback="The value of this input should be less than 255 characters"
label-align="right"
label-cols="3"
label-for="expiration-policy-name-matching"
@@ -125,6 +122,7 @@ exports[`Expiration Policy Form renders 1`] = `
<gl-form-textarea-stub
disabled="true"
id="expiration-policy-name-matching"
+ noresize="true"
placeholder=".*"
trim=""
value=""
@@ -132,7 +130,6 @@ exports[`Expiration Policy Form renders 1`] = `
</gl-form-group-stub>
<gl-form-group-stub
id="expiration-policy-keep-name-group"
- invalid-feedback="The value of this input should be less than 255 characters"
label-align="right"
label-cols="3"
label-for="expiration-policy-keep-name"
@@ -141,6 +138,7 @@ exports[`Expiration Policy Form renders 1`] = `
<gl-form-textarea-stub
disabled="true"
id="expiration-policy-keep-name"
+ noresize="true"
placeholder=""
trim=""
value=""
diff --git a/spec/frontend/registry/shared/components/expiration_policy_fields_spec.js b/spec/frontend/registry/shared/components/expiration_policy_fields_spec.js
index 4825351a6d3..ee765ffd1c0 100644
--- a/spec/frontend/registry/shared/components/expiration_policy_fields_spec.js
+++ b/spec/frontend/registry/shared/components/expiration_policy_fields_spec.js
@@ -2,7 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import { GlSprintf } from '@gitlab/ui';
import component from '~/registry/shared/components/expiration_policy_fields.vue';
-import { NAME_REGEX_LENGTH } from '~/registry/shared/constants';
+import { NAME_REGEX_LENGTH, ENABLED_TEXT, DISABLED_TEXT } from '~/registry/shared/constants';
import { formOptions } from '../mock_data';
describe('Expiration Policy Form', () => {
@@ -94,7 +94,9 @@ describe('Expiration Policy Form', () => {
: 'input';
element.vm.$emit(modelUpdateEvent, value);
return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('input')).toEqual([[{ [modelName]: value }]]);
+ expect(wrapper.emitted('input')).toEqual([
+ [{ newValue: { [modelName]: value }, modified: modelName }],
+ ]);
});
});
@@ -126,42 +128,61 @@ describe('Expiration Policy Form', () => {
});
describe.each`
- modelName | elementName | stateVariable
- ${'name_regex'} | ${'name-matching'} | ${'nameRegexState'}
- ${'name_regex_keep'} | ${'keep-name'} | ${'nameKeepRegexState'}
- `('regex textarea validation', ({ modelName, elementName, stateVariable }) => {
- describe(`when name regex is longer than ${NAME_REGEX_LENGTH}`, () => {
- const invalidString = new Array(NAME_REGEX_LENGTH + 2).join(',');
-
- beforeEach(() => {
- mountComponent({ value: { [modelName]: invalidString } });
+ modelName | elementName
+ ${'name_regex'} | ${'name-matching'}
+ ${'name_regex_keep'} | ${'keep-name'}
+ `('regex textarea validation', ({ modelName, elementName }) => {
+ const invalidString = new Array(NAME_REGEX_LENGTH + 2).join(',');
+
+ describe('when apiError contains an error message', () => {
+ const errorMessage = 'something went wrong';
+
+ it('shows the error message on the relevant field', () => {
+ mountComponent({ apiErrors: { [modelName]: errorMessage } });
+ expect(findFormGroup(elementName).attributes('invalid-feedback')).toBe(errorMessage);
});
- it(`${stateVariable} is false`, () => {
- expect(wrapper.vm.textAreaState[stateVariable]).toBe(false);
- });
-
- it('emit the @invalidated event', () => {
- expect(wrapper.emitted('invalidated')).toBeTruthy();
+ it('gives precedence to API errors compared to local ones', () => {
+ mountComponent({
+ apiErrors: { [modelName]: errorMessage },
+ value: { [modelName]: invalidString },
+ });
+ expect(findFormGroup(elementName).attributes('invalid-feedback')).toBe(errorMessage);
});
});
- it('if the user did not type validation is null', () => {
- mountComponent({ value: { [modelName]: '' } });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.textAreaState[stateVariable]).toBe(null);
+ describe('when apiErrors is empty', () => {
+ it('if the user did not type validation is null', async () => {
+ mountComponent({ value: { [modelName]: '' } });
+ expect(findFormGroup(elementName).attributes('state')).toBeUndefined();
expect(wrapper.emitted('validated')).toBeTruthy();
});
- });
- it(`if the user typed and is less than ${NAME_REGEX_LENGTH} state is true`, () => {
- mountComponent({ value: { [modelName]: 'foo' } });
- return wrapper.vm.$nextTick().then(() => {
+ it(`if the user typed and is less than ${NAME_REGEX_LENGTH} state is true`, () => {
+ mountComponent({ value: { [modelName]: 'foo' } });
+
const formGroup = findFormGroup(elementName);
const formElement = findFormElements(elementName, formGroup);
expect(formGroup.attributes('state')).toBeTruthy();
expect(formElement.attributes('state')).toBeTruthy();
});
+
+ describe(`when name regex is longer than ${NAME_REGEX_LENGTH}`, () => {
+ beforeEach(() => {
+ mountComponent({ value: { [modelName]: invalidString } });
+ });
+
+ it('textAreaValidation state is false', () => {
+ expect(findFormGroup(elementName).attributes('state')).toBeUndefined();
+ // we are forced to check the model attribute because falsy attrs are all casted to undefined in attrs
+ // while in this case false shows an error and null instead shows nothing.
+ expect(wrapper.vm.textAreaValidation[modelName].state).toBe(false);
+ });
+
+ it('emit the @invalidated event', () => {
+ expect(wrapper.emitted('invalidated')).toBeTruthy();
+ });
+ });
});
});
@@ -169,13 +190,13 @@ describe('Expiration Policy Form', () => {
it('toggleDescriptionText show disabled when settings.enabled is false', () => {
mountComponent();
const toggleHelpText = findFormGroup('toggle').find('span');
- expect(toggleHelpText.html()).toContain('disabled');
+ expect(toggleHelpText.html()).toContain(DISABLED_TEXT);
});
it('toggleDescriptionText show enabled when settings.enabled is true', () => {
mountComponent({ value: { enabled: true } });
const toggleHelpText = findFormGroup('toggle').find('span');
- expect(toggleHelpText.html()).toContain('enabled');
+ expect(toggleHelpText.html()).toContain(ENABLED_TEXT);
});
});
});
diff --git a/spec/frontend/releases/components/app_new_spec.js b/spec/frontend/releases/components/app_new_spec.js
new file mode 100644
index 00000000000..0d5664766e5
--- /dev/null
+++ b/spec/frontend/releases/components/app_new_spec.js
@@ -0,0 +1,26 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import { mount } from '@vue/test-utils';
+import ReleaseNewApp from '~/releases/components/app_new.vue';
+
+Vue.use(Vuex);
+
+describe('Release new component', () => {
+ let wrapper;
+
+ const factory = () => {
+ const store = new Vuex.Store();
+ wrapper = mount(ReleaseNewApp, { store });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('renders the app', () => {
+ factory();
+
+ expect(wrapper.exists()).toBe(true);
+ });
+});
diff --git a/spec/frontend/releases/components/release_block_assets_spec.js b/spec/frontend/releases/components/release_block_assets_spec.js
index 44b190b0d19..a85532a8118 100644
--- a/spec/frontend/releases/components/release_block_assets_spec.js
+++ b/spec/frontend/releases/components/release_block_assets_spec.js
@@ -4,6 +4,7 @@ import ReleaseBlockAssets from '~/releases/components/release_block_assets.vue';
import { ASSET_LINK_TYPE } from '~/releases/constants';
import { trimText } from 'helpers/text_helper';
import { assets } from '../mock_data';
+import { cloneDeep } from 'lodash';
describe('Release block assets', () => {
let wrapper;
@@ -30,7 +31,7 @@ describe('Release block assets', () => {
wrapper.findAll('h5').filter(h5 => h5.text() === sections[type]);
beforeEach(() => {
- defaultProps = { assets };
+ defaultProps = { assets: cloneDeep(assets) };
});
describe('with default props', () => {
@@ -96,6 +97,35 @@ describe('Release block assets', () => {
});
});
+ describe('sources', () => {
+ const testSources = ({ shouldSourcesBeRendered }) => {
+ assets.sources.forEach(s => {
+ expect(wrapper.find(`a[href="${s.url}"]`).exists()).toBe(shouldSourcesBeRendered);
+ });
+ };
+
+ describe('when the release has sources', () => {
+ beforeEach(() => {
+ createComponent(defaultProps);
+ });
+
+ it('renders sources', () => {
+ testSources({ shouldSourcesBeRendered: true });
+ });
+ });
+
+ describe('when the release does not have sources', () => {
+ beforeEach(() => {
+ delete defaultProps.assets.sources;
+ createComponent(defaultProps);
+ });
+
+ it('does not render any sources', () => {
+ testSources({ shouldSourcesBeRendered: false });
+ });
+ });
+ });
+
describe('external vs internal links', () => {
const containsExternalSourceIndicator = () =>
wrapper.contains('[data-testid="external-link-indicator"]');
diff --git a/spec/frontend/reports/codequality_report/components/codequality_issue_body_spec.js b/spec/frontend/reports/codequality_report/components/codequality_issue_body_spec.js
new file mode 100644
index 00000000000..3e11af9c9df
--- /dev/null
+++ b/spec/frontend/reports/codequality_report/components/codequality_issue_body_spec.js
@@ -0,0 +1,62 @@
+import { shallowMount } from '@vue/test-utils';
+import component from '~/reports/codequality_report/components/codequality_issue_body.vue';
+import { STATUS_FAILED, STATUS_NEUTRAL, STATUS_SUCCESS } from '~/reports/constants';
+
+describe('code quality issue body issue body', () => {
+ let wrapper;
+
+ const codequalityIssue = {
+ name:
+ 'rubygem-rest-client: session fixation vulnerability via Set-Cookie headers in 30x redirection responses',
+ path: 'Gemfile.lock',
+ severity: 'normal',
+ type: 'Issue',
+ urlPath: '/Gemfile.lock#L22',
+ };
+
+ const mountWithStatus = initialStatus => {
+ wrapper = shallowMount(component, {
+ propsData: {
+ issue: codequalityIssue,
+ status: initialStatus,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('with success', () => {
+ it('renders fixed label', () => {
+ mountWithStatus(STATUS_SUCCESS);
+
+ expect(wrapper.text()).toContain('Fixed');
+ });
+ });
+
+ describe('without success', () => {
+ it('renders fixed label', () => {
+ mountWithStatus(STATUS_FAILED);
+
+ expect(wrapper.text()).not.toContain('Fixed');
+ });
+ });
+
+ describe('name', () => {
+ it('renders name', () => {
+ mountWithStatus(STATUS_NEUTRAL);
+
+ expect(wrapper.text()).toContain(codequalityIssue.name);
+ });
+ });
+
+ describe('path', () => {
+ it('renders the report-link path using the correct code quality issue', () => {
+ mountWithStatus(STATUS_NEUTRAL);
+
+ expect(wrapper.find('report-link-stub').props('issue')).toBe(codequalityIssue);
+ });
+ });
+});
diff --git a/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js b/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js
new file mode 100644
index 00000000000..1905ca0d5e1
--- /dev/null
+++ b/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js
@@ -0,0 +1,146 @@
+import { mount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import GroupedCodequalityReportsApp from '~/reports/codequality_report/grouped_codequality_reports_app.vue';
+import CodequalityIssueBody from '~/reports/codequality_report/components/codequality_issue_body.vue';
+import store from '~/reports/codequality_report/store';
+import { mockParsedHeadIssues, mockParsedBaseIssues } from './mock_data';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('Grouped code quality reports app', () => {
+ const Component = localVue.extend(GroupedCodequalityReportsApp);
+ let wrapper;
+ let mockStore;
+
+ const mountComponent = (props = {}) => {
+ wrapper = mount(Component, {
+ store: mockStore,
+ localVue,
+ propsData: {
+ basePath: 'base.json',
+ headPath: 'head.json',
+ baseBlobPath: 'base/blob/path/',
+ headBlobPath: 'head/blob/path/',
+ codequalityHelpPath: 'codequality_help.html',
+ ...props,
+ },
+ methods: {
+ fetchReports: () => {},
+ },
+ });
+ };
+
+ const findWidget = () => wrapper.find('.js-codequality-widget');
+ const findIssueBody = () => wrapper.find(CodequalityIssueBody);
+
+ beforeEach(() => {
+ mockStore = store();
+ mountComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when it is loading reports', () => {
+ beforeEach(() => {
+ mockStore.state.isLoading = true;
+ });
+
+ it('should render loading text', () => {
+ expect(findWidget().text()).toEqual('Loading codeclimate report');
+ });
+ });
+
+ describe('when base and head reports are loaded and compared', () => {
+ describe('with no issues', () => {
+ beforeEach(() => {
+ mockStore.state.newIssues = [];
+ mockStore.state.resolvedIssues = [];
+ });
+
+ it('renders no changes text', () => {
+ expect(findWidget().text()).toEqual('No changes to code quality');
+ });
+ });
+
+ describe('with issues', () => {
+ describe('with new issues', () => {
+ beforeEach(() => {
+ mockStore.state.newIssues = [mockParsedHeadIssues[0]];
+ mockStore.state.resolvedIssues = [];
+ });
+
+ it('renders summary text', () => {
+ expect(findWidget().text()).toContain('Code quality degraded on 1 point');
+ });
+
+ it('renders custom codequality issue body', () => {
+ expect(findIssueBody().props('issue')).toEqual(mockParsedHeadIssues[0]);
+ });
+ });
+
+ describe('with resolved issues', () => {
+ beforeEach(() => {
+ mockStore.state.newIssues = [];
+ mockStore.state.resolvedIssues = [mockParsedBaseIssues[0]];
+ });
+
+ it('renders summary text', () => {
+ expect(findWidget().text()).toContain('Code quality improved on 1 point');
+ });
+
+ it('renders custom codequality issue body', () => {
+ expect(findIssueBody().props('issue')).toEqual(mockParsedBaseIssues[0]);
+ });
+ });
+
+ describe('with new and resolved issues', () => {
+ beforeEach(() => {
+ mockStore.state.newIssues = [mockParsedHeadIssues[0]];
+ mockStore.state.resolvedIssues = [mockParsedBaseIssues[0]];
+ });
+
+ it('renders summary text', () => {
+ expect(findWidget().text()).toContain(
+ 'Code quality improved on 1 point and degraded on 1 point',
+ );
+ });
+
+ it('renders custom codequality issue body', () => {
+ expect(findIssueBody().props('issue')).toEqual(mockParsedHeadIssues[0]);
+ });
+ });
+ });
+ });
+
+ describe('when there is a head report but no base report', () => {
+ beforeEach(() => {
+ mockStore.state.basePath = null;
+ mockStore.state.hasError = true;
+ });
+
+ it('renders error text', () => {
+ expect(findWidget().text()).toEqual('Failed to load codeclimate report');
+ });
+
+ it('renders a help icon with more information', () => {
+ expect(findWidget().html()).toContain('ic-question');
+ });
+ });
+
+ describe('on error', () => {
+ beforeEach(() => {
+ mockStore.state.hasError = true;
+ });
+
+ it('renders error text', () => {
+ expect(findWidget().text()).toContain('Failed to load codeclimate report');
+ });
+
+ it('does not render a help icon', () => {
+ expect(findWidget().html()).not.toContain('ic-question');
+ });
+ });
+});
diff --git a/spec/frontend/reports/codequality_report/mock_data.js b/spec/frontend/reports/codequality_report/mock_data.js
new file mode 100644
index 00000000000..9bd61527d3f
--- /dev/null
+++ b/spec/frontend/reports/codequality_report/mock_data.js
@@ -0,0 +1,90 @@
+export const headIssues = [
+ {
+ check_name: 'Rubocop/Lint/UselessAssignment',
+ description: 'Insecure Dependency',
+ location: {
+ path: 'lib/six.rb',
+ lines: {
+ begin: 6,
+ end: 7,
+ },
+ },
+ fingerprint: 'e879dd9bbc0953cad5037cde7ff0f627',
+ },
+ {
+ categories: ['Security'],
+ check_name: 'Insecure Dependency',
+ description: 'Insecure Dependency',
+ location: {
+ path: 'Gemfile.lock',
+ lines: {
+ begin: 22,
+ end: 22,
+ },
+ },
+ fingerprint: 'ca2e59451e98ae60ba2f54e3857c50e5',
+ },
+];
+
+export const mockParsedHeadIssues = [
+ {
+ ...headIssues[1],
+ name: 'Insecure Dependency',
+ path: 'lib/six.rb',
+ urlPath: 'headPath/lib/six.rb#L6',
+ line: 6,
+ },
+];
+
+export const baseIssues = [
+ {
+ categories: ['Security'],
+ check_name: 'Insecure Dependency',
+ description: 'Insecure Dependency',
+ location: {
+ path: 'Gemfile.lock',
+ lines: {
+ begin: 22,
+ end: 22,
+ },
+ },
+ fingerprint: 'ca2e59451e98ae60ba2f54e3857c50e5',
+ },
+ {
+ categories: ['Security'],
+ check_name: 'Insecure Dependency',
+ description: 'Insecure Dependency',
+ location: {
+ path: 'Gemfile.lock',
+ lines: {
+ begin: 21,
+ end: 21,
+ },
+ },
+ fingerprint: 'ca2354534dee94ae60ba2f54e3857c50e5',
+ },
+];
+
+export const mockParsedBaseIssues = [
+ {
+ ...baseIssues[1],
+ name: 'Insecure Dependency',
+ path: 'Gemfile.lock',
+ line: 21,
+ urlPath: 'basePath/Gemfile.lock#L21',
+ },
+];
+
+export const issueDiff = [
+ {
+ categories: ['Security'],
+ check_name: 'Insecure Dependency',
+ description: 'Insecure Dependency',
+ fingerprint: 'ca2e59451e98ae60ba2f54e3857c50e5',
+ line: 6,
+ location: { lines: { begin: 22, end: 22 }, path: 'Gemfile.lock' },
+ name: 'Insecure Dependency',
+ path: 'lib/six.rb',
+ urlPath: 'headPath/lib/six.rb#L6',
+ },
+];
diff --git a/spec/frontend/reports/codequality_report/store/actions_spec.js b/spec/frontend/reports/codequality_report/store/actions_spec.js
new file mode 100644
index 00000000000..6c30fdb7871
--- /dev/null
+++ b/spec/frontend/reports/codequality_report/store/actions_spec.js
@@ -0,0 +1,151 @@
+import axios from '~/lib/utils/axios_utils';
+import MockAdapter from 'axios-mock-adapter';
+import * as actions from '~/reports/codequality_report/store/actions';
+import * as types from '~/reports/codequality_report/store/mutation_types';
+import createStore from '~/reports/codequality_report/store';
+import { TEST_HOST } from 'spec/test_constants';
+import testAction from 'helpers/vuex_action_helper';
+import { headIssues, baseIssues, mockParsedHeadIssues, mockParsedBaseIssues } from '../mock_data';
+
+// mock codequality comparison worker
+jest.mock('~/reports/codequality_report/workers/codequality_comparison_worker', () =>
+ jest.fn().mockImplementation(() => {
+ return {
+ addEventListener: (eventName, callback) => {
+ callback({
+ data: {
+ newIssues: [mockParsedHeadIssues[0]],
+ resolvedIssues: [mockParsedBaseIssues[0]],
+ },
+ });
+ },
+ };
+ }),
+);
+
+describe('Codequality Reports actions', () => {
+ let localState;
+ let localStore;
+
+ beforeEach(() => {
+ localStore = createStore();
+ localState = localStore.state;
+ });
+
+ describe('setPaths', () => {
+ it('should commit SET_PATHS mutation', done => {
+ const paths = {
+ basePath: 'basePath',
+ headPath: 'headPath',
+ baseBlobPath: 'baseBlobPath',
+ headBlobPath: 'headBlobPath',
+ helpPath: 'codequalityHelpPath',
+ };
+
+ testAction(
+ actions.setPaths,
+ paths,
+ localState,
+ [{ type: types.SET_PATHS, payload: paths }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('fetchReports', () => {
+ let mock;
+
+ beforeEach(() => {
+ localState.headPath = `${TEST_HOST}/head.json`;
+ localState.basePath = `${TEST_HOST}/base.json`;
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('on success', () => {
+ it('commits REQUEST_REPORTS and dispatches receiveReportsSuccess', done => {
+ mock.onGet(`${TEST_HOST}/head.json`).reply(200, headIssues);
+ mock.onGet(`${TEST_HOST}/base.json`).reply(200, baseIssues);
+
+ testAction(
+ actions.fetchReports,
+ null,
+ localState,
+ [{ type: types.REQUEST_REPORTS }],
+ [
+ {
+ payload: {
+ newIssues: [mockParsedHeadIssues[0]],
+ resolvedIssues: [mockParsedBaseIssues[0]],
+ },
+ type: 'receiveReportsSuccess',
+ },
+ ],
+ done,
+ );
+ });
+ });
+
+ describe('on error', () => {
+ it('commits REQUEST_REPORTS and dispatches receiveReportsError', done => {
+ mock.onGet(`${TEST_HOST}/head.json`).reply(500);
+
+ testAction(
+ actions.fetchReports,
+ null,
+ localState,
+ [{ type: types.REQUEST_REPORTS }],
+ [{ type: 'receiveReportsError' }],
+ done,
+ );
+ });
+ });
+
+ describe('with no base path', () => {
+ it('commits REQUEST_REPORTS and dispatches receiveReportsError', done => {
+ localState.basePath = null;
+
+ testAction(
+ actions.fetchReports,
+ null,
+ localState,
+ [{ type: types.REQUEST_REPORTS }],
+ [{ type: 'receiveReportsError' }],
+ done,
+ );
+ });
+ });
+ });
+
+ describe('receiveReportsSuccess', () => {
+ it('commits RECEIVE_REPORTS_SUCCESS', done => {
+ const data = { issues: [] };
+
+ testAction(
+ actions.receiveReportsSuccess,
+ data,
+ localState,
+ [{ type: types.RECEIVE_REPORTS_SUCCESS, payload: data }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveReportsError', () => {
+ it('commits RECEIVE_REPORTS_ERROR', done => {
+ testAction(
+ actions.receiveReportsError,
+ null,
+ localState,
+ [{ type: types.RECEIVE_REPORTS_ERROR }],
+ [],
+ done,
+ );
+ });
+ });
+});
diff --git a/spec/frontend/reports/codequality_report/store/getters_spec.js b/spec/frontend/reports/codequality_report/store/getters_spec.js
new file mode 100644
index 00000000000..a641e2fe74f
--- /dev/null
+++ b/spec/frontend/reports/codequality_report/store/getters_spec.js
@@ -0,0 +1,95 @@
+import * as getters from '~/reports/codequality_report/store/getters';
+import createStore from '~/reports/codequality_report/store';
+import { LOADING, ERROR, SUCCESS } from '~/reports/constants';
+
+describe('Codequality reports store getters', () => {
+ let localState;
+ let localStore;
+
+ beforeEach(() => {
+ localStore = createStore();
+ localState = localStore.state;
+ });
+
+ describe('hasCodequalityIssues', () => {
+ describe('when there are issues', () => {
+ it('returns true', () => {
+ localState.newIssues = [{ reason: 'repetitive code' }];
+ localState.resolvedIssues = [];
+
+ expect(getters.hasCodequalityIssues(localState)).toEqual(true);
+
+ localState.newIssues = [];
+ localState.resolvedIssues = [{ reason: 'repetitive code' }];
+
+ expect(getters.hasCodequalityIssues(localState)).toEqual(true);
+ });
+ });
+
+ describe('when there are no issues', () => {
+ it('returns false when there are no issues', () => {
+ expect(getters.hasCodequalityIssues(localState)).toEqual(false);
+ });
+ });
+ });
+
+ describe('codequalityStatus', () => {
+ describe('when loading', () => {
+ it('returns loading status', () => {
+ localState.isLoading = true;
+
+ expect(getters.codequalityStatus(localState)).toEqual(LOADING);
+ });
+ });
+
+ describe('on error', () => {
+ it('returns error status', () => {
+ localState.hasError = true;
+
+ expect(getters.codequalityStatus(localState)).toEqual(ERROR);
+ });
+ });
+
+ describe('when successfully loaded', () => {
+ it('returns error status', () => {
+ expect(getters.codequalityStatus(localState)).toEqual(SUCCESS);
+ });
+ });
+ });
+
+ describe('codequalityText', () => {
+ it.each`
+ resolvedIssues | newIssues | expectedText
+ ${0} | ${0} | ${'No changes to code quality'}
+ ${0} | ${1} | ${'Code quality degraded on 1 point'}
+ ${2} | ${0} | ${'Code quality improved on 2 points'}
+ ${1} | ${2} | ${'Code quality improved on 1 point and degraded on 2 points'}
+ `(
+ 'returns a summary containing $resolvedIssues resolved issues and $newIssues new issues',
+ ({ newIssues, resolvedIssues, expectedText }) => {
+ localState.newIssues = new Array(newIssues).fill({ reason: 'Repetitive code' });
+ localState.resolvedIssues = new Array(resolvedIssues).fill({ reason: 'Repetitive code' });
+
+ expect(getters.codequalityText(localState)).toEqual(expectedText);
+ },
+ );
+ });
+
+ describe('codequalityPopover', () => {
+ describe('when head report is available but base report is not', () => {
+ it('returns a popover with a documentation link', () => {
+ localState.headPath = 'head.json';
+ localState.basePath = undefined;
+ localState.helpPath = 'codequality_help.html';
+
+ expect(getters.codequalityPopover(localState).title).toEqual(
+ 'Base pipeline codequality artifact not found',
+ );
+ expect(getters.codequalityPopover(localState).content).toContain(
+ 'Learn more about codequality reports',
+ 'href="codequality_help.html"',
+ );
+ });
+ });
+ });
+});
diff --git a/spec/frontend/reports/codequality_report/store/mutations_spec.js b/spec/frontend/reports/codequality_report/store/mutations_spec.js
new file mode 100644
index 00000000000..658abf3088c
--- /dev/null
+++ b/spec/frontend/reports/codequality_report/store/mutations_spec.js
@@ -0,0 +1,80 @@
+import mutations from '~/reports/codequality_report/store/mutations';
+import createStore from '~/reports/codequality_report/store';
+
+describe('Codequality Reports mutations', () => {
+ let localState;
+ let localStore;
+
+ beforeEach(() => {
+ localStore = createStore();
+ localState = localStore.state;
+ });
+
+ describe('SET_PATHS', () => {
+ it('sets paths to given values', () => {
+ const basePath = 'base.json';
+ const headPath = 'head.json';
+ const baseBlobPath = 'base/blob/path/';
+ const headBlobPath = 'head/blob/path/';
+ const helpPath = 'help.html';
+
+ mutations.SET_PATHS(localState, {
+ basePath,
+ headPath,
+ baseBlobPath,
+ headBlobPath,
+ helpPath,
+ });
+
+ expect(localState.basePath).toEqual(basePath);
+ expect(localState.headPath).toEqual(headPath);
+ expect(localState.baseBlobPath).toEqual(baseBlobPath);
+ expect(localState.headBlobPath).toEqual(headBlobPath);
+ expect(localState.helpPath).toEqual(helpPath);
+ });
+ });
+
+ describe('REQUEST_REPORTS', () => {
+ it('sets isLoading to true', () => {
+ mutations.REQUEST_REPORTS(localState);
+
+ expect(localState.isLoading).toEqual(true);
+ });
+ });
+
+ describe('RECEIVE_REPORTS_SUCCESS', () => {
+ it('sets isLoading to false', () => {
+ mutations.RECEIVE_REPORTS_SUCCESS(localState, {});
+
+ expect(localState.isLoading).toEqual(false);
+ });
+
+ it('sets hasError to false', () => {
+ mutations.RECEIVE_REPORTS_SUCCESS(localState, {});
+
+ expect(localState.hasError).toEqual(false);
+ });
+
+ it('sets newIssues and resolvedIssues from response data', () => {
+ const data = { newIssues: [{ id: 1 }], resolvedIssues: [{ id: 2 }] };
+ mutations.RECEIVE_REPORTS_SUCCESS(localState, data);
+
+ expect(localState.newIssues).toEqual(data.newIssues);
+ expect(localState.resolvedIssues).toEqual(data.resolvedIssues);
+ });
+ });
+
+ describe('RECEIVE_REPORTS_ERROR', () => {
+ it('sets isLoading to false', () => {
+ mutations.RECEIVE_REPORTS_ERROR(localState);
+
+ expect(localState.isLoading).toEqual(false);
+ });
+
+ it('sets hasError to true', () => {
+ mutations.RECEIVE_REPORTS_ERROR(localState);
+
+ expect(localState.hasError).toEqual(true);
+ });
+ });
+});
diff --git a/spec/frontend/reports/codequality_report/store/utils/codequality_comparison_spec.js b/spec/frontend/reports/codequality_report/store/utils/codequality_comparison_spec.js
new file mode 100644
index 00000000000..5dd69d3c4d4
--- /dev/null
+++ b/spec/frontend/reports/codequality_report/store/utils/codequality_comparison_spec.js
@@ -0,0 +1,139 @@
+import {
+ parseCodeclimateMetrics,
+ doCodeClimateComparison,
+} from '~/reports/codequality_report/store/utils/codequality_comparison';
+import { baseIssues, mockParsedHeadIssues, mockParsedBaseIssues } from '../../mock_data';
+
+jest.mock('~/reports/codequality_report/workers/codequality_comparison_worker', () => {
+ let mockPostMessageCallback;
+ return jest.fn().mockImplementation(() => {
+ return {
+ addEventListener: (_, callback) => {
+ mockPostMessageCallback = callback;
+ },
+ postMessage: data => {
+ if (!data.headIssues) return mockPostMessageCallback({ data: {} });
+ if (!data.baseIssues) throw new Error();
+ const key = 'fingerprint';
+ return mockPostMessageCallback({
+ data: {
+ newIssues: data.headIssues.filter(
+ item => !data.baseIssues.find(el => el[key] === item[key]),
+ ),
+ resolvedIssues: data.baseIssues.filter(
+ item => !data.headIssues.find(el => el[key] === item[key]),
+ ),
+ },
+ });
+ },
+ };
+ });
+});
+
+describe('Codequality report store utils', () => {
+ let result;
+
+ describe('parseCodeclimateMetrics', () => {
+ it('should parse the received issues', () => {
+ [result] = parseCodeclimateMetrics(baseIssues, 'path');
+
+ expect(result.name).toEqual(baseIssues[0].check_name);
+ expect(result.path).toEqual(baseIssues[0].location.path);
+ expect(result.line).toEqual(baseIssues[0].location.lines.begin);
+ });
+
+ describe('when an issue has no location or path', () => {
+ const issue = { description: 'Insecure Dependency' };
+
+ beforeEach(() => {
+ [result] = parseCodeclimateMetrics([issue], 'path');
+ });
+
+ it('is parsed', () => {
+ expect(result.name).toEqual(issue.description);
+ });
+ });
+
+ describe('when an issue has a path but no line', () => {
+ const issue = { description: 'Insecure Dependency', location: { path: 'Gemfile.lock' } };
+
+ beforeEach(() => {
+ [result] = parseCodeclimateMetrics([issue], 'path');
+ });
+
+ it('is parsed', () => {
+ expect(result.name).toEqual(issue.description);
+ expect(result.path).toEqual(issue.location.path);
+ expect(result.urlPath).toEqual(`path/${issue.location.path}`);
+ });
+ });
+
+ describe('when an issue has a line nested in positions', () => {
+ const issue = {
+ description: 'Insecure Dependency',
+ location: {
+ path: 'Gemfile.lock',
+ positions: { begin: { line: 84 } },
+ },
+ };
+
+ beforeEach(() => {
+ [result] = parseCodeclimateMetrics([issue], 'path');
+ });
+
+ it('is parsed', () => {
+ expect(result.name).toEqual(issue.description);
+ expect(result.path).toEqual(issue.location.path);
+ expect(result.urlPath).toEqual(
+ `path/${issue.location.path}#L${issue.location.positions.begin.line}`,
+ );
+ });
+ });
+
+ describe('with an empty issue array', () => {
+ beforeEach(() => {
+ result = parseCodeclimateMetrics([], 'path');
+ });
+
+ it('returns an empty array', () => {
+ expect(result).toEqual([]);
+ });
+ });
+ });
+
+ describe('doCodeClimateComparison', () => {
+ describe('when the comparison worker finds changed issues', () => {
+ beforeEach(async () => {
+ result = await doCodeClimateComparison(mockParsedHeadIssues, mockParsedBaseIssues);
+ });
+
+ it('returns the new and resolved issues', () => {
+ expect(result.resolvedIssues[0]).toEqual(mockParsedBaseIssues[0]);
+ expect(result.newIssues[0]).toEqual(mockParsedHeadIssues[0]);
+ });
+ });
+
+ describe('when the comparison worker finds no changed issues', () => {
+ beforeEach(async () => {
+ result = await doCodeClimateComparison([], []);
+ });
+
+ it('returns the empty issue arrays', () => {
+ expect(result.newIssues).toEqual([]);
+ expect(result.resolvedIssues).toEqual([]);
+ });
+ });
+
+ describe('when the comparison worker is given malformed data', () => {
+ it('rejects the promise', () => {
+ return expect(doCodeClimateComparison(null)).rejects.toEqual({});
+ });
+ });
+
+ describe('when the comparison worker encounters an error', () => {
+ it('rejects the promise and throws an error', () => {
+ return expect(doCodeClimateComparison([], null)).rejects.toThrow();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/reports/components/grouped_test_reports_app_spec.js b/spec/frontend/reports/components/grouped_test_reports_app_spec.js
index 6a402277f52..017e0335569 100644
--- a/spec/frontend/reports/components/grouped_test_reports_app_spec.js
+++ b/spec/frontend/reports/components/grouped_test_reports_app_spec.js
@@ -15,20 +15,29 @@ localVue.use(Vuex);
describe('Grouped test reports app', () => {
const endpoint = 'endpoint.json';
+ const pipelinePath = '/path/to/pipeline';
const Component = localVue.extend(GroupedTestReportsApp);
let wrapper;
let mockStore;
- const mountComponent = () => {
+ const mountComponent = ({
+ glFeatures = { junitPipelineView: false },
+ props = { pipelinePath },
+ } = {}) => {
wrapper = mount(Component, {
store: mockStore,
localVue,
propsData: {
endpoint,
+ pipelinePath,
+ ...props,
},
methods: {
fetchReports: () => {},
},
+ provide: {
+ glFeatures,
+ },
});
};
@@ -39,6 +48,7 @@ describe('Grouped test reports app', () => {
};
const findHeader = () => wrapper.find('[data-testid="report-section-code-text"]');
+ const findFullTestReportLink = () => wrapper.find('[data-testid="group-test-reports-full-link"]');
const findSummaryDescription = () => wrapper.find('[data-testid="test-summary-row-description"]');
const findIssueDescription = () => wrapper.find('[data-testid="test-issue-body-description"]');
const findAllIssueDescriptions = () =>
@@ -67,6 +77,39 @@ describe('Grouped test reports app', () => {
});
});
+ describe('`View full report` button', () => {
+ it('should not render the full test report link', () => {
+ expect(findFullTestReportLink().exists()).toBe(false);
+ });
+
+ describe('With junitPipelineView feature flag enabled', () => {
+ beforeEach(() => {
+ mountComponent({ glFeatures: { junitPipelineView: true } });
+ });
+
+ it('should render the full test report link', () => {
+ const fullTestReportLink = findFullTestReportLink();
+
+ expect(fullTestReportLink.exists()).toBe(true);
+ expect(pipelinePath).not.toBe('');
+ expect(fullTestReportLink.attributes('href')).toBe(`${pipelinePath}/test_report`);
+ });
+ });
+
+ describe('Without a pipelinePath', () => {
+ beforeEach(() => {
+ mountComponent({
+ glFeatures: { junitPipelineView: true },
+ props: { pipelinePath: '' },
+ });
+ });
+
+ it('should not render the full test report link', () => {
+ expect(findFullTestReportLink().exists()).toBe(false);
+ });
+ });
+ });
+
describe('with new failed result', () => {
beforeEach(() => {
setReports(newFailedTestReports);
diff --git a/spec/frontend/reports/components/report_section_spec.js b/spec/frontend/reports/components/report_section_spec.js
index eaeb074acaf..a620b5d9afc 100644
--- a/spec/frontend/reports/components/report_section_spec.js
+++ b/spec/frontend/reports/components/report_section_spec.js
@@ -1,9 +1,11 @@
import Vue from 'vue';
+import { shallowMount } from '@vue/test-utils';
import mountComponent, { mountComponentWithSlots } from 'helpers/vue_mount_component_helper';
import reportSection from '~/reports/components/report_section.vue';
describe('Report section', () => {
let vm;
+ let wrapper;
const ReportSection = Vue.extend(reportSection);
const resolvedIssues = [
@@ -16,22 +18,41 @@ describe('Report section', () => {
},
];
+ const defaultProps = {
+ component: '',
+ status: 'SUCCESS',
+ loadingText: 'Loading codeclimate report',
+ errorText: 'foo',
+ successText: 'Code quality improved on 1 point and degraded on 1 point',
+ resolvedIssues,
+ hasIssues: false,
+ alwaysOpen: false,
+ };
+
+ const createComponent = props => {
+ wrapper = shallowMount(reportSection, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+ return wrapper;
+ };
+
afterEach(() => {
- vm.$destroy();
+ if (vm) {
+ vm.$destroy();
+ vm = null;
+ }
+ if (wrapper) {
+ wrapper.destroy();
+ wrapper = null;
+ }
});
describe('computed', () => {
beforeEach(() => {
- vm = mountComponent(ReportSection, {
- component: '',
- status: 'SUCCESS',
- loadingText: 'Loading codeclimate report',
- errorText: 'foo',
- successText: 'Code quality improved on 1 point and degraded on 1 point',
- resolvedIssues,
- hasIssues: false,
- alwaysOpen: false,
- });
+ vm = mountComponent(ReportSection, defaultProps);
});
describe('isCollapsible', () => {
@@ -105,12 +126,7 @@ describe('Report section', () => {
describe('with success status', () => {
beforeEach(() => {
vm = mountComponent(ReportSection, {
- component: '',
- status: 'SUCCESS',
- loadingText: 'Loading codeclimate report',
- errorText: 'foo',
- successText: 'Code quality improved on 1 point and degraded on 1 point',
- resolvedIssues,
+ ...defaultProps,
hasIssues: true,
});
});
@@ -160,6 +176,50 @@ describe('Report section', () => {
});
});
+ describe('snowplow events', () => {
+ it('does emit an event on issue toggle if the shouldEmitToggleEvent prop does exist', done => {
+ createComponent({ hasIssues: true, shouldEmitToggleEvent: true });
+
+ expect(wrapper.emitted().toggleEvent).toBeUndefined();
+
+ wrapper.vm.$el.querySelector('button').click();
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ expect(wrapper.emitted().toggleEvent).toHaveLength(1);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not emit an event on issue toggle if the shouldEmitToggleEvent prop does not exist', done => {
+ createComponent({ hasIssues: true });
+
+ expect(wrapper.emitted().toggleEvent).toBeUndefined();
+
+ wrapper.vm.$el.querySelector('button').click();
+ return wrapper.vm
+ .$nextTick()
+ .then(() => {
+ expect(wrapper.emitted().toggleEvent).toBeUndefined();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not emit an event if always-open is set to true', done => {
+ createComponent({ alwaysOpen: true, hasIssues: true, shouldEmitToggleEvent: true });
+
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ expect(wrapper.emitted().toggleEvent).toBeUndefined();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
describe('with failed request', () => {
it('should render error indicator', () => {
vm = mountComponent(ReportSection, {
@@ -199,7 +259,7 @@ describe('Report section', () => {
});
describe('Success and Error slots', () => {
- const createComponent = status => {
+ const createComponentWithSlots = status => {
vm = mountComponentWithSlots(ReportSection, {
props: {
status,
@@ -214,7 +274,7 @@ describe('Report section', () => {
};
it('only renders success slot when status is "SUCCESS"', () => {
- createComponent('SUCCESS');
+ createComponentWithSlots('SUCCESS');
expect(vm.$el.textContent.trim()).toContain('This is a success');
expect(vm.$el.textContent.trim()).not.toContain('This is an error');
@@ -222,7 +282,7 @@ describe('Report section', () => {
});
it('only renders error slot when status is "ERROR"', () => {
- createComponent('ERROR');
+ createComponentWithSlots('ERROR');
expect(vm.$el.textContent.trim()).toContain('This is an error');
expect(vm.$el.textContent.trim()).not.toContain('This is a success');
@@ -230,7 +290,7 @@ describe('Report section', () => {
});
it('only renders loading slot when status is "LOADING"', () => {
- createComponent('LOADING');
+ createComponentWithSlots('LOADING');
expect(vm.$el.textContent.trim()).toContain('This is loading');
expect(vm.$el.textContent.trim()).not.toContain('This is an error');
diff --git a/spec/frontend/reports/components/summary_row_spec.js b/spec/frontend/reports/components/summary_row_spec.js
index cb0cc025e80..85c68ed069b 100644
--- a/spec/frontend/reports/components/summary_row_spec.js
+++ b/spec/frontend/reports/components/summary_row_spec.js
@@ -1,10 +1,8 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
-import component from '~/reports/components/summary_row.vue';
+import { mount } from '@vue/test-utils';
+import SummaryRow from '~/reports/components/summary_row.vue';
describe('Summary row', () => {
- const Component = Vue.extend(component);
- let vm;
+ let wrapper;
const props = {
summary: 'SAST detected 1 new vulnerability and 1 fixed vulnerability',
@@ -15,23 +13,42 @@ describe('Summary row', () => {
statusIcon: 'warning',
};
- beforeEach(() => {
- vm = mountComponent(Component, props);
- });
+ const createComponent = ({ propsData = {}, slots = {} } = {}) => {
+ wrapper = mount(SummaryRow, {
+ propsData: {
+ ...props,
+ ...propsData,
+ },
+ slots,
+ });
+ };
+
+ const findSummary = () => wrapper.find('.report-block-list-issue-description-text');
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
+ wrapper = null;
});
it('renders provided summary', () => {
- expect(
- vm.$el.querySelector('.report-block-list-issue-description-text').textContent.trim(),
- ).toEqual(props.summary);
+ createComponent();
+ expect(findSummary().text()).toEqual(props.summary);
});
it('renders provided icon', () => {
- expect(vm.$el.querySelector('.report-block-list-icon span').classList).toContain(
+ createComponent();
+ expect(wrapper.find('.report-block-list-icon span').classes()).toContain(
'js-ci-status-icon-warning',
);
});
+
+ describe('summary slot', () => {
+ it('replaces the summary prop', () => {
+ const summarySlotContent = 'Summary slot content';
+ createComponent({ slots: { summary: summarySlotContent } });
+
+ expect(wrapper.text()).not.toContain(props.summary);
+ expect(findSummary().text()).toEqual(summarySlotContent);
+ });
+ });
});
diff --git a/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap b/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap
index 97597ed8063..ac60fc4917d 100644
--- a/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap
+++ b/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap
@@ -1,5 +1,58 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`Repository table row component renders a symlink table row 1`] = `
+<tr
+ class="tree-item"
+>
+ <td
+ class="tree-item-file-name cursor-default position-relative"
+ >
+ <a
+ class="tree-item-link str-truncated"
+ data-qa-selector="file_name_link"
+ href="https://test.com"
+ >
+ <file-icon-stub
+ class="mr-1 position-relative text-secondary"
+ cssclasses="position-relative file-icon"
+ filemode="120000"
+ filename="test"
+ size="16"
+ />
+ <span
+ class="position-relative"
+ >
+ test
+ </span>
+ </a>
+
+ <!---->
+
+ <!---->
+
+ <!---->
+ </td>
+
+ <td
+ class="d-none d-sm-table-cell tree-commit cursor-default"
+ >
+ <gl-skeleton-loading-stub
+ class="h-auto"
+ lines="1"
+ />
+ </td>
+
+ <td
+ class="tree-time-ago text-right cursor-default"
+ >
+ <gl-skeleton-loading-stub
+ class="ml-auto h-auto w-50"
+ lines="1"
+ />
+ </td>
+</tr>
+`;
+
exports[`Repository table row component renders table row 1`] = `
<tr
class="tree-item"
@@ -15,6 +68,7 @@ exports[`Repository table row component renders table row 1`] = `
<file-icon-stub
class="mr-1 position-relative text-secondary"
cssclasses="position-relative file-icon"
+ filemode=""
filename="test"
size="16"
/>
@@ -67,6 +121,7 @@ exports[`Repository table row component renders table row for path with special
<file-icon-stub
class="mr-1 position-relative text-secondary"
cssclasses="position-relative file-icon"
+ filemode=""
filename="test"
size="16"
/>
diff --git a/spec/frontend/repository/components/table/index_spec.js b/spec/frontend/repository/components/table/index_spec.js
index 9db90839b29..ed50f292b8c 100644
--- a/spec/frontend/repository/components/table/index_spec.js
+++ b/spec/frontend/repository/components/table/index_spec.js
@@ -23,6 +23,15 @@ const MOCK_BLOBS = [
type: 'blob',
webUrl: 'http://test.com',
},
+ {
+ id: '125abc',
+ sha: '125abc',
+ flatPath: 'blob3',
+ name: 'blob3.md',
+ type: 'blob',
+ webUrl: 'http://test.com',
+ mode: '120000',
+ },
];
function factory({ path, isLoading = false, entries = {} }) {
@@ -74,7 +83,9 @@ describe('Repository table component', () => {
},
});
- expect(vm.find(TableRow).exists()).toBe(true);
- expect(vm.findAll(TableRow).length).toBe(2);
+ const rows = vm.findAll(TableRow);
+
+ expect(rows.length).toEqual(3);
+ expect(rows.at(2).attributes().mode).toEqual('120000');
});
});
diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js
index 800a7e586a8..767b117c798 100644
--- a/spec/frontend/repository/components/table/row_spec.js
+++ b/spec/frontend/repository/components/table/row_spec.js
@@ -2,6 +2,7 @@ import { shallowMount, RouterLinkStub } from '@vue/test-utils';
import { GlBadge, GlLink, GlIcon } from '@gitlab/ui';
import TableRow from '~/repository/components/table/row.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
+import { FILE_SYMLINK_MODE } from '~/vue_shared/constants';
let vm;
let $router;
@@ -48,6 +49,21 @@ describe('Repository table row component', () => {
});
});
+ it('renders a symlink table row', () => {
+ factory({
+ id: '1',
+ sha: '123',
+ path: 'test',
+ type: 'blob',
+ currentPath: '/',
+ mode: FILE_SYMLINK_MODE,
+ });
+
+ return vm.vm.$nextTick().then(() => {
+ expect(vm.element).toMatchSnapshot();
+ });
+ });
+
it('renders table row for path with special character', () => {
factory({
id: '1',
diff --git a/spec/frontend/repository/components/web_ide_link_spec.js b/spec/frontend/repository/components/web_ide_link_spec.js
new file mode 100644
index 00000000000..59e1a4fd719
--- /dev/null
+++ b/spec/frontend/repository/components/web_ide_link_spec.js
@@ -0,0 +1,51 @@
+import WebIdeLink from '~/repository/components/web_ide_link.vue';
+import { mount } from '@vue/test-utils';
+
+describe('Web IDE link component', () => {
+ let wrapper;
+
+ function createComponent(props) {
+ wrapper = mount(WebIdeLink, {
+ propsData: { ...props },
+ mocks: {
+ $route: {
+ params: {},
+ },
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders link to the Web IDE for a project if only projectPath is given', () => {
+ createComponent({ projectPath: 'gitlab-org/gitlab', refSha: 'master' });
+
+ expect(wrapper.attributes('href')).toBe('/-/ide/project/gitlab-org/gitlab/edit/master/-/');
+ expect(wrapper.text()).toBe('Web IDE');
+ });
+
+ it('renders link to the Web IDE for a project even if both projectPath and forkPath are given', () => {
+ createComponent({
+ projectPath: 'gitlab-org/gitlab',
+ refSha: 'master',
+ forkPath: 'my-namespace/gitlab',
+ });
+
+ expect(wrapper.attributes('href')).toBe('/-/ide/project/gitlab-org/gitlab/edit/master/-/');
+ expect(wrapper.text()).toBe('Web IDE');
+ });
+
+ it('renders link to the forked project if it exists and cannot write to the repo', () => {
+ createComponent({
+ projectPath: 'gitlab-org/gitlab',
+ refSha: 'master',
+ forkPath: 'my-namespace/gitlab',
+ canPushCode: false,
+ });
+
+ expect(wrapper.attributes('href')).toBe('/-/ide/project/my-namespace/gitlab/edit/master/-/');
+ expect(wrapper.text()).toBe('Edit fork in Web IDE');
+ });
+});
diff --git a/spec/frontend/repository/utils/dom_spec.js b/spec/frontend/repository/utils/dom_spec.js
index 0b61161c9d0..e8b0565868e 100644
--- a/spec/frontend/repository/utils/dom_spec.js
+++ b/spec/frontend/repository/utils/dom_spec.js
@@ -1,5 +1,6 @@
import { setHTMLFixture } from '../../helpers/fixtures';
import { updateElementsVisibility, updateFormAction } from '~/repository/utils/dom';
+import { TEST_HOST } from 'helpers/test_constants';
describe('updateElementsVisibility', () => {
it('adds hidden class', () => {
@@ -31,7 +32,7 @@ describe('updateFormAction', () => {
updateFormAction('.js-test', '/gitlab/create', path);
expect(document.querySelector('.js-test').action).toBe(
- `http://localhost/gitlab/create/${path.replace(/^\//, '')}`,
+ `${TEST_HOST}/gitlab/create/${path.replace(/^\//, '')}`,
);
});
});
diff --git a/spec/frontend/global_search_input_spec.js b/spec/frontend/search_autocomplete_spec.js
index 8c00ea5f193..05b36474548 100644
--- a/spec/frontend/global_search_input_spec.js
+++ b/spec/frontend/search_autocomplete_spec.js
@@ -2,30 +2,24 @@
import $ from 'jquery';
import '~/gl_dropdown';
-import initGlobalSearchInput from '~/global_search_input';
+import initSearchAutocomplete from '~/search_autocomplete';
import '~/lib/utils/common_utils';
+import axios from '~/lib/utils/axios_utils';
+import AxiosMockAdapter from 'axios-mock-adapter';
-describe('Global search input dropdown', () => {
+describe('Search autocomplete dropdown', () => {
let widget = null;
const userName = 'root';
-
const userId = 1;
-
const dashboardIssuesPath = '/dashboard/issues';
-
const dashboardMRsPath = '/dashboard/merge_requests';
-
const projectIssuesPath = '/gitlab-org/gitlab-foss/issues';
-
const projectMRsPath = '/gitlab-org/gitlab-foss/-/merge_requests';
-
const groupIssuesPath = '/groups/gitlab-org/-/issues';
-
const groupMRsPath = '/groups/gitlab-org/-/merge_requests';
-
+ const autocompletePath = '/search/autocomplete';
const projectName = 'GitLab Community Edition';
-
const groupName = 'Gitlab Org';
const removeBodyAttributes = () => {
@@ -112,15 +106,16 @@ describe('Global search input dropdown', () => {
expect(list.find(mrsIHaveCreatedLink).text()).toBe("Merge requests I've created");
};
- preloadFixtures('static/global_search_input.html');
+ preloadFixtures('static/search_autocomplete.html');
beforeEach(() => {
- loadFixtures('static/global_search_input.html');
+ loadFixtures('static/search_autocomplete.html');
window.gon = {};
window.gon.current_user_id = userId;
window.gon.current_username = userName;
+ window.gl = window.gl || (window.gl = {});
- return (widget = initGlobalSearchInput());
+ return (widget = initSearchAutocomplete({ autocompletePath }));
});
afterEach(() => {
@@ -183,31 +178,105 @@ describe('Global search input dropdown', () => {
widget.wrap.trigger($.Event('keydown', { which: DOWN }));
const enterKeyEvent = $.Event('keydown', { which: ENTER });
widget.searchInput.trigger(enterKeyEvent);
+
// This does not currently catch failing behavior. For security reasons,
// browsers will not trigger default behavior (form submit, in this
// example) on JavaScript-created keypresses.
expect(submitSpy).not.toHaveBeenCalled();
});
- describe('disableDropdown', () => {
+ describe('show autocomplete results', () => {
+ beforeEach(() => {
+ widget.enableAutocomplete();
+
+ const axiosMock = new AxiosMockAdapter(axios);
+ const autocompleteUrl = new RegExp(autocompletePath);
+
+ axiosMock.onGet(autocompleteUrl).reply(200, [
+ {
+ category: 'Projects',
+ id: 1,
+ value: 'Gitlab Test',
+ label: 'Gitlab Org / Gitlab Test',
+ url: '/gitlab-org/gitlab-test',
+ avatar_url: '',
+ },
+ {
+ category: 'Groups',
+ id: 1,
+ value: 'Gitlab Org',
+ label: 'Gitlab Org',
+ url: '/gitlab-org',
+ avatar_url: '',
+ },
+ ]);
+ });
+
+ function triggerAutocomplete() {
+ return new Promise(resolve => {
+ const dropdown = widget.searchInput.data('glDropdown');
+ const filterCallback = dropdown.filter.options.callback;
+ dropdown.filter.options.callback = jest.fn(data => {
+ filterCallback(data);
+
+ resolve();
+ });
+
+ widget.searchInput.val('Gitlab');
+ widget.searchInput.triggerHandler('input');
+ });
+ }
+
+ it('suggest Projects', done => {
+ // eslint-disable-next-line promise/catch-or-return
+ triggerAutocomplete().finally(() => {
+ const list = widget.wrap.find('.dropdown-menu').find('ul');
+ const link = "a[href$='/gitlab-org/gitlab-test']";
+
+ expect(list.find(link).length).toBe(1);
+
+ done();
+ });
+
+ // Make sure jest properly acknowledge the `done` invocation
+ jest.runOnlyPendingTimers();
+ });
+
+ it('suggest Groups', done => {
+ // eslint-disable-next-line promise/catch-or-return
+ triggerAutocomplete().finally(() => {
+ const list = widget.wrap.find('.dropdown-menu').find('ul');
+ const link = "a[href$='/gitlab-org']";
+
+ expect(list.find(link).length).toBe(1);
+
+ done();
+ });
+
+ // Make sure jest properly acknowledge the `done` invocation
+ jest.runOnlyPendingTimers();
+ });
+ });
+
+ describe('disableAutocomplete', () => {
beforeEach(() => {
- widget.enableDropdown();
+ widget.enableAutocomplete();
});
it('should close the Dropdown', () => {
const toggleSpy = jest.spyOn(widget.dropdownToggle, 'dropdown');
widget.dropdown.addClass('show');
- widget.disableDropdown();
+ widget.disableAutocomplete();
expect(toggleSpy).toHaveBeenCalledWith('toggle');
});
});
- describe('enableDropdown', () => {
+ describe('enableAutocomplete', () => {
it('should open the Dropdown', () => {
const toggleSpy = jest.spyOn(widget.dropdownToggle, 'dropdown');
- widget.enableDropdown();
+ widget.enableAutocomplete();
expect(toggleSpy).toHaveBeenCalledWith('toggle');
});
diff --git a/spec/frontend/self_monitor/components/__snapshots__/self_monitor_form_spec.js.snap b/spec/frontend/self_monitor/components/__snapshots__/self_monitor_form_spec.js.snap
index f7a9827a9ad..f4ac2f57261 100644
--- a/spec/frontend/self_monitor/components/__snapshots__/self_monitor_form_spec.js.snap
+++ b/spec/frontend/self_monitor/components/__snapshots__/self_monitor_form_spec.js.snap
@@ -47,9 +47,7 @@ exports[`self monitor component When the self monitor project has not been creat
label-for="self-monitor-toggle"
>
<gl-toggle-stub
- labeloff="Toggle Status: OFF"
- labelon="Toggle Status: ON"
- labelposition="hidden"
+ labelposition="top"
name="self-monitor-toggle"
/>
</gl-form-group-stub>
diff --git a/spec/frontend/self_monitor/components/self_monitor_form_spec.js b/spec/frontend/self_monitor/components/self_monitor_form_spec.js
index 0e6abba08f3..aa6f71b6412 100644
--- a/spec/frontend/self_monitor/components/self_monitor_form_spec.js
+++ b/spec/frontend/self_monitor/components/self_monitor_form_spec.js
@@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import { GlDeprecatedButton } from '@gitlab/ui';
import SelfMonitor from '~/self_monitor/components/self_monitor_form.vue';
import { createStore } from '~/self_monitor/store';
+import { TEST_HOST } from 'helpers/test_constants';
describe('self monitor component', () => {
let wrapper;
@@ -82,7 +83,7 @@ describe('self monitor component', () => {
.find({ ref: 'selfMonitoringFormText' })
.find('a')
.attributes('href'),
- ).toEqual('http://localhost/instance-administrators-random/gitlab-self-monitoring');
+ ).toEqual(`${TEST_HOST}/instance-administrators-random/gitlab-self-monitoring`);
});
});
});
diff --git a/spec/frontend/sidebar/__snapshots__/confidential_issue_sidebar_spec.js.snap b/spec/frontend/sidebar/__snapshots__/confidential_issue_sidebar_spec.js.snap
index cf7832f3948..da571af3a0d 100644
--- a/spec/frontend/sidebar/__snapshots__/confidential_issue_sidebar_spec.js.snap
+++ b/spec/frontend/sidebar/__snapshots__/confidential_issue_sidebar_spec.js.snap
@@ -3,6 +3,7 @@
exports[`Confidential Issue Sidebar Block renders for confidential = false and isEditable = false 1`] = `
<div
class="block issuable-sidebar-item confidentiality"
+ iid=""
>
<div
class="sidebar-collapsed-icon"
@@ -35,6 +36,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i
<div
class="no-value sidebar-item-value"
+ data-testid="not-confidential"
>
<icon-stub
aria-hidden="true"
@@ -55,6 +57,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i
exports[`Confidential Issue Sidebar Block renders for confidential = false and isEditable = true 1`] = `
<div
class="block issuable-sidebar-item confidentiality"
+ iid=""
>
<div
class="sidebar-collapsed-icon"
@@ -95,6 +98,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i
<div
class="no-value sidebar-item-value"
+ data-testid="not-confidential"
>
<icon-stub
aria-hidden="true"
@@ -115,6 +119,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i
exports[`Confidential Issue Sidebar Block renders for confidential = true and isEditable = false 1`] = `
<div
class="block issuable-sidebar-item confidentiality"
+ iid=""
>
<div
class="sidebar-collapsed-icon"
@@ -167,6 +172,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = true and is
exports[`Confidential Issue Sidebar Block renders for confidential = true and isEditable = true 1`] = `
<div
class="block issuable-sidebar-item confidentiality"
+ iid=""
>
<div
class="sidebar-collapsed-icon"
diff --git a/spec/frontend/sidebar/confidential/edit_form_buttons_spec.js b/spec/frontend/sidebar/confidential/edit_form_buttons_spec.js
index acdfb5139bf..15493d3087f 100644
--- a/spec/frontend/sidebar/confidential/edit_form_buttons_spec.js
+++ b/spec/frontend/sidebar/confidential/edit_form_buttons_spec.js
@@ -1,16 +1,49 @@
import { shallowMount } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
import EditFormButtons from '~/sidebar/components/confidential/edit_form_buttons.vue';
+import eventHub from '~/sidebar/event_hub';
+import createStore from '~/notes/stores';
+import waitForPromises from 'helpers/wait_for_promises';
+import flash from '~/flash';
+
+jest.mock('~/sidebar/event_hub', () => ({ $emit: jest.fn() }));
+jest.mock('~/flash');
describe('Edit Form Buttons', () => {
let wrapper;
+ let store;
const findConfidentialToggle = () => wrapper.find('[data-testid="confidential-toggle"]');
- const createComponent = props => {
+ const createComponent = ({
+ props = {},
+ data = {},
+ confidentialApolloSidebar = false,
+ resolved = true,
+ }) => {
+ store = createStore();
+ if (resolved) {
+ jest.spyOn(store, 'dispatch').mockResolvedValue();
+ } else {
+ jest.spyOn(store, 'dispatch').mockRejectedValue();
+ }
+
wrapper = shallowMount(EditFormButtons, {
propsData: {
- updateConfidentialAttribute: () => {},
+ fullPath: '',
...props,
},
+ data() {
+ return {
+ isLoading: true,
+ ...data,
+ };
+ },
+ provide: {
+ glFeatures: {
+ confidentialApolloSidebar,
+ },
+ },
+ store,
});
};
@@ -19,10 +52,32 @@ describe('Edit Form Buttons', () => {
wrapper = null;
});
+ describe('when isLoading', () => {
+ beforeEach(() => {
+ createComponent({});
+
+ wrapper.vm.$store.state.noteableData.confidential = false;
+ });
+
+ it('renders "Applying" in the toggle button', () => {
+ expect(findConfidentialToggle().text()).toBe('Applying');
+ });
+
+ it('disables the toggle button', () => {
+ expect(findConfidentialToggle().attributes('disabled')).toBe('disabled');
+ });
+
+ it('finds the GlLoadingIcon', () => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ });
+ });
+
describe('when not confidential', () => {
- it('renders Turn On in the ', () => {
+ it('renders Turn On in the toggle button', () => {
createComponent({
- isConfidential: false,
+ data: {
+ isLoading: false,
+ },
});
expect(findConfidentialToggle().text()).toBe('Turn On');
@@ -30,12 +85,75 @@ describe('Edit Form Buttons', () => {
});
describe('when confidential', () => {
- it('renders on or off text based on confidentiality', () => {
+ beforeEach(() => {
createComponent({
- isConfidential: true,
+ data: {
+ isLoading: false,
+ },
});
+ wrapper.vm.$store.state.noteableData.confidential = true;
+ });
+
+ it('renders on or off text based on confidentiality', () => {
expect(findConfidentialToggle().text()).toBe('Turn Off');
});
+
+ describe('when clicking on the confidential toggle', () => {
+ it('emits updateConfidentialAttribute', () => {
+ findConfidentialToggle().trigger('click');
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('updateConfidentialAttribute');
+ });
+ });
+ });
+
+ describe('when confidentialApolloSidebar is turned on', () => {
+ const isConfidential = true;
+
+ describe('when succeeds', () => {
+ beforeEach(() => {
+ createComponent({ data: { isLoading: false }, confidentialApolloSidebar: true });
+ wrapper.vm.$store.state.noteableData.confidential = isConfidential;
+ findConfidentialToggle().trigger('click');
+ });
+
+ it('dispatches the correct action', () => {
+ expect(store.dispatch).toHaveBeenCalledWith('updateConfidentialityOnIssue', {
+ confidential: !isConfidential,
+ fullPath: '',
+ });
+ });
+
+ it('resets loading', () => {
+ return waitForPromises().then(() => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ });
+ });
+
+ it('emits close form', () => {
+ return waitForPromises().then(() => {
+ expect(eventHub.$emit).toHaveBeenCalledWith('closeConfidentialityForm');
+ });
+ });
+ });
+
+ describe('when fails', () => {
+ beforeEach(() => {
+ createComponent({
+ data: { isLoading: false },
+ confidentialApolloSidebar: true,
+ resolved: false,
+ });
+ wrapper.vm.$store.state.noteableData.confidential = isConfidential;
+ findConfidentialToggle().trigger('click');
+ });
+
+ it('calls flash with the correct message', () => {
+ expect(flash).toHaveBeenCalledWith(
+ 'Something went wrong trying to change the confidentiality of this issue',
+ );
+ });
+ });
});
});
diff --git a/spec/frontend/sidebar/confidential/edit_form_spec.js b/spec/frontend/sidebar/confidential/edit_form_spec.js
index 137019a1e1b..a22bbe5ae0d 100644
--- a/spec/frontend/sidebar/confidential/edit_form_spec.js
+++ b/spec/frontend/sidebar/confidential/edit_form_spec.js
@@ -10,6 +10,8 @@ describe('Edit Form Dropdown', () => {
wrapper = shallowMount(EditForm, {
propsData: {
...props,
+ isLoading: false,
+ fullPath: '',
},
});
};
diff --git a/spec/frontend/sidebar/confidential_issue_sidebar_spec.js b/spec/frontend/sidebar/confidential_issue_sidebar_spec.js
index fe7c3aadeeb..06cf1e6166c 100644
--- a/spec/frontend/sidebar/confidential_issue_sidebar_spec.js
+++ b/spec/frontend/sidebar/confidential_issue_sidebar_spec.js
@@ -7,6 +7,7 @@ import createFlash from '~/flash';
import RecaptchaModal from '~/vue_shared/components/recaptcha_modal.vue';
import createStore from '~/notes/stores';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
+import eventHub from '~/sidebar/event_hub';
jest.mock('~/flash');
jest.mock('~/sidebar/services/sidebar_service');
@@ -15,6 +16,9 @@ describe('Confidential Issue Sidebar Block', () => {
useMockLocationHelper();
let wrapper;
+ const mutate = jest
+ .fn()
+ .mockResolvedValue({ data: { issueSetConfidential: { issue: { confidential: true } } } });
const findRecaptchaModal = () => wrapper.find(RecaptchaModal);
@@ -25,24 +29,32 @@ describe('Confidential Issue Sidebar Block', () => {
wrapper.vm
.$nextTick()
.then(() => {
- const editForm = wrapper.find(EditForm);
- const { updateConfidentialAttribute } = editForm.props();
- updateConfidentialAttribute();
+ eventHub.$emit('updateConfidentialAttribute');
})
// wait for reCAPTCHA modal to render
.then(() => wrapper.vm.$nextTick())
);
};
- const createComponent = propsData => {
+ const createComponent = ({ propsData, data = {} }) => {
const store = createStore();
const service = new SidebarService();
wrapper = shallowMount(ConfidentialIssueSidebar, {
store,
+ data() {
+ return data;
+ },
propsData: {
service,
+ iid: '',
+ fullPath: '',
...propsData,
},
+ mocks: {
+ $apollo: {
+ mutate,
+ },
+ },
});
};
@@ -60,7 +72,9 @@ describe('Confidential Issue Sidebar Block', () => {
'renders for confidential = $confidential and isEditable = $isEditable',
({ confidential, isEditable }) => {
createComponent({
- isEditable,
+ propsData: {
+ isEditable,
+ },
});
wrapper.vm.$store.state.noteableData.confidential = confidential;
@@ -73,7 +87,9 @@ describe('Confidential Issue Sidebar Block', () => {
describe('if editable', () => {
beforeEach(() => {
createComponent({
- isEditable: true,
+ propsData: {
+ isEditable: true,
+ },
});
wrapper.vm.$store.state.noteableData.confidential = true;
});
diff --git a/spec/frontend/snippets/components/edit_spec.js b/spec/frontend/snippets/components/edit_spec.js
index 83f46dd347f..d2265dfd506 100644
--- a/spec/frontend/snippets/components/edit_spec.js
+++ b/spec/frontend/snippets/components/edit_spec.js
@@ -1,9 +1,8 @@
import { shallowMount } from '@vue/test-utils';
-import axios from '~/lib/utils/axios_utils';
import Flash from '~/flash';
import { GlLoadingIcon } from '@gitlab/ui';
-import { joinPaths, redirectTo } from '~/lib/utils/url_utility';
+import { redirectTo } from '~/lib/utils/url_utility';
import SnippetEditApp from '~/snippets/components/edit.vue';
import SnippetDescriptionEdit from '~/snippets/components/snippet_description_edit.vue';
@@ -16,25 +15,17 @@ import { SNIPPET_CREATE_MUTATION_ERROR, SNIPPET_UPDATE_MUTATION_ERROR } from '~/
import UpdateSnippetMutation from '~/snippets/mutations/updateSnippet.mutation.graphql';
import CreateSnippetMutation from '~/snippets/mutations/createSnippet.mutation.graphql';
-import AxiosMockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import { ApolloMutation } from 'vue-apollo';
jest.mock('~/lib/utils/url_utility', () => ({
- getBaseURL: jest.fn().mockReturnValue('foo/'),
redirectTo: jest.fn().mockName('redirectTo'),
- joinPaths: jest
- .fn()
- .mockName('joinPaths')
- .mockReturnValue('contentApiURL'),
}));
jest.mock('~/flash');
let flashSpy;
-const contentMock = 'Foo Bar';
-const rawPathMock = '/foo/bar';
const rawProjectPathMock = '/project/path';
const newlyEditedSnippetUrl = 'http://foo.bar';
const apiError = { message: 'Ufff' };
@@ -43,15 +34,27 @@ const mutationError = 'Bummer';
const attachedFilePath1 = 'foo/bar';
const attachedFilePath2 = 'alpha/beta';
+const actionWithContent = {
+ content: 'Foo Bar',
+};
+const actionWithoutContent = {
+ content: '',
+};
+
const defaultProps = {
snippetGid: 'gid://gitlab/PersonalSnippet/42',
markdownPreviewPath: 'http://preview.foo.bar',
markdownDocsPath: 'http://docs.foo.bar',
};
+const defaultData = {
+ blobsActions: {
+ ...actionWithContent,
+ action: '',
+ },
+};
describe('Snippet Edit app', () => {
let wrapper;
- let axiosMock;
const resolveMutate = jest.fn().mockResolvedValue({
data: {
@@ -156,18 +159,21 @@ describe('Snippet Edit app', () => {
});
it.each`
- title | content | expectation
- ${''} | ${''} | ${true}
- ${'foo'} | ${''} | ${true}
- ${''} | ${'foo'} | ${true}
- ${'foo'} | ${'bar'} | ${false}
+ title | blobsActions | expectation
+ ${''} | ${{}} | ${true}
+ ${''} | ${{ actionWithContent }} | ${true}
+ ${''} | ${{ actionWithoutContent }} | ${true}
+ ${'foo'} | ${{}} | ${true}
+ ${'foo'} | ${{ actionWithoutContent }} | ${true}
+ ${'foo'} | ${{ actionWithoutContent, actionWithContent }} | ${true}
+ ${'foo'} | ${{ actionWithContent }} | ${false}
`(
- 'disables submit button unless both title and content are present',
- ({ title, content, expectation }) => {
+ 'disables submit button unless both title and content for all blobs are present',
+ ({ title, blobsActions, expectation }) => {
createComponent({
data: {
snippet: { title },
- content,
+ blobsActions,
},
});
const isBtnDisabled = Boolean(findSubmitButton().attributes('disabled'));
@@ -192,83 +198,31 @@ describe('Snippet Edit app', () => {
});
describe('functionality', () => {
- describe('handling of the data from GraphQL response', () => {
- const snippet = {
- blob: {
- rawPath: rawPathMock,
- },
- };
- const getResSchema = newSnippet => {
- return {
- data: {
- snippets: {
- edges: newSnippet ? [] : [snippet],
- },
- },
+ describe('form submission handling', () => {
+ it('does not submit unchanged blobs', () => {
+ const foo = {
+ action: '',
+ };
+ const bar = {
+ action: 'update',
};
- };
-
- const bootstrapForExistingSnippet = resp => {
createComponent({
data: {
- snippet,
+ blobsActions: {
+ foo,
+ bar,
+ },
},
});
-
- if (resp === 500) {
- axiosMock.onGet('contentApiURL').reply(500);
- } else {
- axiosMock.onGet('contentApiURL').reply(200, contentMock);
- }
- wrapper.vm.onSnippetFetch(getResSchema());
- };
-
- const bootstrapForNewSnippet = () => {
- createComponent();
- wrapper.vm.onSnippetFetch(getResSchema(true));
- };
-
- beforeEach(() => {
- axiosMock = new AxiosMockAdapter(axios);
- });
-
- afterEach(() => {
- axiosMock.restore();
- });
-
- it('fetches blob content with the additional query', () => {
- bootstrapForExistingSnippet();
-
- return waitForPromises().then(() => {
- expect(joinPaths).toHaveBeenCalledWith('foo/', rawPathMock);
- expect(wrapper.vm.newSnippet).toBe(false);
- expect(wrapper.vm.content).toBe(contentMock);
- });
- });
-
- it('flashes the error message if fetching content fails', () => {
- bootstrapForExistingSnippet(500);
-
- return waitForPromises().then(() => {
- expect(flashSpy).toHaveBeenCalled();
- expect(wrapper.vm.content).toBe('');
- });
- });
-
- it('does not fetch content for new snippet', () => {
- bootstrapForNewSnippet();
+ clickSubmitBtn();
return waitForPromises().then(() => {
- // we keep using waitForPromises to make sure we do not run failed test
- expect(wrapper.vm.newSnippet).toBe(true);
- expect(wrapper.vm.content).toBe('');
- expect(joinPaths).not.toHaveBeenCalled();
- expect(wrapper.vm.snippet).toEqual(wrapper.vm.$options.newSnippetSchema);
+ expect(resolveMutate).toHaveBeenCalledWith(
+ expect.objectContaining({ variables: { input: { files: [bar] } } }),
+ );
});
});
- });
- describe('form submission handling', () => {
it.each`
newSnippet | projectPath | mutation | mutationName
${true} | ${rawProjectPathMock} | ${CreateSnippetMutation} | ${'CreateSnippetMutation with projectPath'}
@@ -279,6 +233,7 @@ describe('Snippet Edit app', () => {
createComponent({
data: {
newSnippet,
+ ...defaultData,
},
props: {
...defaultProps,
@@ -419,5 +374,57 @@ describe('Snippet Edit app', () => {
expect(resolveMutate).toHaveBeenCalledWith(updateMutationPayload());
});
});
+
+ describe('on before unload', () => {
+ let event;
+ let returnValueSetter;
+
+ const bootstrap = data => {
+ createComponent({
+ data,
+ });
+
+ event = new Event('beforeunload');
+ returnValueSetter = jest.spyOn(event, 'returnValue', 'set');
+ };
+
+ it('does not prevent page navigation if there are no blobs', () => {
+ bootstrap();
+ window.dispatchEvent(event);
+
+ expect(returnValueSetter).not.toHaveBeenCalled();
+ });
+
+ it('does not prevent page navigation if there are no changes to the blobs content', () => {
+ bootstrap({
+ blobsActions: {
+ foo: {
+ ...actionWithContent,
+ action: '',
+ },
+ },
+ });
+ window.dispatchEvent(event);
+
+ expect(returnValueSetter).not.toHaveBeenCalled();
+ });
+
+ it('prevents page navigation if there are some changes in the snippet content', () => {
+ bootstrap({
+ blobsActions: {
+ foo: {
+ ...actionWithContent,
+ action: 'update',
+ },
+ },
+ });
+
+ window.dispatchEvent(event);
+
+ expect(returnValueSetter).toHaveBeenCalledWith(
+ 'Are you sure you want to lose unsaved changes?',
+ );
+ });
+ });
});
});
diff --git a/spec/frontend/snippets/components/show_spec.js b/spec/frontend/snippets/components/show_spec.js
index 33608df8cf2..b5446e70028 100644
--- a/spec/frontend/snippets/components/show_spec.js
+++ b/spec/frontend/snippets/components/show_spec.js
@@ -1,10 +1,13 @@
import SnippetApp from '~/snippets/components/show.vue';
+import BlobEmbeddable from '~/blob/components/blob_embeddable.vue';
import SnippetHeader from '~/snippets/components/snippet_header.vue';
import SnippetTitle from '~/snippets/components/snippet_title.vue';
import SnippetBlob from '~/snippets/components/snippet_blob_view.vue';
import { GlLoadingIcon } from '@gitlab/ui';
+import { Blob, BinaryBlob } from 'jest/blob/components/mock_data';
import { shallowMount } from '@vue/test-utils';
+import { SNIPPET_VISIBILITY_PUBLIC } from '~/snippets/constants';
describe('Snippet view app', () => {
let wrapper;
@@ -12,7 +15,7 @@ describe('Snippet view app', () => {
snippetGid: 'gid://gitlab/PersonalSnippet/42',
};
- function createComponent({ props = defaultProps, loading = false } = {}) {
+ function createComponent({ props = defaultProps, data = {}, loading = false } = {}) {
const $apollo = {
queries: {
snippet: {
@@ -26,6 +29,9 @@ describe('Snippet view app', () => {
propsData: {
...props,
},
+ data() {
+ return data;
+ },
});
}
afterEach(() => {
@@ -37,10 +43,33 @@ describe('Snippet view app', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
- it('renders all components after the query is finished', () => {
+ it('renders all simple components after the query is finished', () => {
createComponent();
expect(wrapper.find(SnippetHeader).exists()).toBe(true);
expect(wrapper.find(SnippetTitle).exists()).toBe(true);
- expect(wrapper.find(SnippetBlob).exists()).toBe(true);
+ });
+
+ it('renders embeddable component if visibility allows', () => {
+ createComponent({
+ data: {
+ snippet: {
+ visibilityLevel: SNIPPET_VISIBILITY_PUBLIC,
+ webUrl: 'http://foo.bar',
+ },
+ },
+ });
+ expect(wrapper.contains(BlobEmbeddable)).toBe(true);
+ });
+
+ it('renders correct snippet-blob components', () => {
+ createComponent({
+ data: {
+ blobs: [Blob, BinaryBlob],
+ },
+ });
+ const blobs = wrapper.findAll(SnippetBlob);
+ expect(blobs.length).toBe(2);
+ expect(blobs.at(0).props('blob')).toEqual(Blob);
+ expect(blobs.at(1).props('blob')).toEqual(BinaryBlob);
});
});
diff --git a/spec/frontend/snippets/components/snippet_blob_edit_spec.js b/spec/frontend/snippets/components/snippet_blob_edit_spec.js
index 75688e61892..009074b4558 100644
--- a/spec/frontend/snippets/components/snippet_blob_edit_spec.js
+++ b/spec/frontend/snippets/components/snippet_blob_edit_spec.js
@@ -4,78 +4,161 @@ import BlobContentEdit from '~/blob/components/blob_edit_content.vue';
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
+import AxiosMockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import { joinPaths } from '~/lib/utils/url_utility';
+import waitForPromises from 'helpers/wait_for_promises';
jest.mock('~/blob/utils', () => jest.fn());
+jest.mock('~/lib/utils/url_utility', () => ({
+ getBaseURL: jest.fn().mockReturnValue('foo/'),
+ joinPaths: jest
+ .fn()
+ .mockName('joinPaths')
+ .mockReturnValue('contentApiURL'),
+}));
+
+jest.mock('~/flash');
+
+let flashSpy;
+
describe('Snippet Blob Edit component', () => {
let wrapper;
- const value = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
- const fileName = 'lorem.txt';
- const findHeader = () => wrapper.find(BlobHeaderEdit);
- const findContent = () => wrapper.find(BlobContentEdit);
+ let axiosMock;
+ const contentMock = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
+ const pathMock = 'lorem.txt';
+ const rawPathMock = 'foo/bar';
+ const blob = {
+ path: pathMock,
+ content: contentMock,
+ rawPath: rawPathMock,
+ };
+ const findComponent = component => wrapper.find(component);
- function createComponent(props = {}) {
+ function createComponent(props = {}, data = { isContentLoading: false }) {
wrapper = shallowMount(SnippetBlobEdit, {
propsData: {
- value,
- fileName,
- isLoading: false,
...props,
},
+ data() {
+ return {
+ ...data,
+ };
+ },
});
+ flashSpy = jest.spyOn(wrapper.vm, 'flashAPIFailure');
}
beforeEach(() => {
+ axiosMock = new AxiosMockAdapter(axios);
createComponent();
});
afterEach(() => {
+ axiosMock.restore();
wrapper.destroy();
});
describe('rendering', () => {
it('matches the snapshot', () => {
+ createComponent({ blob });
expect(wrapper.element).toMatchSnapshot();
});
it('renders required components', () => {
- expect(findHeader().exists()).toBe(true);
- expect(findContent().exists()).toBe(true);
+ expect(findComponent(BlobHeaderEdit).exists()).toBe(true);
+ expect(findComponent(BlobContentEdit).exists()).toBe(true);
});
- it('renders loader if isLoading equals true', () => {
- createComponent({ isLoading: true });
+ it('renders loader if existing blob is supplied but no content is fetched yet', () => {
+ createComponent({ blob }, { isContentLoading: true });
expect(wrapper.contains(GlLoadingIcon)).toBe(true);
- expect(findContent().exists()).toBe(false);
+ expect(findComponent(BlobContentEdit).exists()).toBe(false);
+ });
+
+ it('does not render loader if when blob is not supplied', () => {
+ createComponent();
+ expect(wrapper.contains(GlLoadingIcon)).toBe(false);
+ expect(findComponent(BlobContentEdit).exists()).toBe(true);
});
});
describe('functionality', () => {
- it('does not fail without content', () => {
+ it('does not fail without blob', () => {
const spy = jest.spyOn(global.console, 'error');
- createComponent({ value: undefined });
+ createComponent({ blob: undefined });
expect(spy).not.toHaveBeenCalled();
- expect(findContent().exists()).toBe(true);
+ expect(findComponent(BlobContentEdit).exists()).toBe(true);
});
- it('emits "name-change" event when the file name gets changed', () => {
- expect(wrapper.emitted('name-change')).toBeUndefined();
- const newFilename = 'foo.bar';
- findHeader().vm.$emit('input', newFilename);
+ it.each`
+ emitter | prop
+ ${BlobHeaderEdit} | ${'filePath'}
+ ${BlobContentEdit} | ${'content'}
+ `('emits "blob-updated" event when the $prop gets changed', ({ emitter, prop }) => {
+ expect(wrapper.emitted('blob-updated')).toBeUndefined();
+ const newValue = 'foo.bar';
+ findComponent(emitter).vm.$emit('input', newValue);
return nextTick().then(() => {
- expect(wrapper.emitted('name-change')[0]).toEqual([newFilename]);
+ expect(wrapper.emitted('blob-updated')[0]).toEqual([
+ expect.objectContaining({
+ [prop]: newValue,
+ }),
+ ]);
});
});
- it('emits "input" event when the file content gets changed', () => {
- expect(wrapper.emitted('input')).toBeUndefined();
- const newValue = 'foo.bar';
- findContent().vm.$emit('input', newValue);
+ describe('fetching blob content', () => {
+ const bootstrapForExistingSnippet = resp => {
+ createComponent({
+ blob: {
+ ...blob,
+ content: '',
+ },
+ });
- return nextTick().then(() => {
- expect(wrapper.emitted('input')[0]).toEqual([newValue]);
+ if (resp === 500) {
+ axiosMock.onGet('contentApiURL').reply(500);
+ } else {
+ axiosMock.onGet('contentApiURL').reply(200, contentMock);
+ }
+ };
+
+ const bootstrapForNewSnippet = () => {
+ createComponent();
+ };
+
+ it('fetches blob content with the additional query', () => {
+ bootstrapForExistingSnippet();
+
+ return waitForPromises().then(() => {
+ expect(joinPaths).toHaveBeenCalledWith('foo/', rawPathMock);
+ expect(findComponent(BlobHeaderEdit).props('value')).toBe(pathMock);
+ expect(findComponent(BlobContentEdit).props('value')).toBe(contentMock);
+ });
+ });
+
+ it('flashes the error message if fetching content fails', () => {
+ bootstrapForExistingSnippet(500);
+
+ return waitForPromises().then(() => {
+ expect(flashSpy).toHaveBeenCalled();
+ expect(findComponent(BlobContentEdit).props('value')).toBe('');
+ });
+ });
+
+ it('does not fetch content for new snippet', () => {
+ bootstrapForNewSnippet();
+
+ return waitForPromises().then(() => {
+ // we keep using waitForPromises to make sure we do not run failed test
+ expect(findComponent(BlobHeaderEdit).props('value')).toBe('');
+ expect(findComponent(BlobContentEdit).props('value')).toBe('');
+ expect(joinPaths).not.toHaveBeenCalled();
+ });
});
});
});
diff --git a/spec/frontend/snippets/components/snippet_blob_view_spec.js b/spec/frontend/snippets/components/snippet_blob_view_spec.js
index e4d8ee9b7df..c8f1c8fc8a9 100644
--- a/spec/frontend/snippets/components/snippet_blob_view_spec.js
+++ b/spec/frontend/snippets/components/snippet_blob_view_spec.js
@@ -23,13 +23,17 @@ describe('Blob Embeddable', () => {
id: 'gid://foo.bar/snippet',
webUrl: 'https://foo.bar',
visibilityLevel: SNIPPET_VISIBILITY_PUBLIC,
- blob: BlobMock,
};
const dataMock = {
activeViewerType: SimpleViewerMock.type,
};
- function createComponent(props = {}, data = dataMock, contentLoading = false) {
+ function createComponent({
+ snippetProps = {},
+ data = dataMock,
+ blob = BlobMock,
+ contentLoading = false,
+ } = {}) {
const $apollo = {
queries: {
blobContent: {
@@ -44,8 +48,9 @@ describe('Blob Embeddable', () => {
propsData: {
snippet: {
...snippet,
- ...props,
+ ...snippetProps,
},
+ blob,
},
data() {
return {
@@ -63,7 +68,6 @@ describe('Blob Embeddable', () => {
describe('rendering', () => {
it('renders correct components', () => {
createComponent();
- expect(wrapper.find(BlobEmbeddable).exists()).toBe(true);
expect(wrapper.find(BlobHeader).exists()).toBe(true);
expect(wrapper.find(BlobContent).exists()).toBe(true);
});
@@ -72,19 +76,14 @@ describe('Blob Embeddable', () => {
'does not render blob-embeddable by default',
visibilityLevel => {
createComponent({
- visibilityLevel,
+ snippetProps: {
+ visibilityLevel,
+ },
});
expect(wrapper.find(BlobEmbeddable).exists()).toBe(false);
},
);
- it('does render blob-embeddable for public snippet', () => {
- createComponent({
- visibilityLevel: SNIPPET_VISIBILITY_PUBLIC,
- });
- expect(wrapper.find(BlobEmbeddable).exists()).toBe(true);
- });
-
it('sets simple viewer correctly', () => {
createComponent();
expect(wrapper.find(SimpleViewer).exists()).toBe(true);
@@ -92,7 +91,9 @@ describe('Blob Embeddable', () => {
it('sets rich viewer correctly', () => {
const data = { ...dataMock, activeViewerType: RichViewerMock.type };
- createComponent({}, data);
+ createComponent({
+ data,
+ });
expect(wrapper.find(RichViewer).exists()).toBe(true);
});
@@ -137,7 +138,9 @@ describe('Blob Embeddable', () => {
});
it('renders simple viewer by default if URL contains hash', () => {
- createComponent({}, {});
+ createComponent({
+ data: {},
+ });
expect(wrapper.vm.activeViewerType).toBe(SimpleViewerMock.type);
expect(wrapper.find(SimpleViewer).exists()).toBe(true);
@@ -183,12 +186,11 @@ describe('Blob Embeddable', () => {
});
it(`sets '${SimpleViewerMock.type}' as active on ${BLOB_RENDER_EVENT_SHOW_SOURCE} event`, () => {
- createComponent(
- {},
- {
+ createComponent({
+ data: {
activeViewerType: RichViewerMock.type,
},
- );
+ });
findContentEl().vm.$emit(BLOB_RENDER_EVENT_SHOW_SOURCE);
expect(wrapper.vm.activeViewerType).toEqual(SimpleViewerMock.type);
diff --git a/spec/frontend/snippets/components/snippet_header_spec.js b/spec/frontend/snippets/components/snippet_header_spec.js
index 5230910b6f5..0825da92118 100644
--- a/spec/frontend/snippets/components/snippet_header_spec.js
+++ b/spec/frontend/snippets/components/snippet_header_spec.js
@@ -3,6 +3,7 @@ import DeleteSnippetMutation from '~/snippets/mutations/deleteSnippet.mutation.g
import { ApolloMutation } from 'vue-apollo';
import { GlButton, GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { Blob, BinaryBlob } from 'jest/blob/components/mock_data';
describe('Snippet header component', () => {
let wrapper;
@@ -20,9 +21,7 @@ describe('Snippet header component', () => {
author: {
name: 'Thor Odinson',
},
- blob: {
- binary: false,
- },
+ blobs: [Blob],
};
const mutationVariables = {
mutation: DeleteSnippetMutation,
@@ -49,7 +48,6 @@ describe('Snippet header component', () => {
mutationRes = mutationTypes.RESOLVE,
snippetProps = {},
} = {}) {
- // const defaultProps = Object.assign({}, snippet, snippetProps);
const defaultProps = Object.assign(snippet, snippetProps);
if (permissions) {
Object.assign(defaultProps.userPermissions, {
@@ -131,15 +129,18 @@ describe('Snippet header component', () => {
expect(wrapper.find(GlModal).exists()).toBe(true);
});
- it('renders Edit button as disabled for binary snippets', () => {
+ it.each`
+ blobs | isDisabled | condition
+ ${[Blob]} | ${false} | ${'no binary'}
+ ${[Blob, BinaryBlob]} | ${true} | ${'several blobs. incl. a binary'}
+ ${[BinaryBlob]} | ${true} | ${'binary'}
+ `('renders Edit button when snippet contains $condition file', ({ blobs, isDisabled }) => {
createComponent({
snippetProps: {
- blob: {
- binary: true,
- },
+ blobs,
},
});
- expect(wrapper.find('[href*="edit"]').props('disabled')).toBe(true);
+ expect(wrapper.find('[href*="edit"]').props('disabled')).toBe(isDisabled);
});
describe('Delete mutation', () => {
diff --git a/spec/frontend/static_site_editor/components/edit_area_spec.js b/spec/frontend/static_site_editor/components/edit_area_spec.js
index d7c798e6620..11c5abf1b08 100644
--- a/spec/frontend/static_site_editor/components/edit_area_spec.js
+++ b/spec/frontend/static_site_editor/components/edit_area_spec.js
@@ -1,6 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import RichContentEditor from '~/vue_shared/components/rich_content_editor/rich_content_editor.vue';
+import { EDITOR_TYPES } from '~/vue_shared/components/rich_content_editor/constants';
import EditArea from '~/static_site_editor/components/edit_area.vue';
import PublishToolbar from '~/static_site_editor/components/publish_toolbar.vue';
@@ -51,7 +52,7 @@ describe('~/static_site_editor/components/edit_area.vue', () => {
it('renders rich content editor', () => {
expect(findRichContentEditor().exists()).toBe(true);
- expect(findRichContentEditor().props('value')).toBe(body);
+ expect(findRichContentEditor().props('content')).toBe(body);
});
it('renders publish toolbar', () => {
@@ -75,6 +76,15 @@ describe('~/static_site_editor/components/edit_area.vue', () => {
return wrapper.vm.$nextTick();
});
+ it('updates parsedSource with new content', () => {
+ const newContent = 'New content';
+ const spySyncParsedSource = jest.spyOn(wrapper.vm.parsedSource, 'sync');
+
+ findRichContentEditor().vm.$emit('input', newContent);
+
+ expect(spySyncParsedSource).toHaveBeenCalledWith(newContent, true);
+ });
+
it('sets publish toolbar as saveable', () => {
expect(findPublishToolbar().props('saveable')).toBe(true);
});
@@ -91,4 +101,33 @@ describe('~/static_site_editor/components/edit_area.vue', () => {
});
});
});
+
+ describe('when the mode changes', () => {
+ const setInitialMode = mode => {
+ wrapper.setData({ editorMode: mode });
+ };
+
+ afterEach(() => {
+ setInitialMode(EDITOR_TYPES.wysiwyg);
+ });
+
+ it.each`
+ initialMode | targetMode | resetValue
+ ${EDITOR_TYPES.wysiwyg} | ${EDITOR_TYPES.markdown} | ${content}
+ ${EDITOR_TYPES.markdown} | ${EDITOR_TYPES.wysiwyg} | ${body}
+ `(
+ 'sets editorMode from $initialMode to $targetMode',
+ ({ initialMode, targetMode, resetValue }) => {
+ setInitialMode(initialMode);
+
+ const resetInitialValue = jest.fn();
+
+ findRichContentEditor().setMethods({ resetInitialValue });
+ findRichContentEditor().vm.$emit('modeChange', targetMode);
+
+ expect(resetInitialValue).toHaveBeenCalledWith(resetValue);
+ expect(wrapper.vm.editorMode).toBe(targetMode);
+ },
+ );
+ });
});
diff --git a/spec/frontend/static_site_editor/mock_data.js b/spec/frontend/static_site_editor/mock_data.js
index 422048a5f69..96de9b73af0 100644
--- a/spec/frontend/static_site_editor/mock_data.js
+++ b/spec/frontend/static_site_editor/mock_data.js
@@ -10,6 +10,8 @@ export const sourceContentBody = `## On this page
- TOC
{:toc .hidden-md .hidden-lg}
+
+![image](path/to/image1.png)
`;
export const sourceContent = `${sourceContentHeader}${sourceContentSpacing}${sourceContentBody}`;
export const sourceContentTitle = 'Handbook';
@@ -48,3 +50,8 @@ export const createMergeRequestResponse = {
};
export const trackingCategory = 'projects:static_site_editor:show';
+
+export const images = new Map([
+ ['path/to/image1.png', 'image1-content'],
+ ['path/to/image2.png', 'image2-content'],
+]);
diff --git a/spec/frontend/static_site_editor/services/parse_source_file_spec.js b/spec/frontend/static_site_editor/services/parse_source_file_spec.js
index fe99c4f5334..4588548e614 100644
--- a/spec/frontend/static_site_editor/services/parse_source_file_spec.js
+++ b/spec/frontend/static_site_editor/services/parse_source_file_spec.js
@@ -1,64 +1,58 @@
-import {
- sourceContent as content,
- sourceContentHeader as header,
- sourceContentSpacing as spacing,
- sourceContentBody as body,
-} from '../mock_data';
+import { sourceContent as content, sourceContentBody as body } from '../mock_data';
import parseSourceFile from '~/static_site_editor/services/parse_source_file';
describe('parseSourceFile', () => {
- const contentSimple = content;
const contentComplex = [content, content, content].join('');
+ const complexBody = [body, content, content].join('');
+ const edit = 'and more';
+ const newContent = `${content} ${edit}`;
+ const newContentComplex = `${contentComplex} ${edit}`;
- describe('the editable shape and its expected values', () => {
+ describe('unmodified content', () => {
it.each`
- sourceContent | sourceHeader | sourceSpacing | sourceBody | desc
- ${contentSimple} | ${header} | ${spacing} | ${body} | ${'extracts header'}
- ${contentComplex} | ${header} | ${spacing} | ${[body, content, content].join('')} | ${'extracts body'}
- `('$desc', ({ sourceContent, sourceHeader, sourceSpacing, sourceBody }) => {
- const { editable } = parseSourceFile(sourceContent);
-
- expect(editable).toMatchObject({
- raw: sourceContent,
- header: sourceHeader,
- spacing: sourceSpacing,
- body: sourceBody,
- });
+ parsedSource
+ ${parseSourceFile(content)}
+ ${parseSourceFile(contentComplex)}
+ `('returns false by default', ({ parsedSource }) => {
+ expect(parsedSource.isModified()).toBe(false);
});
- it('returns the same front matter regardless of front matter duplication', () => {
- const parsedSourceSimple = parseSourceFile(contentSimple);
- const parsedSourceComplex = parseSourceFile(contentComplex);
-
- expect(parsedSourceSimple.editable.header).toBe(parsedSourceComplex.editable.header);
- });
- });
-
- describe('editable body to raw content default and changes', () => {
it.each`
- sourceContent | desc
- ${contentSimple} | ${'returns false by default for both raw and body'}
- ${contentComplex} | ${'returns false by default for both raw and body'}
- `('$desc', ({ sourceContent }) => {
- const parsedSource = parseSourceFile(sourceContent);
+ parsedSource | isBody | target
+ ${parseSourceFile(content)} | ${undefined} | ${content}
+ ${parseSourceFile(content)} | ${false} | ${content}
+ ${parseSourceFile(content)} | ${true} | ${body}
+ ${parseSourceFile(contentComplex)} | ${undefined} | ${contentComplex}
+ ${parseSourceFile(contentComplex)} | ${false} | ${contentComplex}
+ ${parseSourceFile(contentComplex)} | ${true} | ${complexBody}
+ `(
+ 'returns only the $target content when the `isBody` parameter argument is $isBody',
+ ({ parsedSource, isBody, target }) => {
+ expect(parsedSource.content(isBody)).toBe(target);
+ },
+ );
+ });
- expect(parsedSource.isModifiedRaw()).toBe(false);
- expect(parsedSource.isModifiedBody()).toBe(false);
- });
+ describe('modified content', () => {
+ const newBody = `${body} ${edit}`;
+ const newComplexBody = `${complexBody} ${edit}`;
it.each`
- sourceContent | editableKey | syncKey | isModifiedKey | desc
- ${contentSimple} | ${'body'} | ${'syncRaw'} | ${'isModifiedRaw'} | ${'returns true after modification and sync'}
- ${contentSimple} | ${'raw'} | ${'syncBody'} | ${'isModifiedBody'} | ${'returns true after modification and sync'}
- ${contentComplex} | ${'body'} | ${'syncRaw'} | ${'isModifiedRaw'} | ${'returns true after modification and sync'}
- ${contentComplex} | ${'raw'} | ${'syncBody'} | ${'isModifiedBody'} | ${'returns true after modification and sync'}
- `('$desc', ({ sourceContent, editableKey, syncKey, isModifiedKey }) => {
- const parsedSource = parseSourceFile(sourceContent);
- parsedSource.editable[editableKey] += 'Added content';
- parsedSource[syncKey]();
-
- expect(parsedSource[isModifiedKey]()).toBe(true);
- });
+ parsedSource | isModified | targetRaw | targetBody
+ ${parseSourceFile(content)} | ${false} | ${content} | ${body}
+ ${parseSourceFile(content)} | ${true} | ${newContent} | ${newBody}
+ ${parseSourceFile(contentComplex)} | ${false} | ${contentComplex} | ${complexBody}
+ ${parseSourceFile(contentComplex)} | ${true} | ${newContentComplex} | ${newComplexBody}
+ `(
+ 'returns $isModified after a $targetRaw sync',
+ ({ parsedSource, isModified, targetRaw, targetBody }) => {
+ parsedSource.sync(targetRaw);
+
+ expect(parsedSource.isModified()).toBe(isModified);
+ expect(parsedSource.content()).toBe(targetRaw);
+ expect(parsedSource.content(true)).toBe(targetBody);
+ },
+ );
});
});
diff --git a/spec/frontend/static_site_editor/services/submit_content_changes_spec.js b/spec/frontend/static_site_editor/services/submit_content_changes_spec.js
index 3636de3fe70..a9169eb3e16 100644
--- a/spec/frontend/static_site_editor/services/submit_content_changes_spec.js
+++ b/spec/frontend/static_site_editor/services/submit_content_changes_spec.js
@@ -22,6 +22,7 @@ import {
sourcePath,
sourceContent as content,
trackingCategory,
+ images,
} from '../mock_data';
jest.mock('~/static_site_editor/services/generate_branch_name');
@@ -69,7 +70,7 @@ describe('submitContentChanges', () => {
});
it('commits the content changes to the branch when creating branch succeeds', () => {
- return submitContentChanges({ username, projectId, sourcePath, content }).then(() => {
+ return submitContentChanges({ username, projectId, sourcePath, content, images }).then(() => {
expect(Api.commitMultiple).toHaveBeenCalledWith(projectId, {
branch,
commit_message: mergeRequestTitle,
@@ -79,6 +80,35 @@ describe('submitContentChanges', () => {
file_path: sourcePath,
content,
},
+ {
+ action: 'create',
+ content: 'image1-content',
+ encoding: 'base64',
+ file_path: 'path/to/image1.png',
+ },
+ ],
+ });
+ });
+ });
+
+ it('does not commit an image if it has been removed from the content', () => {
+ const contentWithoutImages = '## Content without images';
+ return submitContentChanges({
+ username,
+ projectId,
+ sourcePath,
+ content: contentWithoutImages,
+ images,
+ }).then(() => {
+ expect(Api.commitMultiple).toHaveBeenCalledWith(projectId, {
+ branch,
+ commit_message: mergeRequestTitle,
+ actions: [
+ {
+ action: 'update',
+ file_path: sourcePath,
+ content: contentWithoutImages,
+ },
],
});
});
@@ -87,13 +117,13 @@ describe('submitContentChanges', () => {
it('notifies error when content could not be committed', () => {
Api.commitMultiple.mockRejectedValueOnce();
- return expect(submitContentChanges({ username, projectId })).rejects.toThrow(
+ return expect(submitContentChanges({ username, projectId, images })).rejects.toThrow(
SUBMIT_CHANGES_COMMIT_ERROR,
);
});
it('creates a merge request when commiting changes succeeds', () => {
- return submitContentChanges({ username, projectId, sourcePath, content }).then(() => {
+ return submitContentChanges({ username, projectId, sourcePath, content, images }).then(() => {
expect(Api.createProjectMergeRequest).toHaveBeenCalledWith(
projectId,
convertObjectPropsToSnakeCase({
@@ -108,7 +138,7 @@ describe('submitContentChanges', () => {
it('notifies error when merge request could not be created', () => {
Api.createProjectMergeRequest.mockRejectedValueOnce();
- return expect(submitContentChanges({ username, projectId })).rejects.toThrow(
+ return expect(submitContentChanges({ username, projectId, images })).rejects.toThrow(
SUBMIT_CHANGES_MERGE_REQUEST_ERROR,
);
});
@@ -117,9 +147,11 @@ describe('submitContentChanges', () => {
let result;
beforeEach(() => {
- return submitContentChanges({ username, projectId, sourcePath, content }).then(_result => {
- result = _result;
- });
+ return submitContentChanges({ username, projectId, sourcePath, content, images }).then(
+ _result => {
+ result = _result;
+ },
+ );
});
it('returns the branch name', () => {
@@ -147,7 +179,7 @@ describe('submitContentChanges', () => {
describe('sends the correct tracking event', () => {
beforeEach(() => {
- return submitContentChanges({ username, projectId, sourcePath, content });
+ return submitContentChanges({ username, projectId, sourcePath, content, images });
});
it('for committing changes', () => {
diff --git a/spec/frontend/vue_mr_widget/components/approvals/approvals_spec.js b/spec/frontend/vue_mr_widget/components/approvals/approvals_spec.js
new file mode 100644
index 00000000000..e39f66d3f30
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/approvals/approvals_spec.js
@@ -0,0 +1,391 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlButton } from '@gitlab/ui';
+import Approvals from '~/vue_merge_request_widget/components/approvals/approvals.vue';
+import ApprovalsSummary from '~/vue_merge_request_widget/components/approvals/approvals_summary.vue';
+import ApprovalsSummaryOptional from '~/vue_merge_request_widget/components/approvals/approvals_summary_optional.vue';
+import createFlash from '~/flash';
+import {
+ FETCH_LOADING,
+ FETCH_ERROR,
+ APPROVE_ERROR,
+ UNAPPROVE_ERROR,
+} from '~/vue_merge_request_widget/components/approvals/messages';
+import eventHub from '~/vue_merge_request_widget/event_hub';
+
+jest.mock('~/flash');
+
+const TEST_HELP_PATH = 'help/path';
+const testApprovedBy = () => [1, 7, 10].map(id => ({ id }));
+const testApprovals = () => ({
+ approved: false,
+ approved_by: testApprovedBy().map(user => ({ user })),
+ approval_rules_left: [],
+ approvals_left: 4,
+ suggested_approvers: [],
+ user_can_approve: true,
+ user_has_approved: true,
+ require_password_to_approve: false,
+});
+const testApprovalRulesResponse = () => ({ rules: [{ id: 2 }] });
+
+// For some reason, the `Promise.resolve()` needs to be deferred
+// or the timing doesn't work.
+const tick = () => Promise.resolve();
+const waitForTick = done =>
+ tick()
+ .then(done)
+ .catch(done.fail);
+
+describe('MRWidget approvals', () => {
+ let wrapper;
+ let service;
+ let mr;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(Approvals, {
+ propsData: {
+ mr,
+ service,
+ ...props,
+ },
+ });
+ };
+
+ const findAction = () => wrapper.find(GlButton);
+ const findActionData = () => {
+ const action = findAction();
+
+ return !action.exists()
+ ? null
+ : {
+ variant: action.props('variant'),
+ category: action.props('category'),
+ text: action.text(),
+ };
+ };
+ const findSummary = () => wrapper.find(ApprovalsSummary);
+ const findOptionalSummary = () => wrapper.find(ApprovalsSummaryOptional);
+
+ beforeEach(() => {
+ service = {
+ ...{
+ fetchApprovals: jest.fn().mockReturnValue(Promise.resolve(testApprovals())),
+ fetchApprovalSettings: jest
+ .fn()
+ .mockReturnValue(Promise.resolve(testApprovalRulesResponse())),
+ approveMergeRequest: jest.fn().mockReturnValue(Promise.resolve(testApprovals())),
+ unapproveMergeRequest: jest.fn().mockReturnValue(Promise.resolve(testApprovals())),
+ approveMergeRequestWithAuth: jest.fn().mockReturnValue(Promise.resolve(testApprovals())),
+ },
+ };
+ mr = {
+ ...{
+ setApprovals: jest.fn(),
+ setApprovalRules: jest.fn(),
+ },
+ approvalsHelpPath: TEST_HELP_PATH,
+ approvals: testApprovals(),
+ approvalRules: [],
+ isOpen: true,
+ state: 'open',
+ };
+
+ jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('when created', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('shows loading message', () => {
+ wrapper.setData({ fetchingApprovals: true });
+
+ return tick().then(() => {
+ expect(wrapper.text()).toContain(FETCH_LOADING);
+ });
+ });
+
+ it('fetches approvals', () => {
+ expect(service.fetchApprovals).toHaveBeenCalled();
+ });
+ });
+
+ describe('when fetch approvals error', () => {
+ beforeEach(done => {
+ jest.spyOn(service, 'fetchApprovals').mockReturnValue(Promise.reject());
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('still shows loading message', () => {
+ expect(wrapper.text()).toContain(FETCH_LOADING);
+ });
+
+ it('flashes error', () => {
+ expect(createFlash).toHaveBeenCalledWith(FETCH_ERROR);
+ });
+ });
+
+ describe('action button', () => {
+ describe('when mr is closed', () => {
+ beforeEach(done => {
+ mr.isOpen = false;
+ mr.approvals.user_has_approved = false;
+ mr.approvals.user_can_approve = true;
+
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('action is not rendered', () => {
+ expect(findActionData()).toBe(null);
+ });
+ });
+
+ describe('when user cannot approve', () => {
+ beforeEach(done => {
+ mr.approvals.user_has_approved = false;
+ mr.approvals.user_can_approve = false;
+
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('action is not rendered', () => {
+ expect(findActionData()).toBe(null);
+ });
+ });
+
+ describe('when user can approve', () => {
+ beforeEach(() => {
+ mr.approvals.user_has_approved = false;
+ mr.approvals.user_can_approve = true;
+ });
+
+ describe('and MR is unapproved', () => {
+ beforeEach(done => {
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('approve action is rendered', () => {
+ expect(findActionData()).toEqual({
+ variant: 'info',
+ text: 'Approve',
+ category: 'primary',
+ });
+ });
+ });
+
+ describe('and MR is approved', () => {
+ beforeEach(() => {
+ mr.approvals.approved = true;
+ });
+
+ describe('with no approvers', () => {
+ beforeEach(done => {
+ mr.approvals.approved_by = [];
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('approve action (with inverted style) is rendered', () => {
+ expect(findActionData()).toEqual({
+ variant: 'info',
+ text: 'Approve',
+ category: 'secondary',
+ });
+ });
+ });
+
+ describe('with approvers', () => {
+ beforeEach(done => {
+ mr.approvals.approved_by = [{ user: { id: 7 } }];
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('approve additionally action is rendered', () => {
+ expect(findActionData()).toEqual({
+ variant: 'info',
+ text: 'Approve additionally',
+ category: 'secondary',
+ });
+ });
+ });
+ });
+
+ describe('when approve action is clicked', () => {
+ beforeEach(done => {
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('shows loading icon', () => {
+ jest.spyOn(service, 'approveMergeRequest').mockReturnValue(new Promise(() => {}));
+ const action = findAction();
+
+ expect(action.props('loading')).toBe(false);
+
+ action.vm.$emit('click');
+
+ return tick().then(() => {
+ expect(action.props('loading')).toBe(true);
+ });
+ });
+
+ describe('and after loading', () => {
+ beforeEach(done => {
+ findAction().vm.$emit('click');
+ waitForTick(done);
+ });
+
+ it('calls service approve', () => {
+ expect(service.approveMergeRequest).toHaveBeenCalled();
+ });
+
+ it('emits to eventHub', () => {
+ expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
+ });
+
+ it('calls store setApprovals', () => {
+ expect(mr.setApprovals).toHaveBeenCalledWith(testApprovals());
+ });
+ });
+
+ describe('and error', () => {
+ beforeEach(done => {
+ jest.spyOn(service, 'approveMergeRequest').mockReturnValue(Promise.reject());
+ findAction().vm.$emit('click');
+ waitForTick(done);
+ });
+
+ it('flashes error message', () => {
+ expect(createFlash).toHaveBeenCalledWith(APPROVE_ERROR);
+ });
+ });
+ });
+ });
+
+ describe('when user has approved', () => {
+ beforeEach(done => {
+ mr.approvals.user_has_approved = true;
+ mr.approvals.user_can_approve = false;
+
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('revoke action is rendered', () => {
+ expect(findActionData()).toEqual({
+ variant: 'warning',
+ text: 'Revoke approval',
+ category: 'secondary',
+ });
+ });
+
+ describe('when revoke action is clicked', () => {
+ describe('and successful', () => {
+ beforeEach(done => {
+ findAction().vm.$emit('click');
+ waitForTick(done);
+ });
+
+ it('calls service unapprove', () => {
+ expect(service.unapproveMergeRequest).toHaveBeenCalled();
+ });
+
+ it('emits to eventHub', () => {
+ expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
+ });
+
+ it('calls store setApprovals', () => {
+ expect(mr.setApprovals).toHaveBeenCalledWith(testApprovals());
+ });
+ });
+
+ describe('and error', () => {
+ beforeEach(done => {
+ jest.spyOn(service, 'unapproveMergeRequest').mockReturnValue(Promise.reject());
+ findAction().vm.$emit('click');
+ waitForTick(done);
+ });
+
+ it('flashes error message', () => {
+ expect(createFlash).toHaveBeenCalledWith(UNAPPROVE_ERROR);
+ });
+ });
+ });
+ });
+ });
+
+ describe('approvals optional summary', () => {
+ describe('when no approvals required and no approvers', () => {
+ beforeEach(() => {
+ mr.approvals.approved_by = [];
+ mr.approvals.approvals_required = 0;
+ mr.approvals.user_has_approved = false;
+ });
+
+ describe('and can approve', () => {
+ beforeEach(done => {
+ mr.approvals.user_can_approve = true;
+
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('is shown', () => {
+ expect(findSummary().exists()).toBe(false);
+ expect(findOptionalSummary().props()).toEqual({
+ canApprove: true,
+ helpPath: TEST_HELP_PATH,
+ });
+ });
+ });
+
+ describe('and cannot approve', () => {
+ beforeEach(done => {
+ mr.approvals.user_can_approve = false;
+
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('is shown', () => {
+ expect(findSummary().exists()).toBe(false);
+ expect(findOptionalSummary().props()).toEqual({
+ canApprove: false,
+ helpPath: TEST_HELP_PATH,
+ });
+ });
+ });
+ });
+ });
+
+ describe('approvals summary', () => {
+ beforeEach(done => {
+ createComponent();
+ waitForTick(done);
+ });
+
+ it('is rendered with props', () => {
+ const expected = testApprovals();
+ const summary = findSummary();
+
+ expect(findOptionalSummary().exists()).toBe(false);
+ expect(summary.exists()).toBe(true);
+ expect(summary.props()).toMatchObject({
+ approvalsLeft: expected.approvals_left,
+ rulesLeft: expected.approval_rules_left,
+ approvers: testApprovedBy(),
+ });
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_optional_spec.js b/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_optional_spec.js
new file mode 100644
index 00000000000..77fad7f51ab
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_optional_spec.js
@@ -0,0 +1,57 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlLink } from '@gitlab/ui';
+import {
+ OPTIONAL,
+ OPTIONAL_CAN_APPROVE,
+} from '~/vue_merge_request_widget/components/approvals/messages';
+import ApprovalsSummaryOptional from '~/vue_merge_request_widget/components/approvals/approvals_summary_optional.vue';
+
+const TEST_HELP_PATH = 'help/path';
+
+describe('MRWidget approvals summary optional', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(ApprovalsSummaryOptional, {
+ propsData: props,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const findHelpLink = () => wrapper.find(GlLink);
+
+ describe('when can approve', () => {
+ beforeEach(() => {
+ createComponent({ canApprove: true, helpPath: TEST_HELP_PATH });
+ });
+
+ it('shows optional can approve message', () => {
+ expect(wrapper.text()).toEqual(OPTIONAL_CAN_APPROVE);
+ });
+
+ it('shows help link', () => {
+ const link = findHelpLink();
+
+ expect(link.exists()).toBe(true);
+ expect(link.attributes('href')).toBe(TEST_HELP_PATH);
+ });
+ });
+
+ describe('when cannot approve', () => {
+ beforeEach(() => {
+ createComponent({ canApprove: false, helpPath: TEST_HELP_PATH });
+ });
+
+ it('shows optional message', () => {
+ expect(wrapper.text()).toEqual(OPTIONAL);
+ });
+
+ it('does not show help link', () => {
+ expect(findHelpLink().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_spec.js b/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_spec.js
new file mode 100644
index 00000000000..822d075f28f
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_spec.js
@@ -0,0 +1,93 @@
+import { shallowMount } from '@vue/test-utils';
+import { APPROVED_MESSAGE } from '~/vue_merge_request_widget/components/approvals/messages';
+import ApprovalsSummary from '~/vue_merge_request_widget/components/approvals/approvals_summary.vue';
+import { toNounSeriesText } from '~/lib/utils/grammar';
+import UserAvatarList from '~/vue_shared/components/user_avatar/user_avatar_list.vue';
+
+const testApprovers = () => Array.from({ length: 5 }, (_, i) => i).map(id => ({ id }));
+const testRulesLeft = () => ['Lorem', 'Ipsum', 'dolar & sit'];
+const TEST_APPROVALS_LEFT = 3;
+
+describe('MRWidget approvals summary', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(ApprovalsSummary, {
+ propsData: {
+ approved: false,
+ approvers: testApprovers(),
+ approvalsLeft: TEST_APPROVALS_LEFT,
+ rulesLeft: testRulesLeft(),
+ ...props,
+ },
+ });
+ };
+
+ const findAvatars = () => wrapper.find(UserAvatarList);
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('when approved', () => {
+ beforeEach(() => {
+ createComponent({
+ approved: true,
+ });
+ });
+
+ it('shows approved message', () => {
+ expect(wrapper.text()).toContain(APPROVED_MESSAGE);
+ });
+
+ it('renders avatar list for approvers', () => {
+ const avatars = findAvatars();
+
+ expect(avatars.exists()).toBe(true);
+ expect(avatars.props()).toEqual(
+ expect.objectContaining({
+ items: testApprovers(),
+ }),
+ );
+ });
+ });
+
+ describe('when not approved', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('render message', () => {
+ const names = toNounSeriesText(testRulesLeft());
+
+ expect(wrapper.text()).toContain(
+ `Requires ${TEST_APPROVALS_LEFT} more approvals from ${names}.`,
+ );
+ });
+ });
+
+ describe('when no rulesLeft', () => {
+ beforeEach(() => {
+ createComponent({
+ rulesLeft: [],
+ });
+ });
+
+ it('renders message', () => {
+ expect(wrapper.text()).toContain(`Requires ${TEST_APPROVALS_LEFT} more approvals.`);
+ });
+ });
+
+ describe('when no approvers', () => {
+ beforeEach(() => {
+ createComponent({
+ approvers: [],
+ });
+ });
+
+ it('does not render avatar list', () => {
+ expect(wrapper.find(UserAvatarList).exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js
index 05690aa1248..e7c10ab4c2d 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js
@@ -1,39 +1,61 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
+import { shallowMount } from '@vue/test-utils';
import MrWidgetAuthor from '~/vue_merge_request_widget/components/mr_widget_author.vue';
+window.gl = window.gl || {};
+
describe('MrWidgetAuthor', () => {
- let vm;
+ let wrapper;
+ let oldWindowGl;
+ const mockAuthor = {
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://localhost:3000/root',
+ avatarUrl: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ };
beforeEach(() => {
- const Component = Vue.extend(MrWidgetAuthor);
-
- vm = mountComponent(Component, {
- author: {
- name: 'Administrator',
- username: 'root',
- webUrl: 'http://localhost:3000/root',
- avatarUrl:
- 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ oldWindowGl = window.gl;
+ window.gl = {
+ mrWidgetData: {
+ defaultAvatarUrl: 'no_avatar.png',
+ },
+ };
+ wrapper = shallowMount(MrWidgetAuthor, {
+ propsData: {
+ author: mockAuthor,
},
});
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
+ window.gl = oldWindowGl;
});
it('renders link with the author web url', () => {
- expect(vm.$el.getAttribute('href')).toEqual('http://localhost:3000/root');
+ expect(wrapper.attributes('href')).toBe('http://localhost:3000/root');
});
it('renders image with avatar url', () => {
- expect(vm.$el.querySelector('img').getAttribute('src')).toEqual(
+ expect(wrapper.find('img').attributes('src')).toBe(
'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
);
});
+ it('renders image with default avatar url when no avatarUrl is present in author', async () => {
+ wrapper.setProps({
+ author: {
+ ...mockAuthor,
+ avatarUrl: null,
+ },
+ });
+
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.find('img').attributes('src')).toBe('no_avatar.png');
+ });
+
it('renders author name', () => {
- expect(vm.$el.textContent.trim()).toEqual('Administrator');
+ expect(wrapper.find('span').text()).toBe('Administrator');
});
});
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_expandable_section_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_expandable_section_spec.js
new file mode 100644
index 00000000000..69a50899d4d
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_expandable_section_spec.js
@@ -0,0 +1,65 @@
+import { GlButton, GlCollapse, GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import MrCollapsibleSection from '~/vue_merge_request_widget/components/mr_widget_expandable_section.vue';
+
+describe('MrWidgetExpanableSection', () => {
+ let wrapper;
+
+ const findButton = () => wrapper.find(GlButton);
+ const findCollapse = () => wrapper.find(GlCollapse);
+
+ beforeEach(() => {
+ wrapper = shallowMount(MrCollapsibleSection, {
+ slots: {
+ content: '<span>Collapsable Content</span>',
+ header: '<span>Header Content</span>',
+ },
+ });
+ });
+
+ it('renders Icon', () => {
+ expect(wrapper.contains(GlIcon)).toBe(true);
+ });
+
+ it('renders header slot', () => {
+ expect(wrapper.text()).toContain('Header Content');
+ });
+
+ it('renders content slot', () => {
+ expect(wrapper.text()).toContain('Collapsable Content');
+ });
+
+ describe('when collapse section is closed', () => {
+ it('renders button with expand text', () => {
+ expect(findButton().text()).toBe('Expand');
+ });
+
+ it('renders a collpased section with no visibility', () => {
+ const collapse = findCollapse();
+
+ expect(collapse.exists()).toBe(true);
+ expect(collapse.attributes('visible')).toBeUndefined();
+ });
+ });
+
+ describe('when collapse section is open', () => {
+ beforeEach(() => {
+ findButton().vm.$emit('click');
+ return wrapper.vm.$nextTick();
+ });
+
+ it('renders button with collapse text', () => {
+ const button = findButton();
+
+ expect(button.exists()).toBe(true);
+ expect(button.text()).toBe('Collapse');
+ });
+
+ it('renders a collpased section with visible content', () => {
+ const collapse = findCollapse();
+
+ expect(collapse.exists()).toBe(true);
+ expect(collapse.attributes('visible')).toBe('true');
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_header_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_header_spec.js
index b492a69fb3d..21058005d29 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_header_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_header_spec.js
@@ -1,7 +1,13 @@
import Vue from 'vue';
+import Mousetrap from 'mousetrap';
import mountComponent from 'helpers/vue_mount_component_helper';
import headerComponent from '~/vue_merge_request_widget/components/mr_widget_header.vue';
+jest.mock('mousetrap', () => ({
+ bind: jest.fn(),
+ unbind: jest.fn(),
+}));
+
describe('MRWidgetHeader', () => {
let vm;
let Component;
@@ -126,6 +132,35 @@ describe('MRWidgetHeader', () => {
it('renders target branch', () => {
expect(vm.$el.querySelector('.js-target-branch').textContent.trim()).toEqual('master');
});
+
+ describe('keyboard shortcuts', () => {
+ it('binds a keyboard shortcut handler to the "b" key', () => {
+ expect(Mousetrap.bind).toHaveBeenCalledWith('b', expect.any(Function));
+ });
+
+ it('triggers a click on the "copy to clipboard" button when the handler is executed', () => {
+ const testClickHandler = jest.fn();
+ vm.$refs.copyBranchNameButton.$el.addEventListener('click', testClickHandler);
+
+ // Get a reference to the function that was assigned to the "b" shortcut key.
+ const shortcutHandler = Mousetrap.bind.mock.calls[0][1];
+
+ expect(testClickHandler).not.toHaveBeenCalled();
+
+ // Simulate Mousetrap calling the function.
+ shortcutHandler();
+
+ expect(testClickHandler).toHaveBeenCalledTimes(1);
+ });
+
+ it('unbinds the keyboard shortcut when the component is destroyed', () => {
+ expect(Mousetrap.unbind).not.toHaveBeenCalled();
+
+ vm.$destroy();
+
+ expect(Mousetrap.unbind).toHaveBeenCalledWith('b');
+ });
+ });
});
describe('with an open merge request', () => {
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js
index 309aec179d9..6486826c3ec 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js
@@ -1,189 +1,182 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
+import { shallowMount, mount } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
import { trimText } from 'helpers/text_helper';
-import pipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
+import { SUCCESS } from '~/vue_merge_request_widget/constants';
+import PipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
+import PipelineStage from '~/pipelines/components/pipelines_list/stage.vue';
import mockData from '../mock_data';
describe('MRWidgetPipeline', () => {
- let vm;
- let Component;
-
- beforeEach(() => {
- Component = Vue.extend(pipelineComponent);
- });
+ let wrapper;
+
+ const defaultProps = {
+ pipeline: mockData.pipeline,
+ ciStatus: SUCCESS,
+ hasCi: true,
+ mrTroubleshootingDocsPath: 'help',
+ ciTroubleshootingDocsPath: 'ci-help',
+ };
+
+ const ciErrorMessage =
+ 'Could not retrieve the pipeline status. For troubleshooting steps, read the documentation.';
+ const monitoringMessage = 'Checking pipeline status.';
+
+ const findCIErrorMessage = () => wrapper.find('[data-testid="ci-error-message"]');
+ const findPipelineID = () => wrapper.find('[data-testid="pipeline-id"]');
+ const findPipelineInfoContainer = () => wrapper.find('[data-testid="pipeline-info-container"]');
+ const findCommitLink = () => wrapper.find('[data-testid="commit-link"]');
+ const findPipelineGraph = () => wrapper.find('[data-testid="widget-mini-pipeline-graph"]');
+ const findAllPipelineStages = () => wrapper.findAll(PipelineStage);
+ const findPipelineCoverage = () => wrapper.find('[data-testid="pipeline-coverage"]');
+ const findPipelineCoverageDelta = () => wrapper.find('[data-testid="pipeline-coverage-delta"]');
+ const findMonitoringPipelineMessage = () =>
+ wrapper.find('[data-testid="monitoring-pipeline-message"]');
+ const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+
+ const createWrapper = (props, mountFn = shallowMount) => {
+ wrapper = mountFn(PipelineComponent, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+ };
afterEach(() => {
- vm.$destroy();
+ if (wrapper?.destroy) {
+ wrapper.destroy();
+ wrapper = null;
+ }
});
describe('computed', () => {
describe('hasPipeline', () => {
- it('should return true when there is a pipeline', () => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- ciStatus: 'success',
- hasCi: true,
- troubleshootingDocsPath: 'help',
- });
+ beforeEach(() => {
+ createWrapper();
+ });
- expect(vm.hasPipeline).toEqual(true);
+ it('should return true when there is a pipeline', () => {
+ expect(wrapper.vm.hasPipeline).toBe(true);
});
- it('should return false when there is no pipeline', () => {
- vm = mountComponent(Component, {
- pipeline: {},
- troubleshootingDocsPath: 'help',
- });
+ it('should return false when there is no pipeline', async () => {
+ wrapper.setProps({ pipeline: {} });
- expect(vm.hasPipeline).toEqual(false);
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.vm.hasPipeline).toBe(false);
});
});
describe('hasCIError', () => {
- it('should return false when there is no CI error', () => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- hasCi: true,
- ciStatus: 'success',
- troubleshootingDocsPath: 'help',
- });
+ beforeEach(() => {
+ createWrapper();
+ });
- expect(vm.hasCIError).toEqual(false);
+ it('should return false when there is no CI error', () => {
+ expect(wrapper.vm.hasCIError).toBe(false);
});
- it('should return true when there is a CI error', () => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- hasCi: true,
- ciStatus: null,
- troubleshootingDocsPath: 'help',
- });
+ it('should return true when there is a pipeline, but no ci status', async () => {
+ wrapper.setProps({ ciStatus: null });
- expect(vm.hasCIError).toEqual(true);
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.vm.hasCIError).toBe(true);
});
});
describe('coverageDeltaClass', () => {
- it('should return no class if there is no coverage change', () => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- pipelineCoverageDelta: '0',
- troubleshootingDocsPath: 'help',
- });
+ beforeEach(() => {
+ createWrapper({ pipelineCoverageDelta: '0' });
+ });
- expect(vm.coverageDeltaClass).toEqual('');
+ it('should return no class if there is no coverage change', async () => {
+ expect(wrapper.vm.coverageDeltaClass).toBe('');
});
- it('should return text-success if the coverage increased', () => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- pipelineCoverageDelta: '10',
- troubleshootingDocsPath: 'help',
- });
+ it('should return text-success if the coverage increased', async () => {
+ wrapper.setProps({ pipelineCoverageDelta: '10' });
- expect(vm.coverageDeltaClass).toEqual('text-success');
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.vm.coverageDeltaClass).toBe('text-success');
});
- it('should return text-danger if the coverage decreased', () => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- pipelineCoverageDelta: '-12',
- troubleshootingDocsPath: 'help',
- });
+ it('should return text-danger if the coverage decreased', async () => {
+ wrapper.setProps({ pipelineCoverageDelta: '-12' });
- expect(vm.coverageDeltaClass).toEqual('text-danger');
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.vm.coverageDeltaClass).toBe('text-danger');
});
});
});
describe('rendered output', () => {
- it('should render CI error', () => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- hasCi: true,
- troubleshootingDocsPath: 'help',
- });
-
- expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain(
- 'Could not retrieve the pipeline status. For troubleshooting steps, read the documentation.',
- );
+ beforeEach(() => {
+ createWrapper({ ciStatus: null }, mount);
});
- it('should render CI error when no pipeline is provided', () => {
- vm = mountComponent(Component, {
- pipeline: {},
- hasCi: true,
- ciStatus: 'success',
- troubleshootingDocsPath: 'help',
- });
-
- expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain(
- 'Could not retrieve the pipeline status. For troubleshooting steps, read the documentation.',
- );
+ it('should render CI error if there is a pipeline, but no status', async () => {
+ expect(findCIErrorMessage().text()).toBe(ciErrorMessage);
});
- it('should render CI error when no CI is provided and pipeline must succeed is turned on', () => {
- vm = mountComponent(Component, {
+ it('should render a loading state when no pipeline is found', async () => {
+ wrapper.setProps({
pipeline: {},
hasCi: false,
pipelineMustSucceed: true,
- troubleshootingDocsPath: 'help',
});
- expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain(
- 'No pipeline has been run for this commit.',
- );
+ await wrapper.vm.$nextTick();
+
+ expect(findMonitoringPipelineMessage().text()).toBe(monitoringMessage);
+ expect(findLoadingIcon().exists()).toBe(true);
});
describe('with a pipeline', () => {
beforeEach(() => {
- vm = mountComponent(Component, {
- pipeline: mockData.pipeline,
- hasCi: true,
- ciStatus: 'success',
- pipelineCoverageDelta: mockData.pipelineCoverageDelta,
- troubleshootingDocsPath: 'help',
- });
+ createWrapper(
+ {
+ pipelineCoverageDelta: mockData.pipelineCoverageDelta,
+ },
+ mount,
+ );
});
it('should render pipeline ID', () => {
- expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual(
- `#${mockData.pipeline.id}`,
- );
+ expect(
+ findPipelineID()
+ .text()
+ .trim(),
+ ).toBe(`#${mockData.pipeline.id}`);
});
it('should render pipeline status and commit id', () => {
- expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain(
- mockData.pipeline.details.status.label,
- );
+ expect(findPipelineInfoContainer().text()).toMatch(mockData.pipeline.details.status.label);
- expect(vm.$el.querySelector('.js-commit-link').textContent.trim()).toEqual(
- mockData.pipeline.commit.short_id,
- );
+ expect(
+ findCommitLink()
+ .text()
+ .trim(),
+ ).toBe(mockData.pipeline.commit.short_id);
- expect(vm.$el.querySelector('.js-commit-link').getAttribute('href')).toEqual(
- mockData.pipeline.commit.commit_path,
- );
+ expect(findCommitLink().attributes('href')).toBe(mockData.pipeline.commit.commit_path);
});
it('should render pipeline graph', () => {
- expect(vm.$el.querySelector('.mr-widget-pipeline-graph')).toBeDefined();
- expect(vm.$el.querySelectorAll('.stage-container').length).toEqual(
- mockData.pipeline.details.stages.length,
- );
+ expect(findPipelineGraph().exists()).toBe(true);
+ expect(findAllPipelineStages().length).toBe(mockData.pipeline.details.stages.length);
});
it('should render coverage information', () => {
- expect(vm.$el.querySelector('.media-body').textContent).toContain(
- `Coverage ${mockData.pipeline.coverage}`,
- );
+ expect(findPipelineCoverage().text()).toMatch(`Coverage ${mockData.pipeline.coverage}%`);
});
it('should render pipeline coverage delta information', () => {
- expect(vm.$el.querySelector('.js-pipeline-coverage-delta.text-danger')).toBeDefined();
- expect(vm.$el.querySelector('.js-pipeline-coverage-delta').textContent).toContain(
- `(${mockData.pipelineCoverageDelta}%)`,
- );
+ expect(findPipelineCoverageDelta().exists()).toBe(true);
+ expect(findPipelineCoverageDelta().text()).toBe(`(${mockData.pipelineCoverageDelta}%)`);
});
});
@@ -192,71 +185,61 @@ describe('MRWidgetPipeline', () => {
const mockCopy = JSON.parse(JSON.stringify(mockData));
delete mockCopy.pipeline.commit;
- vm = mountComponent(Component, {
- pipeline: mockCopy.pipeline,
- hasCi: true,
- ciStatus: 'success',
- troubleshootingDocsPath: 'help',
- });
+ createWrapper({}, mount);
});
it('should render pipeline ID', () => {
- expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual(
- `#${mockData.pipeline.id}`,
- );
+ expect(
+ findPipelineID()
+ .text()
+ .trim(),
+ ).toBe(`#${mockData.pipeline.id}`);
});
it('should render pipeline status', () => {
- expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain(
- mockData.pipeline.details.status.label,
- );
-
- expect(vm.$el.querySelector('.js-commit-link')).toBeNull();
+ expect(findPipelineInfoContainer().text()).toMatch(mockData.pipeline.details.status.label);
});
it('should render pipeline graph', () => {
- expect(vm.$el.querySelector('.mr-widget-pipeline-graph')).toBeDefined();
- expect(vm.$el.querySelectorAll('.stage-container').length).toEqual(
- mockData.pipeline.details.stages.length,
- );
+ expect(findPipelineGraph().exists()).toBe(true);
+ expect(findAllPipelineStages().length).toBe(mockData.pipeline.details.stages.length);
});
it('should render coverage information', () => {
- expect(vm.$el.querySelector('.media-body').textContent).toContain(
- `Coverage ${mockData.pipeline.coverage}`,
- );
+ expect(findPipelineCoverage().text()).toMatch(`Coverage ${mockData.pipeline.coverage}%`);
});
});
describe('without coverage', () => {
- it('should not render a coverage', () => {
+ beforeEach(() => {
const mockCopy = JSON.parse(JSON.stringify(mockData));
delete mockCopy.pipeline.coverage;
- vm = mountComponent(Component, {
- pipeline: mockCopy.pipeline,
- hasCi: true,
- ciStatus: 'success',
- troubleshootingDocsPath: 'help',
- });
+ createWrapper(
+ {
+ pipeline: mockCopy.pipeline,
+ },
+ mount,
+ );
+ });
- expect(vm.$el.querySelector('.media-body').textContent).not.toContain('Coverage');
+ it('should not render a coverage component', () => {
+ expect(findPipelineCoverage().exists()).toBe(false);
});
});
describe('without a pipeline graph', () => {
- it('should not render a pipeline graph', () => {
+ beforeEach(() => {
const mockCopy = JSON.parse(JSON.stringify(mockData));
delete mockCopy.pipeline.details.stages;
- vm = mountComponent(Component, {
+ createWrapper({
pipeline: mockCopy.pipeline,
- hasCi: true,
- ciStatus: 'success',
- troubleshootingDocsPath: 'help',
});
+ });
- expect(vm.$el.querySelector('.js-mini-pipeline-graph')).toEqual(null);
+ it('should not render a pipeline graph', () => {
+ expect(findPipelineGraph().exists()).toBe(false);
});
});
@@ -273,11 +256,8 @@ describe('MRWidgetPipeline', () => {
});
const factory = () => {
- vm = mountComponent(Component, {
+ createWrapper({
pipeline,
- hasCi: true,
- ciStatus: 'success',
- troubleshootingDocsPath: 'help',
sourceBranchLink: mockData.source_branch_link,
});
};
@@ -289,7 +269,7 @@ describe('MRWidgetPipeline', () => {
factory();
const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id} on ${mockData.source_branch_link}`;
- const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText);
+ const actual = trimText(findPipelineInfoContainer().text());
expect(actual).toBe(expected);
});
@@ -302,7 +282,7 @@ describe('MRWidgetPipeline', () => {
factory();
const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id}`;
- const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText);
+ const actual = trimText(findPipelineInfoContainer().text());
expect(actual).toBe(expected);
});
@@ -316,7 +296,7 @@ describe('MRWidgetPipeline', () => {
factory();
const expected = `Detached merge request pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id}`;
- const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText);
+ const actual = trimText(findPipelineInfoContainer().text());
expect(actual).toBe(expected);
});
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js
index 8b0253dc01a..d6c996f7501 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js
@@ -1,37 +1,44 @@
import { mount } from '@vue/test-utils';
-import { GlLink } from '@gitlab/ui';
+import { GlLink, GlSprintf } from '@gitlab/ui';
import suggestPipelineComponent from '~/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue';
-import stubChildren from 'helpers/stub_children';
-import PipelineTourState from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_tour.vue';
import MrWidgetIcon from '~/vue_merge_request_widget/components/mr_widget_icon.vue';
import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
+import { popoverProps, iconName } from './pipeline_tour_mock_data';
-describe('MRWidgetHeader', () => {
+describe('MRWidgetSuggestPipeline', () => {
let wrapper;
- const pipelinePath = '/foo/bar/add/pipeline/path';
- const pipelineSvgPath = '/foo/bar/pipeline/svg/path';
- const humanAccess = 'maintainer';
- const iconName = 'status_notfound';
+ let trackingSpy;
+
+ const mockTrackingOnWrapper = () => {
+ unmockTracking();
+ trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
+ };
beforeEach(() => {
+ document.body.dataset.page = 'projects:merge_requests:show';
+ trackingSpy = mockTracking('_category_', undefined, jest.spyOn);
+
wrapper = mount(suggestPipelineComponent, {
- propsData: { pipelinePath, pipelineSvgPath, humanAccess },
+ propsData: popoverProps,
stubs: {
- ...stubChildren(PipelineTourState),
+ GlSprintf,
},
});
});
afterEach(() => {
wrapper.destroy();
+ unmockTracking();
});
describe('template', () => {
+ const findOkBtn = () => wrapper.find('[data-testid="ok"]');
+
it('renders add pipeline file link', () => {
const link = wrapper.find(GlLink);
expect(link.exists()).toBe(true);
- expect(link.attributes().href).toBe(pipelinePath);
+ expect(link.attributes().href).toBe(popoverProps.pipelinePath);
});
it('renders the expected text', () => {
@@ -51,25 +58,60 @@ describe('MRWidgetHeader', () => {
);
});
+ it('renders the show me how button', () => {
+ const button = findOkBtn();
+
+ expect(button.exists()).toBe(true);
+ expect(button.classes('btn-info')).toEqual(true);
+ expect(button.attributes('href')).toBe(popoverProps.pipelinePath);
+ });
+
+ it('renders the help link', () => {
+ const link = wrapper.find('[data-testid="help"]');
+
+ expect(link.exists()).toBe(true);
+ expect(link.attributes('href')).toBe(wrapper.vm.$options.helpURL);
+ });
+
+ it('renders the empty pipelines image', () => {
+ const image = wrapper.find('[data-testid="pipeline-image"]');
+
+ expect(image.exists()).toBe(true);
+ expect(image.attributes().src).toBe(popoverProps.pipelineSvgPath);
+ });
+
describe('tracking', () => {
- let spy;
+ it('send event for basic view of the suggest pipeline widget', () => {
+ const expectedCategory = undefined;
+ const expectedAction = undefined;
- beforeEach(() => {
- spy = mockTracking('_category_', wrapper.element, jest.spyOn);
+ expect(trackingSpy).toHaveBeenCalledWith(expectedCategory, expectedAction, {
+ label: wrapper.vm.$options.trackLabel,
+ property: popoverProps.humanAccess,
+ });
});
- afterEach(() => {
- unmockTracking();
+ it('send an event when add pipeline link is clicked', () => {
+ mockTrackingOnWrapper();
+ const link = wrapper.find('[data-testid="add-pipeline-link"]');
+ triggerEvent(link.element);
+
+ expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_link', {
+ label: wrapper.vm.$options.trackLabel,
+ property: popoverProps.humanAccess,
+ value: '30',
+ });
});
it('send an event when ok button is clicked', () => {
- const link = wrapper.find(GlLink);
- triggerEvent(link.element);
+ mockTrackingOnWrapper();
+ const okBtn = findOkBtn();
+ triggerEvent(okBtn.element);
- expect(spy).toHaveBeenCalledWith('_category_', 'click_link', {
- label: 'no_pipeline_noticed',
- property: humanAccess,
- value: '30',
+ expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_button', {
+ label: wrapper.vm.$options.trackLabel,
+ property: popoverProps.humanAccess,
+ value: '10',
});
});
});
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_terraform_plan_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_terraform_plan_spec.js
deleted file mode 100644
index 62c5c8e8531..00000000000
--- a/spec/frontend/vue_mr_widget/components/mr_widget_terraform_plan_spec.js
+++ /dev/null
@@ -1,107 +0,0 @@
-import { GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import axios from '~/lib/utils/axios_utils';
-import MockAdapter from 'axios-mock-adapter';
-import MrWidgetTerraformPlan from '~/vue_merge_request_widget/components/mr_widget_terraform_plan.vue';
-import Poll from '~/lib/utils/poll';
-
-const plan = {
- create: 10,
- update: 20,
- delete: 30,
- job_path: '/path/to/ci/logs',
-};
-
-describe('MrWidgetTerraformPlan', () => {
- let mock;
- let wrapper;
-
- const propsData = { endpoint: '/path/to/terraform/report.json' };
-
- const mockPollingApi = (response, body, header) => {
- mock.onGet(propsData.endpoint).reply(response, body, header);
- };
-
- const mountWrapper = () => {
- wrapper = shallowMount(MrWidgetTerraformPlan, { propsData });
- return axios.waitForAll();
- };
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- wrapper.destroy();
- mock.restore();
- });
-
- describe('loading poll', () => {
- beforeEach(() => {
- mockPollingApi(200, { '123': plan }, {});
-
- return mountWrapper().then(() => {
- wrapper.setData({ loading: true });
- return wrapper.vm.$nextTick();
- });
- });
-
- it('Diplays loading icon when loading is true', () => {
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
-
- expect(wrapper.find(GlSprintf).exists()).toBe(false);
-
- expect(wrapper.text()).not.toContain(
- 'A terraform report was generated in your pipelines. Changes are unknown',
- );
- });
- });
-
- describe('successful poll', () => {
- let pollRequest;
- let pollStop;
-
- beforeEach(() => {
- pollRequest = jest.spyOn(Poll.prototype, 'makeRequest');
- pollStop = jest.spyOn(Poll.prototype, 'stop');
-
- mockPollingApi(200, { '123': plan }, {});
-
- return mountWrapper();
- });
-
- afterEach(() => {
- pollRequest.mockRestore();
- pollStop.mockRestore();
- });
-
- it('content change text', () => {
- expect(wrapper.find(GlSprintf).exists()).toBe(true);
- });
-
- it('renders button when url is found', () => {
- expect(wrapper.find(GlLink).exists()).toBe(true);
- });
-
- it('does not make additional requests after poll is successful', () => {
- expect(pollRequest).toHaveBeenCalledTimes(1);
- expect(pollStop).toHaveBeenCalledTimes(1);
- });
- });
-
- describe('polling fails', () => {
- beforeEach(() => {
- mockPollingApi(500, null, {});
- return mountWrapper();
- });
-
- it('does not display changes text when api fails', () => {
- expect(wrapper.text()).toContain(
- 'A terraform report was generated in your pipelines. Changes are unknown',
- );
-
- expect(wrapper.find('.js-terraform-report-link').exists()).toBe(false);
- expect(wrapper.find(GlLink).exists()).toBe(false);
- });
- });
-});
diff --git a/spec/frontend/vue_mr_widget/components/pipeline_tour_mock_data.js b/spec/frontend/vue_mr_widget/components/pipeline_tour_mock_data.js
new file mode 100644
index 00000000000..c749c434079
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/pipeline_tour_mock_data.js
@@ -0,0 +1,7 @@
+export const popoverProps = {
+ pipelinePath: '/foo/bar/add/pipeline/path',
+ pipelineSvgPath: 'assets/illustrations/something.svg',
+ humanAccess: 'maintainer',
+};
+
+export const iconName = 'status_notfound';
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js
index e2caa6e8092..ae0f605c419 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js
@@ -8,6 +8,7 @@ import { MWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants';
describe('MRWidgetAutoMergeEnabled', () => {
let vm;
+ let oldWindowGl;
const targetBranchPath = '/foo/bar';
const targetBranch = 'foo';
const sha = '1EA2EZ34';
@@ -16,6 +17,13 @@ describe('MRWidgetAutoMergeEnabled', () => {
const Component = Vue.extend(autoMergeEnabledComponent);
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ oldWindowGl = window.gl;
+ window.gl = {
+ mrWidgetData: {
+ defaultAvatarUrl: 'no_avatar.png',
+ },
+ };
+
vm = mountComponent(Component, {
mr: {
shouldRemoveSourceBranch: false,
@@ -35,6 +43,7 @@ describe('MRWidgetAutoMergeEnabled', () => {
afterEach(() => {
vm.$destroy();
+ window.gl = oldWindowGl;
});
describe('computed', () => {
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_checking_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_checking_spec.js
index 56d55c9afac..afe6bd0e767 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_checking_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_checking_spec.js
@@ -25,7 +25,7 @@ describe('MRWidgetChecking', () => {
it('renders information about merging', () => {
expect(vm.$el.querySelector('.media-body').textContent.trim()).toEqual(
- 'Checking ability to merge automatically…',
+ 'Checking if merge request can be merged…',
);
});
});
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_tour_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_tour_spec.js
deleted file mode 100644
index e8f95e099cc..00000000000
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_tour_spec.js
+++ /dev/null
@@ -1,143 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlPopover } from '@gitlab/ui';
-import Cookies from 'js-cookie';
-import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
-import pipelineTourState from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_tour.vue';
-import { popoverProps, cookieKey } from './pipeline_tour_mock_data';
-
-describe('MRWidgetPipelineTour', () => {
- let wrapper;
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('template', () => {
- describe(`when ${cookieKey} cookie is set`, () => {
- beforeEach(() => {
- Cookies.set(cookieKey, true);
- wrapper = shallowMount(pipelineTourState, {
- propsData: popoverProps,
- });
- });
-
- it('does not render the popover', () => {
- const popover = wrapper.find(GlPopover);
-
- expect(popover.exists()).toBe(false);
- });
-
- describe('tracking', () => {
- let trackingSpy;
-
- beforeEach(() => {
- trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
- });
-
- afterEach(() => {
- unmockTracking();
- });
- it('does not call tracking', () => {
- expect(trackingSpy).not.toHaveBeenCalled();
- });
- });
- });
-
- describe(`when ${cookieKey} cookie is not set`, () => {
- const findOkBtn = () => wrapper.find({ ref: 'ok' });
- const findDismissBtn = () => wrapper.find({ ref: 'no-thanks' });
-
- beforeEach(() => {
- Cookies.remove(cookieKey);
- wrapper = shallowMount(pipelineTourState, {
- propsData: popoverProps,
- });
- });
-
- it('renders the popover', () => {
- const popover = wrapper.find(GlPopover);
-
- expect(popover.exists()).toBe(true);
- });
-
- it('renders the show me how button', () => {
- const button = findOkBtn();
-
- expect(button.exists()).toBe(true);
- expect(button.attributes().category).toBe('primary');
- });
-
- it('renders the dismiss button', () => {
- const button = findDismissBtn();
-
- expect(button.exists()).toBe(true);
- expect(button.attributes().category).toBe('secondary');
- });
-
- it('renders the empty pipelines image', () => {
- const image = wrapper.find('img');
-
- expect(image.exists()).toBe(true);
- expect(image.attributes().src).toBe(popoverProps.pipelineSvgPath);
- });
-
- describe('tracking', () => {
- let trackingSpy;
-
- beforeEach(() => {
- trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
- });
-
- afterEach(() => {
- unmockTracking();
- });
-
- it('send event for basic view of popover', () => {
- document.body.dataset.page = 'projects:merge_requests:show';
-
- wrapper.vm.trackOnShow();
-
- expect(trackingSpy).toHaveBeenCalledWith(undefined, undefined, {
- label: popoverProps.trackLabel,
- property: popoverProps.humanAccess,
- });
- });
-
- it('send an event when ok button is clicked', () => {
- const okBtn = findOkBtn();
- triggerEvent(okBtn.element);
-
- expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_button', {
- label: popoverProps.trackLabel,
- property: popoverProps.humanAccess,
- value: '10',
- });
- });
-
- it('send an event when dismiss button is clicked', () => {
- const dismissBtn = findDismissBtn();
- triggerEvent(dismissBtn.element);
-
- expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_button', {
- label: popoverProps.trackLabel,
- property: popoverProps.humanAccess,
- value: '20',
- });
- });
- });
-
- describe('dismissPopover', () => {
- it('updates popoverDismissed', () => {
- const button = findDismissBtn();
- const popover = wrapper.find(GlPopover);
- button.vm.$emit('click');
-
- return wrapper.vm.$nextTick().then(() => {
- expect(Cookies.get(cookieKey)).toBe('true');
- expect(popover.exists()).toBe(false);
- });
- });
- });
- });
- });
-});
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index 1f0d6a7378c..5eb24315ca6 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -34,6 +34,9 @@ const createTestMr = customConfig => {
ciStatus: null,
sha: '12345678',
squash: false,
+ squashIsEnabledByDefault: false,
+ squashIsReadonly: false,
+ squashIsSelected: false,
commitMessage,
squashCommitMessage,
commitMessageWithDescription,
@@ -694,6 +697,37 @@ describe('ReadyToMerge', () => {
expect(findCheckboxElement().exists()).toBeFalsy();
});
+
+ describe('squash options', () => {
+ it.each`
+ squashState | state | prop | expectation
+ ${'squashIsReadonly'} | ${'enabled'} | ${'isDisabled'} | ${false}
+ ${'squashIsSelected'} | ${'selected'} | ${'value'} | ${false}
+ ${'squashIsSelected'} | ${'unselected'} | ${'value'} | ${false}
+ `(
+ 'is $state when squashIsReadonly returns $expectation ',
+ ({ squashState, prop, expectation }) => {
+ createLocalComponent({
+ mr: { commitsCount: 2, enableSquashBeforeMerge: true, [squashState]: expectation },
+ });
+
+ expect(findCheckboxElement().props(prop)).toBe(expectation);
+ },
+ );
+
+ it('is not rendered for "Do not allow" option', () => {
+ createLocalComponent({
+ mr: {
+ commitsCount: 2,
+ enableSquashBeforeMerge: true,
+ squashIsReadonly: true,
+ squashIsSelected: false,
+ },
+ });
+
+ expect(findCheckboxElement().exists()).toBe(false);
+ });
+ });
});
describe('commits count collapsible header', () => {
@@ -709,7 +743,7 @@ describe('ReadyToMerge', () => {
mr: {
ffOnlyEnabled: true,
enableSquashBeforeMerge: true,
- squash: true,
+ squashIsSelected: true,
commitsCount: 2,
},
});
@@ -803,7 +837,7 @@ describe('ReadyToMerge', () => {
createLocalComponent({
mr: {
ffOnlyEnabled: true,
- squash: true,
+ squashIsSelected: true,
enableSquashBeforeMerge: true,
commitsCount: 2,
},
@@ -824,7 +858,7 @@ describe('ReadyToMerge', () => {
createLocalComponent({
mr: {
commitsCount: 2,
- squash: true,
+ squashIsSelected: true,
enableSquashBeforeMerge: true,
},
});
@@ -854,7 +888,7 @@ describe('ReadyToMerge', () => {
createLocalComponent({
mr: {
commitsCount: 2,
- squash: true,
+ squashIsSelected: true,
enableSquashBeforeMerge: true,
},
});
@@ -872,7 +906,7 @@ describe('ReadyToMerge', () => {
it('should be rendered if squash is enabled and there is more than 1 commit', () => {
createLocalComponent({
- mr: { enableSquashBeforeMerge: true, squash: true, commitsCount: 2 },
+ mr: { enableSquashBeforeMerge: true, squashIsSelected: true, commitsCount: 2 },
});
expect(findCommitDropdownElement().exists()).toBeTruthy();
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
index b70d580ed04..1542b0939aa 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
@@ -19,6 +19,8 @@ describe('Squash before merge component', () => {
wrapper.destroy();
});
+ const findLabel = () => wrapper.find('[data-testid="squashLabel"]');
+
describe('checkbox', () => {
const findCheckbox = () => wrapper.find('.js-squash-checkbox');
@@ -63,6 +65,46 @@ describe('Squash before merge component', () => {
});
});
+ describe('label', () => {
+ describe.each`
+ isDisabled | expectation
+ ${true} | ${'grays out text if it is true'}
+ ${false} | ${'does not gray out text if it is false'}
+ `('isDisabled prop', ({ isDisabled, expectation }) => {
+ beforeEach(() => {
+ createComponent({
+ value: false,
+ isDisabled,
+ });
+ });
+
+ it(expectation, () => {
+ expect(findLabel().classes('gl-text-gray-600')).toBe(isDisabled);
+ });
+ });
+ });
+
+ describe('tooltip', () => {
+ const tooltipTitle = () => findLabel().element.dataset.title;
+
+ it('does not render when isDisabled is false', () => {
+ createComponent({
+ value: true,
+ isDisabled: false,
+ });
+ expect(tooltipTitle()).toBeUndefined();
+ });
+
+ it('display message when when isDisabled is true', () => {
+ createComponent({
+ value: true,
+ isDisabled: true,
+ });
+
+ expect(tooltipTitle()).toBe('Required in this project.');
+ });
+ });
+
describe('about link', () => {
it('is not rendered if no help path is passed', () => {
createComponent({
diff --git a/spec/frontend/vue_mr_widget/components/states/pipeline_tour_mock_data.js b/spec/frontend/vue_mr_widget/components/states/pipeline_tour_mock_data.js
deleted file mode 100644
index 39bc89e459c..00000000000
--- a/spec/frontend/vue_mr_widget/components/states/pipeline_tour_mock_data.js
+++ /dev/null
@@ -1,10 +0,0 @@
-export const popoverProps = {
- pipelinePath: '/foo/bar/add/pipeline/path',
- pipelineSvgPath: 'assets/illustrations/something.svg',
- humanAccess: 'maintainer',
- popoverTarget: 'suggest-popover',
- popoverContainer: 'suggest-pipeline',
- trackLabel: 'some_tracking_label',
-};
-
-export const cookieKey = 'suggest_pipeline_dismissed';
diff --git a/spec/frontend/vue_mr_widget/components/terraform/mock_data.js b/spec/frontend/vue_mr_widget/components/terraform/mock_data.js
new file mode 100644
index 00000000000..ae280146c22
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/terraform/mock_data.js
@@ -0,0 +1,31 @@
+export const invalidPlanWithName = {
+ job_name: 'Invalid Plan',
+ job_path: '/path/to/ci/logs/1',
+ tf_report_error: 'api_error',
+};
+
+export const invalidPlanWithoutName = {
+ tf_report_error: 'invalid_json_format',
+};
+
+export const validPlanWithName = {
+ create: 10,
+ update: 20,
+ delete: 30,
+ job_name: 'Valid Plan',
+ job_path: '/path/to/ci/logs/1',
+};
+
+export const validPlanWithoutName = {
+ create: 10,
+ update: 20,
+ delete: 30,
+ job_path: '/path/to/ci/logs/1',
+};
+
+export const plans = {
+ invalid_plan_one: invalidPlanWithName,
+ invalid_plan_two: invalidPlanWithName,
+ valid_plan_one: validPlanWithName,
+ valid_plan_two: validPlanWithoutName,
+};
diff --git a/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js b/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js
new file mode 100644
index 00000000000..be43f10c03e
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js
@@ -0,0 +1,172 @@
+import { GlSkeletonLoading, GlSprintf } from '@gitlab/ui';
+import { invalidPlanWithName, plans, validPlanWithName } from './mock_data';
+import { shallowMount } from '@vue/test-utils';
+import axios from '~/lib/utils/axios_utils';
+import MockAdapter from 'axios-mock-adapter';
+import MrWidgetExpanableSection from '~/vue_merge_request_widget/components/mr_widget_expandable_section.vue';
+import MrWidgetTerraformContainer from '~/vue_merge_request_widget/components/terraform/mr_widget_terraform_container.vue';
+import Poll from '~/lib/utils/poll';
+import TerraformPlan from '~/vue_merge_request_widget/components/terraform/terraform_plan.vue';
+
+describe('MrWidgetTerraformConainer', () => {
+ let mock;
+ let wrapper;
+
+ const propsData = { endpoint: '/path/to/terraform/report.json' };
+
+ const findHeader = () => wrapper.find('[data-testid="terraform-header-text"]');
+ const findPlans = () => wrapper.findAll(TerraformPlan).wrappers.map(x => x.props('plan'));
+
+ const mockPollingApi = (response, body, header) => {
+ mock.onGet(propsData.endpoint).reply(response, body, header);
+ };
+
+ const mountWrapper = () => {
+ wrapper = shallowMount(MrWidgetTerraformContainer, {
+ propsData,
+ stubs: { MrWidgetExpanableSection, GlSprintf },
+ });
+ return axios.waitForAll();
+ };
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ mock.restore();
+ });
+
+ describe('when data is loading', () => {
+ beforeEach(() => {
+ mockPollingApi(200, plans, {});
+
+ return mountWrapper().then(() => {
+ wrapper.setData({ loading: true });
+ return wrapper.vm.$nextTick();
+ });
+ });
+
+ it('diplays loading skeleton', () => {
+ expect(wrapper.contains(GlSkeletonLoading)).toBe(true);
+ expect(wrapper.contains(MrWidgetExpanableSection)).toBe(false);
+ });
+ });
+
+ describe('when data has finished loading', () => {
+ beforeEach(() => {
+ mockPollingApi(200, plans, {});
+ return mountWrapper();
+ });
+
+ it('displays terraform content', () => {
+ expect(wrapper.contains(GlSkeletonLoading)).toBe(false);
+ expect(wrapper.contains(MrWidgetExpanableSection)).toBe(true);
+ expect(findPlans()).toEqual(Object.values(plans));
+ });
+
+ describe('when data includes one invalid plan', () => {
+ beforeEach(() => {
+ const invalidPlanGroup = { bad_plan: invalidPlanWithName };
+ mockPollingApi(200, invalidPlanGroup, {});
+ return mountWrapper();
+ });
+
+ it('displays header text for one invalid plan', () => {
+ expect(findHeader().text()).toBe('1 Terraform report failed to generate');
+ });
+ });
+
+ describe('when data includes multiple invalid plans', () => {
+ beforeEach(() => {
+ const invalidPlanGroup = {
+ bad_plan_one: invalidPlanWithName,
+ bad_plan_two: invalidPlanWithName,
+ };
+
+ mockPollingApi(200, invalidPlanGroup, {});
+ return mountWrapper();
+ });
+
+ it('displays header text for multiple invalid plans', () => {
+ expect(findHeader().text()).toBe('2 Terraform reports failed to generate');
+ });
+ });
+
+ describe('when data includes one valid plan', () => {
+ beforeEach(() => {
+ const validPlanGroup = { valid_plan: validPlanWithName };
+ mockPollingApi(200, validPlanGroup, {});
+ return mountWrapper();
+ });
+
+ it('displays header text for one valid plans', () => {
+ expect(findHeader().text()).toBe('1 Terraform report was generated in your pipelines');
+ });
+ });
+
+ describe('when data includes multiple valid plans', () => {
+ beforeEach(() => {
+ const validPlanGroup = {
+ valid_plan_one: validPlanWithName,
+ valid_plan_two: validPlanWithName,
+ };
+ mockPollingApi(200, validPlanGroup, {});
+ return mountWrapper();
+ });
+
+ it('displays header text for multiple valid plans', () => {
+ expect(findHeader().text()).toBe('2 Terraform reports were generated in your pipelines');
+ });
+ });
+ });
+
+ describe('polling', () => {
+ let pollRequest;
+ let pollStop;
+
+ beforeEach(() => {
+ pollRequest = jest.spyOn(Poll.prototype, 'makeRequest');
+ pollStop = jest.spyOn(Poll.prototype, 'stop');
+ });
+
+ afterEach(() => {
+ pollRequest.mockRestore();
+ pollStop.mockRestore();
+ });
+
+ describe('successful poll', () => {
+ beforeEach(() => {
+ mockPollingApi(200, plans, {});
+
+ return mountWrapper();
+ });
+
+ it('does not make additional requests after poll is successful', () => {
+ expect(pollRequest).toHaveBeenCalledTimes(1);
+ expect(pollStop).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('polling fails', () => {
+ beforeEach(() => {
+ mockPollingApi(500, null, {});
+ return mountWrapper();
+ });
+
+ it('stops loading', () => {
+ expect(wrapper.contains(GlSkeletonLoading)).toBe(false);
+ });
+
+ it('generates one broken plan', () => {
+ expect(findPlans()).toEqual([{ tf_report_error: 'api_error' }]);
+ });
+
+ it('does not make additional requests after poll is unsuccessful', () => {
+ expect(pollRequest).toHaveBeenCalledTimes(1);
+ expect(pollStop).toHaveBeenCalledTimes(1);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/components/terraform/terraform_plan_spec.js b/spec/frontend/vue_mr_widget/components/terraform/terraform_plan_spec.js
new file mode 100644
index 00000000000..cc68ba0d9df
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/terraform/terraform_plan_spec.js
@@ -0,0 +1,95 @@
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import TerraformPlan from '~/vue_merge_request_widget/components/terraform/terraform_plan.vue';
+import {
+ invalidPlanWithName,
+ invalidPlanWithoutName,
+ validPlanWithName,
+ validPlanWithoutName,
+} from './mock_data';
+
+describe('TerraformPlan', () => {
+ let wrapper;
+
+ const findIcon = () => wrapper.find('[data-testid="change-type-icon"]');
+ const findLogButton = () => wrapper.find('[data-testid="terraform-report-link"]');
+
+ const mountWrapper = propsData => {
+ wrapper = shallowMount(TerraformPlan, { stubs: { GlLink, GlSprintf }, propsData });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('valid plan with job_name', () => {
+ beforeEach(() => {
+ mountWrapper({ plan: validPlanWithName });
+ });
+
+ it('displays a document icon', () => {
+ expect(findIcon().attributes('name')).toBe('doc-changes');
+ });
+
+ it('diplays the header text with a name', () => {
+ expect(wrapper.text()).toContain(
+ `The Terraform report ${validPlanWithName.job_name} was generated in your pipelines.`,
+ );
+ });
+
+ it('diplays the reported changes', () => {
+ expect(wrapper.text()).toContain(
+ `Reported Resource Changes: ${validPlanWithName.create} to add, ${validPlanWithName.update} to change, ${validPlanWithName.delete} to delete`,
+ );
+ });
+
+ it('renders button when url is found', () => {
+ expect(findLogButton().exists()).toBe(true);
+ expect(findLogButton().text()).toEqual('View full log');
+ });
+ });
+
+ describe('valid plan without job_name', () => {
+ beforeEach(() => {
+ mountWrapper({ plan: validPlanWithoutName });
+ });
+
+ it('diplays the header text without a name', () => {
+ expect(wrapper.text()).toContain('A Terraform report was generated in your pipelines.');
+ });
+ });
+
+ describe('invalid plan with job_name', () => {
+ beforeEach(() => {
+ mountWrapper({ plan: invalidPlanWithName });
+ });
+
+ it('displays a warning icon', () => {
+ expect(findIcon().attributes('name')).toBe('warning');
+ });
+
+ it('diplays the header text with a name', () => {
+ expect(wrapper.text()).toContain(
+ `The Terraform report ${invalidPlanWithName.job_name} failed to generate.`,
+ );
+ });
+
+ it('diplays generic error since report values are missing', () => {
+ expect(wrapper.text()).toContain('Generating the report caused an error.');
+ });
+ });
+
+ describe('invalid plan with out job_name', () => {
+ beforeEach(() => {
+ mountWrapper({ plan: invalidPlanWithoutName });
+ });
+
+ it('diplays the header text without a name', () => {
+ expect(wrapper.text()).toContain('A Terraform report failed to generate.');
+ });
+
+ it('does not render button because url is missing', () => {
+ expect(findLogButton().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/mock_data.js b/spec/frontend/vue_mr_widget/mock_data.js
index 8ed153658fd..e00456a78b5 100644
--- a/spec/frontend/vue_mr_widget/mock_data.js
+++ b/spec/frontend/vue_mr_widget/mock_data.js
@@ -211,6 +211,15 @@ export default {
can_revert_on_current_merge_request: true,
can_cherry_pick_on_current_merge_request: true,
},
+ codeclimate: {
+ head_path: 'head.json',
+ base_path: 'base.json',
+ },
+ blob_path: {
+ base_path: 'blob_path',
+ head_path: 'blob_path',
+ },
+ codequality_help_path: 'code_quality.html',
target_branch_path: '/root/acets-app/branches/master',
source_branch_path: '/root/acets-app/branches/daaaa',
conflict_resolution_ui_path: '/root/acets-app/-/merge_requests/22/conflicts',
@@ -239,7 +248,8 @@ export default {
commit_change_content_path: '/root/acets-app/-/merge_requests/22/commit_change_content',
merge_commit_path:
'http://localhost:3000/root/acets-app/commit/53027d060246c8f47e4a9310fb332aa52f221775',
- troubleshooting_docs_path: 'help',
+ mr_troubleshooting_docs_path: 'help',
+ ci_troubleshooting_docs_path: 'help2',
merge_request_pipelines_docs_path: '/help/ci/merge_request_pipelines/index.md',
merge_train_when_pipeline_succeeds_docs_path:
'/help/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/#startadd-to-merge-train-when-pipeline-succeeds',
@@ -312,7 +322,8 @@ export const mockStore = {
{ id: 0, name: 'prod', status: SUCCESS },
{ id: 1, name: 'prod-docs', status: SUCCESS },
],
- troubleshootingDocsPath: 'troubleshooting-docs-path',
+ mrTroubleshootingDocsPath: 'mr-troubleshooting-docs-path',
+ ciTroubleshootingDocsPath: 'ci-troubleshooting-docs-path',
ciStatus: 'ci-status',
hasCI: true,
exposedArtifactsPath: 'exposed_artifacts.json',
diff --git a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
index e022f68fdec..93659fa54fb 100644
--- a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
@@ -609,6 +609,12 @@ describe('mrWidgetOptions', () => {
});
});
+ describe('code quality widget', () => {
+ it('renders the component', () => {
+ expect(vm.$el.querySelector('.js-codequality-widget')).toExist();
+ });
+ });
+
describe('pipeline for target branch after merge', () => {
describe('with information for target branch pipeline', () => {
beforeEach(done => {
diff --git a/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js b/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js
index e54cd345a37..1cb2c6c669b 100644
--- a/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js
+++ b/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js
@@ -49,14 +49,18 @@ describe('getStateKey', () => {
expect(bound()).toEqual('unresolvedDiscussions');
+ data.work_in_progress = true;
+
+ expect(bound()).toEqual('workInProgress');
+
context.onlyAllowMergeIfPipelineSucceeds = true;
context.isPipelineFailed = true;
expect(bound()).toEqual('pipelineFailed');
- data.work_in_progress = true;
+ context.shouldBeRebased = true;
- expect(bound()).toEqual('workInProgress');
+ expect(bound()).toEqual('rebase');
data.has_conflicts = true;
diff --git a/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap
index df4b30f1cb8..19671d425a9 100644
--- a/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap
+++ b/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap
@@ -18,15 +18,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
<gl-emoji
- data-fallback-src="/assets/emoji/thumbsup-59ec2457ab33e8897261d01a495f6cf5c668d0004807dc541c3b1be5294b1e61.png"
data-name="thumbsup"
- data-unicode-version="6.0"
- title="thumbs up sign"
- >
-
- 👍
-
- </gl-emoji>
+ />
</span>
@@ -51,15 +44,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
<gl-emoji
- data-fallback-src="/assets/emoji/thumbsdown-5954334e2dae5357312b3d629f10a496c728029e02216f8c8b887f9b51561c61.png"
data-name="thumbsdown"
- data-unicode-version="6.0"
- title="thumbs down sign"
- >
-
- 👎
-
- </gl-emoji>
+ />
</span>
@@ -84,15 +70,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
<gl-emoji
- data-fallback-src="/assets/emoji/smile-14905c372d5bf7719bd727c9efae31a03291acec79801652a23710c6848c5d14.png"
data-name="smile"
- data-unicode-version="6.0"
- title="smiling face with open mouth and smiling eyes"
- >
-
- 😄
-
- </gl-emoji>
+ />
</span>
@@ -117,15 +96,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
<gl-emoji
- data-fallback-src="/assets/emoji/ok_hand-d63002dce3cc3655b67b8765b7c28d370edba0e3758b2329b60e0e61c4d8e78d.png"
data-name="ok_hand"
- data-unicode-version="6.0"
- title="ok hand sign"
- >
-
- 👌
-
- </gl-emoji>
+ />
</span>
@@ -150,15 +122,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
<gl-emoji
- data-fallback-src="/assets/emoji/cactus-2c5c4c35f26c7046fdc002b337e0d939729b33a26980e675950f9934c91e40fd.png"
data-name="cactus"
- data-unicode-version="6.0"
- title="cactus"
- >
-
- 🌵
-
- </gl-emoji>
+ />
</span>
@@ -183,15 +148,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
<gl-emoji
- data-fallback-src="/assets/emoji/a-bddbb39e8a1d35d42b7c08e7d47f63988cb4d8614b79f74e70b9c67c221896cc.png"
data-name="a"
- data-unicode-version="6.0"
- title="negative squared latin capital letter a"
- >
-
- 🅰
-
- </gl-emoji>
+ />
</span>
@@ -216,15 +174,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
<gl-emoji
- data-fallback-src="/assets/emoji/b-722f9db9442e7c0fc0d0ac0f5291fbf47c6a0ac4d8abd42e97957da705fb82bf.png"
data-name="b"
- data-unicode-version="6.0"
- title="negative squared latin capital letter b"
- >
-
- 🅱
-
- </gl-emoji>
+ />
</span>
diff --git a/spec/frontend/vue_shared/components/file_icon_spec.js b/spec/frontend/vue_shared/components/file_icon_spec.js
index 5a385eee60c..adf0da21f9f 100644
--- a/spec/frontend/vue_shared/components/file_icon_spec.js
+++ b/spec/frontend/vue_shared/components/file_icon_spec.js
@@ -1,12 +1,14 @@
import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
import FileIcon from '~/vue_shared/components/file_icon.vue';
+import { FILE_SYMLINK_MODE } from '~/vue_shared/constants';
describe('File Icon component', () => {
let wrapper;
- const findIcon = () => wrapper.find('svg');
+ const findSvgIcon = () => wrapper.find('svg');
+ const findGlIcon = () => wrapper.find(GlIcon);
const getIconName = () =>
- findIcon()
+ findSvgIcon()
.find('use')
.element.getAttribute('xlink:href')
.replace(`${gon.sprite_file_icons}#`, '');
@@ -27,7 +29,7 @@ describe('File Icon component', () => {
});
expect(wrapper.element.tagName).toEqual('SPAN');
- expect(findIcon().exists()).toBeDefined();
+ expect(findSvgIcon().exists()).toBeDefined();
});
it.each`
@@ -46,8 +48,8 @@ describe('File Icon component', () => {
folder: true,
});
- expect(findIcon().exists()).toBe(false);
- expect(wrapper.find(GlIcon).classes()).toContain('folder-icon');
+ expect(findSvgIcon().exists()).toBe(false);
+ expect(findGlIcon().classes()).toContain('folder-icon');
});
it('should render a loading icon', () => {
@@ -66,8 +68,19 @@ describe('File Icon component', () => {
cssClasses: 'extraclasses',
size,
});
+ const classes = findSvgIcon().classes();
- expect(findIcon().classes()).toContain(`s${size}`);
- expect(findIcon().classes()).toContain('extraclasses');
+ expect(classes).toContain(`s${size}`);
+ expect(classes).toContain('extraclasses');
+ });
+
+ it('should render a symlink icon', () => {
+ createComponent({
+ fileName: 'anything',
+ fileMode: FILE_SYMLINK_MODE,
+ });
+
+ expect(findSvgIcon().exists()).toBe(false);
+ expect(findGlIcon().attributes('name')).toBe('symlink');
});
});
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
index eded5b87abc..05508d14209 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
@@ -13,7 +13,7 @@ import { SortDirection } from '~/vue_shared/components/filtered_search_bar/const
import RecentSearchesStore from '~/filtered_search/stores/recent_searches_store';
import RecentSearchesService from '~/filtered_search/services/recent_searches_service';
-import { mockAvailableTokens, mockSortOptions } from './mock_data';
+import { mockAvailableTokens, mockSortOptions, mockHistoryItems } from './mock_data';
const createComponent = ({
namespace = 'gitlab-org/gitlab-test',
@@ -53,11 +53,17 @@ describe('FilteredSearchBarRoot', () => {
describe('computed', () => {
describe('tokenSymbols', () => {
- it('returns array of map containing type and symbols from `tokens` prop', () => {
+ it('returns a map containing type and symbols from `tokens` prop', () => {
expect(wrapper.vm.tokenSymbols).toEqual({ author_username: '@' });
});
});
+ describe('tokenTitles', () => {
+ it('returns a map containing type and title from `tokens` prop', () => {
+ expect(wrapper.vm.tokenTitles).toEqual({ author_username: 'Author' });
+ });
+ });
+
describe('sortDirectionIcon', () => {
it('returns string "sort-lowest" when `selectedSortDirection` is "ascending"', () => {
wrapper.setData({
@@ -133,14 +139,6 @@ describe('FilteredSearchBarRoot', () => {
});
});
- describe('getRecentSearches', () => {
- it('returns array of strings representing recent searches', () => {
- wrapper.vm.recentSearchesStore.setRecentSearches(['foo']);
-
- expect(wrapper.vm.getRecentSearches()).toEqual(['foo']);
- });
- });
-
describe('handleSortOptionClick', () => {
it('emits component event `onSort` with selected sort by value', () => {
wrapper.vm.handleSortOptionClick(mockSortOptions[1]);
@@ -172,6 +170,27 @@ describe('FilteredSearchBarRoot', () => {
});
});
+ describe('handleHistoryItemSelected', () => {
+ it('emits `onFilter` event with provided filters param', () => {
+ wrapper.vm.handleHistoryItemSelected(mockHistoryItems[0]);
+
+ expect(wrapper.emitted('onFilter')[0]).toEqual([mockHistoryItems[0]]);
+ });
+ });
+
+ describe('handleClearHistory', () => {
+ it('clears search history from recent searches store', () => {
+ jest.spyOn(wrapper.vm.recentSearchesStore, 'setRecentSearches').mockReturnValue([]);
+ jest.spyOn(wrapper.vm.recentSearchesService, 'save');
+
+ wrapper.vm.handleClearHistory();
+
+ expect(wrapper.vm.recentSearchesStore.setRecentSearches).toHaveBeenCalledWith([]);
+ expect(wrapper.vm.recentSearchesService.save).toHaveBeenCalledWith([]);
+ expect(wrapper.vm.recentSearches).toEqual([]);
+ });
+ });
+
describe('handleFilterSubmit', () => {
const mockFilters = [
{
@@ -186,14 +205,11 @@ describe('FilteredSearchBarRoot', () => {
it('calls `recentSearchesStore.addRecentSearch` with serialized value of provided `filters` param', () => {
jest.spyOn(wrapper.vm.recentSearchesStore, 'addRecentSearch');
- // jest.spyOn(wrapper.vm.recentSearchesService, 'save');
wrapper.vm.handleFilterSubmit(mockFilters);
return wrapper.vm.recentSearchesPromise.then(() => {
- expect(wrapper.vm.recentSearchesStore.addRecentSearch).toHaveBeenCalledWith(
- 'author_username:=@root foo',
- );
+ expect(wrapper.vm.recentSearchesStore.addRecentSearch).toHaveBeenCalledWith(mockFilters);
});
});
@@ -203,9 +219,17 @@ describe('FilteredSearchBarRoot', () => {
wrapper.vm.handleFilterSubmit(mockFilters);
return wrapper.vm.recentSearchesPromise.then(() => {
- expect(wrapper.vm.recentSearchesService.save).toHaveBeenCalledWith([
- 'author_username:=@root foo',
- ]);
+ expect(wrapper.vm.recentSearchesService.save).toHaveBeenCalledWith([mockFilters]);
+ });
+ });
+
+ it('sets `recentSearches` data prop with array of searches', () => {
+ jest.spyOn(wrapper.vm.recentSearchesService, 'save');
+
+ wrapper.vm.handleFilterSubmit(mockFilters);
+
+ return wrapper.vm.recentSearchesPromise.then(() => {
+ expect(wrapper.vm.recentSearches).toEqual([mockFilters]);
});
});
@@ -222,6 +246,7 @@ describe('FilteredSearchBarRoot', () => {
wrapper.setData({
selectedSortOption: mockSortOptions[0],
selectedSortDirection: SortDirection.descending,
+ recentSearches: mockHistoryItems,
});
return wrapper.vm.$nextTick();
@@ -232,6 +257,7 @@ describe('FilteredSearchBarRoot', () => {
expect(glFilteredSearchEl.props('placeholder')).toBe('Filter requirements');
expect(glFilteredSearchEl.props('availableTokens')).toEqual(mockAvailableTokens);
+ expect(glFilteredSearchEl.props('historyItems')).toEqual(mockHistoryItems);
});
it('renders sort dropdown component', () => {
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js
index edc0f119262..7e28c4e11e1 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js
@@ -44,6 +44,29 @@ export const mockAuthorToken = {
export const mockAvailableTokens = [mockAuthorToken];
+export const mockHistoryItems = [
+ [
+ {
+ type: 'author_username',
+ value: {
+ data: 'toby',
+ operator: '=',
+ },
+ },
+ 'duo',
+ ],
+ [
+ {
+ type: 'author_username',
+ value: {
+ data: 'root',
+ operator: '=',
+ },
+ },
+ 'si',
+ ],
+];
+
export const mockSortOptions = [
{
id: 1,
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js
index 3650ef79136..45294096eda 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js
@@ -11,11 +11,12 @@ import { mockAuthorToken, mockAuthors } from '../mock_data';
jest.mock('~/flash');
-const createComponent = ({ config = mockAuthorToken, value = { data: '' } } = {}) =>
+const createComponent = ({ config = mockAuthorToken, value = { data: '' }, active = false } = {}) =>
mount(AuthorToken, {
propsData: {
config,
value,
+ active,
},
provide: {
portalName: 'fake target',
@@ -51,29 +52,23 @@ describe('AuthorToken', () => {
describe('computed', () => {
describe('currentValue', () => {
it('returns lowercase string for `value.data`', () => {
- wrapper.setProps({
- value: { data: 'FOO' },
- });
+ wrapper = createComponent({ value: { data: 'FOO' } });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.currentValue).toBe('foo');
- });
+ expect(wrapper.vm.currentValue).toBe('foo');
});
});
describe('activeAuthor', () => {
- it('returns object for currently present `value.data`', () => {
+ it('returns object for currently present `value.data`', async () => {
+ wrapper = createComponent({ value: { data: mockAuthors[0].username } });
+
wrapper.setData({
authors: mockAuthors,
});
- wrapper.setProps({
- value: { data: mockAuthors[0].username },
- });
+ await wrapper.vm.$nextTick();
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.activeAuthor).toEqual(mockAuthors[0]);
- });
+ expect(wrapper.vm.activeAuthor).toEqual(mockAuthors[0]);
});
});
});
diff --git a/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js b/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js
index 8437e68d73c..93f4db5df18 100644
--- a/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js
+++ b/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js
@@ -38,6 +38,9 @@ describe('GlModalVuex', () => {
localVue,
store,
propsData,
+ stubs: {
+ GlModal,
+ },
});
};
@@ -148,4 +151,29 @@ describe('GlModalVuex', () => {
.then(done)
.catch(done.fail);
});
+
+ it.each(['ok', 'cancel'])(
+ 'passes an "%s" handler to the "modal-footer" slot scope',
+ handlerName => {
+ state.isVisible = true;
+
+ const modalFooterSlotContent = jest.fn();
+
+ factory({
+ scopedSlots: {
+ 'modal-footer': modalFooterSlotContent,
+ },
+ });
+
+ const handler = modalFooterSlotContent.mock.calls[0][0][handlerName];
+
+ expect(wrapper.emitted(handlerName)).toBeFalsy();
+ expect(actions.hide).not.toHaveBeenCalled();
+
+ handler();
+
+ expect(actions.hide).toHaveBeenCalledTimes(1);
+ expect(wrapper.emitted(handlerName)).toBeTruthy();
+ },
+ );
});
diff --git a/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js b/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js
index ca75c55df26..548d4476c0f 100644
--- a/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js
+++ b/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
-import { mockAssigneesList } from '../../../../javascripts/boards/mock_data';
+import { mockAssigneesList } from 'jest/boards/mock_data';
const TEST_CSS_CLASSES = 'test-classes';
const TEST_MAX_VISIBLE = 4;
diff --git a/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js b/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js
index 90c3fe54901..69d8c1a5918 100644
--- a/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js
+++ b/spec/frontend/vue_shared/components/issue/issue_milestone_spec.js
@@ -4,7 +4,7 @@ import { shallowMount } from '@vue/test-utils';
import IssueMilestone from '~/vue_shared/components/issue/issue_milestone.vue';
import Icon from '~/vue_shared/components/icon.vue';
-import { mockMilestone } from '../../../../javascripts/boards/mock_data';
+import { mockMilestone } from 'jest/boards/mock_data';
const createComponent = (milestone = mockMilestone) => {
const Component = Vue.extend(IssueMilestone);
diff --git a/spec/frontend/vue_shared/components/issue/issue_warning_spec.js b/spec/frontend/vue_shared/components/issue/issue_warning_spec.js
deleted file mode 100644
index 891c70bcb5c..00000000000
--- a/spec/frontend/vue_shared/components/issue/issue_warning_spec.js
+++ /dev/null
@@ -1,105 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import IssueWarning from '~/vue_shared/components/issue/issue_warning.vue';
-import Icon from '~/vue_shared/components/icon.vue';
-
-describe('Issue Warning Component', () => {
- let wrapper;
-
- const findIcon = () => wrapper.find(Icon);
- const findLockedBlock = () => wrapper.find({ ref: 'locked' });
- const findConfidentialBlock = () => wrapper.find({ ref: 'confidential' });
- const findLockedAndConfidentialBlock = () => wrapper.find({ ref: 'lockedAndConfidential' });
-
- const createComponent = props => {
- wrapper = shallowMount(IssueWarning, {
- propsData: {
- ...props,
- },
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- describe('when issue is locked but not confidential', () => {
- beforeEach(() => {
- createComponent({
- isLocked: true,
- lockedIssueDocsPath: 'locked-path',
- isConfidential: false,
- });
- });
-
- it('renders information about locked issue', () => {
- expect(findLockedBlock().exists()).toBe(true);
- expect(findLockedBlock().element).toMatchSnapshot();
- });
-
- it('renders warning icon', () => {
- expect(findIcon().exists()).toBe(true);
- });
-
- it('does not render information about locked and confidential issue', () => {
- expect(findLockedAndConfidentialBlock().exists()).toBe(false);
- });
-
- it('does not render information about confidential issue', () => {
- expect(findConfidentialBlock().exists()).toBe(false);
- });
- });
-
- describe('when issue is confidential but not locked', () => {
- beforeEach(() => {
- createComponent({
- isLocked: false,
- isConfidential: true,
- confidentialIssueDocsPath: 'confidential-path',
- });
- });
-
- it('renders information about confidential issue', () => {
- expect(findConfidentialBlock().exists()).toBe(true);
- expect(findConfidentialBlock().element).toMatchSnapshot();
- });
-
- it('renders warning icon', () => {
- expect(wrapper.find(Icon).exists()).toBe(true);
- });
-
- it('does not render information about locked issue', () => {
- expect(findLockedBlock().exists()).toBe(false);
- });
-
- it('does not render information about locked and confidential issue', () => {
- expect(findLockedAndConfidentialBlock().exists()).toBe(false);
- });
- });
-
- describe('when issue is locked and confidential', () => {
- beforeEach(() => {
- createComponent({
- isLocked: true,
- isConfidential: true,
- });
- });
-
- it('renders information about locked and confidential issue', () => {
- expect(findLockedAndConfidentialBlock().exists()).toBe(true);
- expect(findLockedAndConfidentialBlock().element).toMatchSnapshot();
- });
-
- it('does not render warning icon', () => {
- expect(wrapper.find(Icon).exists()).toBe(false);
- });
-
- it('does not render information about locked issue', () => {
- expect(findLockedBlock().exists()).toBe(false);
- });
-
- it('does not render information about confidential issue', () => {
- expect(findConfidentialBlock().exists()).toBe(false);
- });
- });
-});
diff --git a/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js b/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js
index 9be0a67e4fa..fe9a5156539 100644
--- a/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js
+++ b/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js
@@ -3,6 +3,7 @@ import { mount } from '@vue/test-utils';
import { formatDate } from '~/lib/utils/datetime_utility';
import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue';
import { defaultAssignees, defaultMilestone } from './related_issuable_mock_data';
+import { TEST_HOST } from 'jest/helpers/test_constants';
describe('RelatedIssuableItem', () => {
let wrapper;
@@ -19,7 +20,7 @@ describe('RelatedIssuableItem', () => {
idKey: 1,
displayReference: 'gitlab-org/gitlab-test#1',
pathIdSeparator: '#',
- path: `${gl.TEST_HOST}/path`,
+ path: `${TEST_HOST}/path`,
title: 'title',
confidential: true,
dueDate: '1990-12-31',
diff --git a/spec/frontend/vue_shared/components/issue/related_issuable_mock_data.js b/spec/frontend/vue_shared/components/issue/related_issuable_mock_data.js
index 5f69d761fdf..17813f2833d 100644
--- a/spec/frontend/vue_shared/components/issue/related_issuable_mock_data.js
+++ b/spec/frontend/vue_shared/components/issue/related_issuable_mock_data.js
@@ -1,3 +1,5 @@
+import { TEST_HOST } from 'jest/helpers/test_constants';
+
export const defaultProps = {
endpoint: '/foo/bar/issues/1/related_issues',
currentNamespacePath: 'foo',
@@ -83,8 +85,8 @@ export const defaultAssignees = [
name: 'Administrator',
username: 'root',
state: 'active',
- avatar_url: `${gl.TEST_HOST}`,
- web_url: `${gl.TEST_HOST}/root`,
+ avatar_url: `${TEST_HOST}`,
+ web_url: `${TEST_HOST}/root`,
status_tooltip_html: null,
path: '/root',
},
@@ -93,8 +95,8 @@ export const defaultAssignees = [
name: 'Brooks Beatty',
username: 'brynn_champlin',
state: 'active',
- avatar_url: `${gl.TEST_HOST}`,
- web_url: `${gl.TEST_HOST}/brynn_champlin`,
+ avatar_url: `${TEST_HOST}`,
+ web_url: `${TEST_HOST}/brynn_champlin`,
status_tooltip_html: null,
path: '/brynn_champlin',
},
@@ -103,8 +105,8 @@ export const defaultAssignees = [
name: 'Bryce Turcotte',
username: 'melynda',
state: 'active',
- avatar_url: `${gl.TEST_HOST}`,
- web_url: `${gl.TEST_HOST}/melynda`,
+ avatar_url: `${TEST_HOST}`,
+ web_url: `${TEST_HOST}/melynda`,
status_tooltip_html: null,
path: '/melynda',
},
@@ -113,8 +115,8 @@ export const defaultAssignees = [
name: 'Conchita Eichmann',
username: 'juliana_gulgowski',
state: 'active',
- avatar_url: `${gl.TEST_HOST}`,
- web_url: `${gl.TEST_HOST}/juliana_gulgowski`,
+ avatar_url: `${TEST_HOST}`,
+ web_url: `${TEST_HOST}/juliana_gulgowski`,
status_tooltip_html: null,
path: '/juliana_gulgowski',
},
diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
index 9a5b95b555f..c6e147899e4 100644
--- a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
@@ -69,11 +69,6 @@ describe('Suggestion Diff component', () => {
expect(addToBatchBtn.html().includes('Add suggestion to batch')).toBe(true);
});
- it('renders correct tooltip message for apply button', () => {
- createComponent();
- expect(wrapper.vm.tooltipMessage).toBe('This also resolves the discussion');
- });
-
describe('when apply suggestion is clicked', () => {
beforeEach(() => {
createComponent();
@@ -227,17 +222,23 @@ describe('Suggestion Diff component', () => {
createComponent({ canApply: false });
});
- it('disables apply suggestion and add to batch buttons', () => {
+ it('disables apply suggestion and hides add to batch button', () => {
expect(findApplyButton().exists()).toBe(true);
- expect(findAddToBatchButton().exists()).toBe(true);
+ expect(findAddToBatchButton().exists()).toBe(false);
expect(findApplyButton().attributes('disabled')).toBe('true');
- expect(findAddToBatchButton().attributes('disabled')).toBe('true');
+ });
+ });
+
+ describe('tooltip message for apply button', () => {
+ it('renders correct tooltip message when button is applicable', () => {
+ createComponent();
+ expect(wrapper.vm.tooltipMessage).toBe('This also resolves this thread');
});
- it('renders correct tooltip message for apply button', () => {
- expect(wrapper.vm.tooltipMessage).toBe(
- "Can't apply as this line has changed or the suggestion already matches its content.",
- );
+ it('renders the inapplicable reason in the tooltip when button is not applicable', () => {
+ const inapplicableReason = 'lorem';
+ createComponent({ canApply: false, inapplicableReason });
+ expect(wrapper.vm.tooltipMessage).toBe(inapplicableReason);
});
});
});
diff --git a/spec/frontend/vue_shared/components/issue/__snapshots__/issue_warning_spec.js.snap b/spec/frontend/vue_shared/components/notes/__snapshots__/noteable_warning_spec.js.snap
index 49b18d3e106..573bc9abe4d 100644
--- a/spec/frontend/vue_shared/components/issue/__snapshots__/issue_warning_spec.js.snap
+++ b/spec/frontend/vue_shared/components/notes/__snapshots__/noteable_warning_spec.js.snap
@@ -1,6 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`Issue Warning Component when issue is confidential but not locked renders information about confidential issue 1`] = `
+exports[`Issue Warning Component when issue is locked but not confidential renders information about locked issue 1`] = `
+<span>
+
+ This issue is locked.
+ Only project members can comment.
+
+ <gl-link-stub
+ href="locked-path"
+ target="_blank"
+ >
+ Learn more
+ </gl-link-stub>
+</span>
+`;
+
+exports[`Issue Warning Component when noteable is confidential but not locked renders information about confidential issue 1`] = `
<span>
This is a confidential issue.
@@ -10,14 +25,12 @@ exports[`Issue Warning Component when issue is confidential but not locked rende
href="confidential-path"
target="_blank"
>
-
- Learn more
-
+ Learn more
</gl-link-stub>
</span>
`;
-exports[`Issue Warning Component when issue is locked and confidential renders information about locked and confidential issue 1`] = `
+exports[`Issue Warning Component when noteable is locked and confidential renders information about locked and confidential noteable 1`] = `
<span>
<span>
This issue is
@@ -43,20 +56,3 @@ exports[`Issue Warning Component when issue is locked and confidential renders i
</span>
`;
-
-exports[`Issue Warning Component when issue is locked but not confidential renders information about locked issue 1`] = `
-<span>
-
- This issue is locked.
- Only project members can comment.
-
- <gl-link-stub
- href="locked-path"
- target="_blank"
- >
-
- Learn more
-
- </gl-link-stub>
-</span>
-`;
diff --git a/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js b/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js
new file mode 100644
index 00000000000..ae8c9a0928e
--- /dev/null
+++ b/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js
@@ -0,0 +1,196 @@
+import { shallowMount } from '@vue/test-utils';
+import NoteableWarning from '~/vue_shared/components/notes/noteable_warning.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+
+describe('Issue Warning Component', () => {
+ let wrapper;
+
+ const findIcon = (w = wrapper) => w.find(Icon);
+ const findLockedBlock = (w = wrapper) => w.find({ ref: 'locked' });
+ const findConfidentialBlock = (w = wrapper) => w.find({ ref: 'confidential' });
+ const findLockedAndConfidentialBlock = (w = wrapper) => w.find({ ref: 'lockedAndConfidential' });
+
+ const createComponent = props =>
+ shallowMount(NoteableWarning, {
+ propsData: {
+ ...props,
+ },
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ wrapper = null;
+ }
+ });
+
+ describe('when issue is locked but not confidential', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ isLocked: true,
+ lockedNoteableDocsPath: 'locked-path',
+ isConfidential: false,
+ });
+ });
+
+ it('renders information about locked issue', () => {
+ expect(findLockedBlock().exists()).toBe(true);
+ expect(findLockedBlock().element).toMatchSnapshot();
+ });
+
+ it('renders warning icon', () => {
+ expect(findIcon().exists()).toBe(true);
+ });
+
+ it('does not render information about locked and confidential issue', () => {
+ expect(findLockedAndConfidentialBlock().exists()).toBe(false);
+ });
+
+ it('does not render information about confidential issue', () => {
+ expect(findConfidentialBlock().exists()).toBe(false);
+ });
+ });
+
+ describe('when noteable is confidential but not locked', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ isLocked: false,
+ isConfidential: true,
+ confidentialNoteableDocsPath: 'confidential-path',
+ });
+ });
+
+ it('renders information about confidential issue', async () => {
+ expect(findConfidentialBlock().exists()).toBe(true);
+ expect(findConfidentialBlock().element).toMatchSnapshot();
+
+ await wrapper.vm.$nextTick();
+ expect(findConfidentialBlock(wrapper).text()).toContain('This is a confidential issue.');
+ });
+
+ it('renders warning icon', () => {
+ expect(wrapper.find(Icon).exists()).toBe(true);
+ });
+
+ it('does not render information about locked noteable', () => {
+ expect(findLockedBlock().exists()).toBe(false);
+ });
+
+ it('does not render information about locked and confidential noteable', () => {
+ expect(findLockedAndConfidentialBlock().exists()).toBe(false);
+ });
+ });
+
+ describe('when noteable is locked and confidential', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ isLocked: true,
+ isConfidential: true,
+ });
+ });
+
+ it('renders information about locked and confidential noteable', () => {
+ expect(findLockedAndConfidentialBlock().exists()).toBe(true);
+ expect(findLockedAndConfidentialBlock().element).toMatchSnapshot();
+ });
+
+ it('does not render warning icon', () => {
+ expect(wrapper.find(Icon).exists()).toBe(false);
+ });
+
+ it('does not render information about locked noteable', () => {
+ expect(findLockedBlock().exists()).toBe(false);
+ });
+
+ it('does not render information about confidential noteable', () => {
+ expect(findConfidentialBlock().exists()).toBe(false);
+ });
+ });
+
+ describe('when noteableType prop is defined', () => {
+ let wrapperLocked;
+ let wrapperConfidential;
+ let wrapperLockedAndConfidential;
+
+ beforeEach(() => {
+ wrapperLocked = createComponent({
+ isLocked: true,
+ isConfidential: false,
+ });
+ wrapperConfidential = createComponent({
+ isLocked: false,
+ isConfidential: true,
+ });
+ wrapperLockedAndConfidential = createComponent({
+ isLocked: true,
+ isConfidential: true,
+ });
+ });
+
+ afterEach(() => {
+ wrapperLocked.destroy();
+ wrapperConfidential.destroy();
+ wrapperLockedAndConfidential.destroy();
+ });
+
+ it('renders confidential & locked messages with noteable "issue"', () => {
+ expect(findLockedBlock(wrapperLocked).text()).toContain('This issue is locked.');
+ expect(findConfidentialBlock(wrapperConfidential).text()).toContain(
+ 'This is a confidential issue.',
+ );
+ expect(findLockedAndConfidentialBlock(wrapperLockedAndConfidential).text()).toContain(
+ 'This issue is confidential and locked.',
+ );
+ });
+
+ it('renders confidential & locked messages with noteable "epic"', async () => {
+ wrapperLocked.setProps({
+ noteableType: 'Epic',
+ });
+ wrapperConfidential.setProps({
+ noteableType: 'Epic',
+ });
+ wrapperLockedAndConfidential.setProps({
+ noteableType: 'Epic',
+ });
+
+ await wrapperLocked.vm.$nextTick();
+ expect(findLockedBlock(wrapperLocked).text()).toContain('This epic is locked.');
+
+ await wrapperConfidential.vm.$nextTick();
+ expect(findConfidentialBlock(wrapperConfidential).text()).toContain(
+ 'This is a confidential epic.',
+ );
+
+ await wrapperLockedAndConfidential.vm.$nextTick();
+ expect(findLockedAndConfidentialBlock(wrapperLockedAndConfidential).text()).toContain(
+ 'This epic is confidential and locked.',
+ );
+ });
+
+ it('renders confidential & locked messages with noteable "merge request"', async () => {
+ wrapperLocked.setProps({
+ noteableType: 'MergeRequest',
+ });
+ wrapperConfidential.setProps({
+ noteableType: 'MergeRequest',
+ });
+ wrapperLockedAndConfidential.setProps({
+ noteableType: 'MergeRequest',
+ });
+
+ await wrapperLocked.vm.$nextTick();
+ expect(findLockedBlock(wrapperLocked).text()).toContain('This merge request is locked.');
+
+ await wrapperConfidential.vm.$nextTick();
+ expect(findConfidentialBlock(wrapperConfidential).text()).toContain(
+ 'This is a confidential merge request.',
+ );
+
+ await wrapperLockedAndConfidential.vm.$nextTick();
+ expect(findLockedAndConfidentialBlock(wrapperLockedAndConfidential).text()).toContain(
+ 'This merge request is confidential and locked.',
+ );
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js b/spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js
index eb1d9e93634..385134c4a3f 100644
--- a/spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js
+++ b/spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js
@@ -74,6 +74,16 @@ describe('ProjectListItem component', () => {
expect(renderedNamespace).toBe('a / ... / e /');
});
+ it(`renders a simple namespace name of a GraphQL project`, () => {
+ options.propsData.project.name_with_namespace = undefined;
+ options.propsData.project.nameWithNamespace = 'test';
+
+ wrapper = shallowMount(Component, options);
+ const renderedNamespace = trimText(wrapper.find('.js-project-namespace').text());
+
+ expect(renderedNamespace).toBe('test /');
+ });
+
it(`renders the project name`, () => {
options.propsData.project.name = 'my-test-project';
diff --git a/spec/frontend/vue_shared/components/remove_member_modal_spec.js b/spec/frontend/vue_shared/components/remove_member_modal_spec.js
new file mode 100644
index 00000000000..2d380b25a0a
--- /dev/null
+++ b/spec/frontend/vue_shared/components/remove_member_modal_spec.js
@@ -0,0 +1,65 @@
+import { GlFormCheckbox, GlModal } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue';
+
+describe('RemoveMemberModal', () => {
+ const memberPath = '/gitlab-org/gitlab-test/-/project_members/90';
+ let wrapper;
+
+ const findForm = () => wrapper.find({ ref: 'form' });
+ const findGlModal = () => wrapper.find(GlModal);
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe.each`
+ state | isAccessRequest | actionText | checkboxTestDescription | checkboxExpected | message
+ ${'removing a member'} | ${'false'} | ${'Remove member'} | ${'shows a checkbox to allow removal from related issues and MRs'} | ${true} | ${'Are you sure you want to remove Jane Doe from the Gitlab Org / Gitlab Test project?'}
+ ${'denying an access request'} | ${'true'} | ${'Deny access request'} | ${'does not show a checkbox'} | ${false} | ${"Are you sure you want to deny Jane Doe's request to join the Gitlab Org / Gitlab Test project?"}
+ `(
+ 'when $state',
+ ({ actionText, isAccessRequest, message, checkboxTestDescription, checkboxExpected }) => {
+ beforeEach(() => {
+ wrapper = shallowMount(RemoveMemberModal, {
+ data() {
+ return {
+ modalData: {
+ isAccessRequest,
+ message,
+ memberPath,
+ },
+ };
+ },
+ });
+ });
+
+ it(`has the title ${actionText}`, () => {
+ expect(findGlModal().attributes('title')).toBe(actionText);
+ });
+
+ it('contains a form action', () => {
+ expect(findForm().attributes('action')).toBe(memberPath);
+ });
+
+ it('displays a message to the user', () => {
+ expect(wrapper.find('[data-testid=modal-message]').text()).toBe(message);
+ });
+
+ it(`${checkboxTestDescription}`, () => {
+ expect(wrapper.contains(GlFormCheckbox)).toBe(checkboxExpected);
+ });
+
+ it('submits the form when the modal is submitted', () => {
+ const spy = jest.spyOn(findForm().element, 'submit');
+
+ findGlModal().vm.$emit('primary');
+
+ expect(spy).toHaveBeenCalled();
+
+ spy.mockRestore();
+ });
+ },
+ );
+});
diff --git a/spec/frontend/vue_shared/components/__snapshots__/resizable_chart_container_spec.js.snap b/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/resizable_chart_container_spec.js.snap
index add0c36a120..add0c36a120 100644
--- a/spec/frontend/vue_shared/components/__snapshots__/resizable_chart_container_spec.js.snap
+++ b/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/resizable_chart_container_spec.js.snap
diff --git a/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/skeleton_loader_spec.js.snap b/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/skeleton_loader_spec.js.snap
new file mode 100644
index 00000000000..103b53cb280
--- /dev/null
+++ b/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/skeleton_loader_spec.js.snap
@@ -0,0 +1,324 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Resizable Skeleton Loader default setup renders the bars, labels, and grid with correct position, size, and rx percentages 1`] = `
+<gl-skeleton-loader-stub
+ baseurl=""
+ height="130"
+ preserveaspectratio="xMidYMid meet"
+ width="400"
+>
+ <rect
+ data-testid="skeleton-chart-grid"
+ height="1px"
+ width="100%"
+ x="0"
+ y="30%"
+ />
+ <rect
+ data-testid="skeleton-chart-grid"
+ height="1px"
+ width="100%"
+ x="0"
+ y="60%"
+ />
+ <rect
+ data-testid="skeleton-chart-grid"
+ height="1px"
+ width="100%"
+ x="0"
+ y="90%"
+ />
+
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="5%"
+ rx="0.4%"
+ width="6%"
+ x="5.875%"
+ y="85%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="7%"
+ rx="0.4%"
+ width="6%"
+ x="17.625%"
+ y="83%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="9%"
+ rx="0.4%"
+ width="6%"
+ x="29.375%"
+ y="81%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="14%"
+ rx="0.4%"
+ width="6%"
+ x="41.125%"
+ y="76%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="21%"
+ rx="0.4%"
+ width="6%"
+ x="52.875%"
+ y="69%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="35%"
+ rx="0.4%"
+ width="6%"
+ x="64.625%"
+ y="55%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="50%"
+ rx="0.4%"
+ width="6%"
+ x="76.375%"
+ y="40%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="80%"
+ rx="0.4%"
+ width="6%"
+ x="88.125%"
+ y="10%"
+ />
+
+ <rect
+ data-testid="skeleton-chart-label"
+ height="5%"
+ rx="0.4%"
+ width="4%"
+ x="6.875%"
+ y="95%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="5%"
+ rx="0.4%"
+ width="4%"
+ x="18.625%"
+ y="95%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="5%"
+ rx="0.4%"
+ width="4%"
+ x="30.375%"
+ y="95%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="5%"
+ rx="0.4%"
+ width="4%"
+ x="42.125%"
+ y="95%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="5%"
+ rx="0.4%"
+ width="4%"
+ x="53.875%"
+ y="95%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="5%"
+ rx="0.4%"
+ width="4%"
+ x="65.625%"
+ y="95%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="5%"
+ rx="0.4%"
+ width="4%"
+ x="77.375%"
+ y="95%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="5%"
+ rx="0.4%"
+ width="4%"
+ x="89.125%"
+ y="95%"
+ />
+</gl-skeleton-loader-stub>
+`;
+
+exports[`Resizable Skeleton Loader with custom settings renders the correct position, and size percentages for bars and labels with different settings 1`] = `
+<gl-skeleton-loader-stub
+ baseurl=""
+ height="130"
+ preserveaspectratio="xMidYMid meet"
+ uniquekey=""
+ width="400"
+>
+ <rect
+ data-testid="skeleton-chart-grid"
+ height="1px"
+ width="100%"
+ x="0"
+ y="30%"
+ />
+ <rect
+ data-testid="skeleton-chart-grid"
+ height="1px"
+ width="100%"
+ x="0"
+ y="60%"
+ />
+ <rect
+ data-testid="skeleton-chart-grid"
+ height="1px"
+ width="100%"
+ x="0"
+ y="90%"
+ />
+
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="5%"
+ rx="0.6%"
+ width="3%"
+ x="6.0625%"
+ y="85%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="7%"
+ rx="0.6%"
+ width="3%"
+ x="18.1875%"
+ y="83%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="9%"
+ rx="0.6%"
+ width="3%"
+ x="30.3125%"
+ y="81%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="14%"
+ rx="0.6%"
+ width="3%"
+ x="42.4375%"
+ y="76%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="21%"
+ rx="0.6%"
+ width="3%"
+ x="54.5625%"
+ y="69%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="35%"
+ rx="0.6%"
+ width="3%"
+ x="66.6875%"
+ y="55%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="50%"
+ rx="0.6%"
+ width="3%"
+ x="78.8125%"
+ y="40%"
+ />
+ <rect
+ data-testid="skeleton-chart-bar"
+ height="80%"
+ rx="0.6%"
+ width="3%"
+ x="90.9375%"
+ y="10%"
+ />
+
+ <rect
+ data-testid="skeleton-chart-label"
+ height="2%"
+ rx="0.6%"
+ width="7%"
+ x="4.0625%"
+ y="98%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="2%"
+ rx="0.6%"
+ width="7%"
+ x="16.1875%"
+ y="98%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="2%"
+ rx="0.6%"
+ width="7%"
+ x="28.3125%"
+ y="98%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="2%"
+ rx="0.6%"
+ width="7%"
+ x="40.4375%"
+ y="98%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="2%"
+ rx="0.6%"
+ width="7%"
+ x="52.5625%"
+ y="98%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="2%"
+ rx="0.6%"
+ width="7%"
+ x="64.6875%"
+ y="98%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="2%"
+ rx="0.6%"
+ width="7%"
+ x="76.8125%"
+ y="98%"
+ />
+ <rect
+ data-testid="skeleton-chart-label"
+ height="2%"
+ rx="0.6%"
+ width="7%"
+ x="88.9375%"
+ y="98%"
+ />
+</gl-skeleton-loader-stub>
+`;
diff --git a/spec/frontend/vue_shared/components/resizable_chart_container_spec.js b/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js
index 3a5514ef318..3a5514ef318 100644
--- a/spec/frontend/vue_shared/components/resizable_chart_container_spec.js
+++ b/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js
diff --git a/spec/frontend/vue_shared/components/resizable_chart/skeleton_loader_spec.js b/spec/frontend/vue_shared/components/resizable_chart/skeleton_loader_spec.js
new file mode 100644
index 00000000000..7facd02e596
--- /dev/null
+++ b/spec/frontend/vue_shared/components/resizable_chart/skeleton_loader_spec.js
@@ -0,0 +1,55 @@
+import { shallowMount } from '@vue/test-utils';
+import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
+
+describe('Resizable Skeleton Loader', () => {
+ let wrapper;
+
+ const createComponent = (propsData = {}) => {
+ wrapper = shallowMount(ChartSkeletonLoader, {
+ propsData,
+ });
+ };
+
+ const verifyElementsPresence = () => {
+ const gridItems = wrapper.findAll('[data-testid="skeleton-chart-grid"]').wrappers;
+ const barItems = wrapper.findAll('[data-testid="skeleton-chart-bar"]').wrappers;
+ const labelItems = wrapper.findAll('[data-testid="skeleton-chart-label"]').wrappers;
+ expect(gridItems.length).toBe(3);
+ expect(barItems.length).toBe(8);
+ expect(labelItems.length).toBe(8);
+ };
+
+ afterEach(() => {
+ if (wrapper?.destroy) {
+ wrapper.destroy();
+ }
+ });
+
+ describe('default setup', () => {
+ beforeEach(() => {
+ createComponent({ uniqueKey: null });
+ });
+
+ it('renders the bars, labels, and grid with correct position, size, and rx percentages', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders the correct number of grid items, bars, and labels', () => {
+ verifyElementsPresence();
+ });
+ });
+
+ describe('with custom settings', () => {
+ beforeEach(() => {
+ createComponent({ uniqueKey: '', rx: 0.6, barWidth: 3, labelWidth: 7, labelHeight: 2 });
+ });
+
+ it('renders the correct position, and size percentages for bars and labels with different settings', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('renders the correct number of grid items, bars, and labels', () => {
+ verifyElementsPresence();
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js
index faa32131fab..78f27c9948b 100644
--- a/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js
+++ b/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js
@@ -2,18 +2,35 @@ import {
generateToolbarItem,
addCustomEventListener,
removeCustomEventListener,
+ registerHTMLToMarkdownRenderer,
addImage,
getMarkdown,
-} from '~/vue_shared/components/rich_content_editor/editor_service';
+} from '~/vue_shared/components/rich_content_editor/services/editor_service';
+import buildHTMLToMarkdownRenderer from '~/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer';
+
+jest.mock('~/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer');
describe('Editor Service', () => {
- const mockInstance = {
- eventManager: { addEventType: jest.fn(), removeEventHandler: jest.fn(), listen: jest.fn() },
- editor: { exec: jest.fn() },
- invoke: jest.fn(),
- };
- const event = 'someCustomEvent';
- const handler = jest.fn();
+ let mockInstance;
+ let event;
+ let handler;
+
+ beforeEach(() => {
+ mockInstance = {
+ eventManager: { addEventType: jest.fn(), removeEventHandler: jest.fn(), listen: jest.fn() },
+ editor: { exec: jest.fn() },
+ invoke: jest.fn(),
+ toMarkOptions: {
+ renderer: {
+ constructor: {
+ factory: jest.fn(),
+ },
+ },
+ },
+ };
+ event = 'someCustomEvent';
+ handler = jest.fn();
+ });
describe('generateToolbarItem', () => {
const config = {
@@ -74,4 +91,33 @@ describe('Editor Service', () => {
expect(mockInstance.invoke).toHaveBeenCalledWith('getMarkdown');
});
});
+
+ describe('registerHTMLToMarkdownRenderer', () => {
+ let baseRenderer;
+ const htmlToMarkdownRenderer = {};
+ const extendedRenderer = {};
+
+ beforeEach(() => {
+ baseRenderer = mockInstance.toMarkOptions.renderer;
+ buildHTMLToMarkdownRenderer.mockReturnValueOnce(htmlToMarkdownRenderer);
+ baseRenderer.constructor.factory.mockReturnValueOnce(extendedRenderer);
+
+ registerHTMLToMarkdownRenderer(mockInstance);
+ });
+
+ it('builds a new instance of the HTML to Markdown renderer', () => {
+ expect(buildHTMLToMarkdownRenderer).toHaveBeenCalledWith(baseRenderer);
+ });
+
+ it('extends base renderer with the HTML to Markdown renderer', () => {
+ expect(baseRenderer.constructor.factory).toHaveBeenCalledWith(
+ baseRenderer,
+ htmlToMarkdownRenderer,
+ );
+ });
+
+ it('replaces the default renderer with extended renderer', () => {
+ expect(mockInstance.toMarkOptions.renderer).toBe(extendedRenderer);
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal_spec.js
new file mode 100644
index 00000000000..0c2ac53aa52
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal_spec.js
@@ -0,0 +1,76 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlModal, GlTabs } from '@gitlab/ui';
+import AddImageModal from '~/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue';
+import UploadImageTab from '~/vue_shared/components/rich_content_editor/modals/add_image/upload_image_tab.vue';
+import { IMAGE_TABS } from '~/vue_shared/components/rich_content_editor/constants';
+
+describe('Add Image Modal', () => {
+ let wrapper;
+ const propsData = { imageRoot: 'path/to/root/' };
+
+ const findModal = () => wrapper.find(GlModal);
+ const findTabs = () => wrapper.find(GlTabs);
+ const findUploadImageTab = () => wrapper.find(UploadImageTab);
+ const findUrlInput = () => wrapper.find({ ref: 'urlInput' });
+ const findDescriptionInput = () => wrapper.find({ ref: 'descriptionInput' });
+
+ beforeEach(() => {
+ wrapper = shallowMount(AddImageModal, {
+ provide: { glFeatures: { sseImageUploads: true } },
+ propsData,
+ });
+ });
+
+ describe('when content is loaded', () => {
+ it('renders a modal component', () => {
+ expect(findModal().exists()).toBe(true);
+ });
+
+ it('renders a Tabs component', () => {
+ expect(findTabs().exists()).toBe(true);
+ });
+
+ it('renders an upload image tab', () => {
+ expect(findUploadImageTab().exists()).toBe(true);
+ });
+
+ it('renders an input to add an image URL', () => {
+ expect(findUrlInput().exists()).toBe(true);
+ });
+
+ it('renders an input to add an image description', () => {
+ expect(findDescriptionInput().exists()).toBe(true);
+ });
+ });
+
+ describe('add image', () => {
+ describe('Upload', () => {
+ it('validates the file', () => {
+ const preventDefault = jest.fn();
+ const description = 'some description';
+ const file = { name: 'some_file.png' };
+
+ wrapper.vm.$refs.uploadImageTab = { validateFile: jest.fn() };
+ wrapper.setData({ file, description, tabIndex: IMAGE_TABS.UPLOAD_TAB });
+
+ findModal().vm.$emit('ok', { preventDefault });
+
+ expect(wrapper.vm.$refs.uploadImageTab.validateFile).toHaveBeenCalled();
+ });
+ });
+
+ describe('URL', () => {
+ it('emits an addImage event when a valid URL is specified', () => {
+ const preventDefault = jest.fn();
+ const mockImage = { imageUrl: '/some/valid/url.png', description: 'some description' };
+ wrapper.setData({ ...mockImage, tabIndex: IMAGE_TABS.URL_TAB });
+
+ findModal().vm.$emit('ok', { preventDefault });
+ expect(preventDefault).not.toHaveBeenCalled();
+ expect(wrapper.emitted('addImage')).toEqual([
+ [{ imageUrl: mockImage.imageUrl, altText: mockImage.description }],
+ ]);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/upload_image_tab_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/upload_image_tab_spec.js
new file mode 100644
index 00000000000..ded490b2568
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/upload_image_tab_spec.js
@@ -0,0 +1,41 @@
+import { shallowMount } from '@vue/test-utils';
+import UploadImageTab from '~/vue_shared/components/rich_content_editor/modals/add_image/upload_image_tab.vue';
+
+describe('Upload Image Tab', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = shallowMount(UploadImageTab);
+ });
+
+ afterEach(() => wrapper.destroy());
+
+ const triggerInputEvent = size => {
+ const file = { size, name: 'file-name.png' };
+ const mockEvent = new Event('input');
+
+ Object.defineProperty(mockEvent, 'target', { value: { files: [file] } });
+
+ wrapper.find({ ref: 'fileInput' }).element.dispatchEvent(mockEvent);
+
+ return file;
+ };
+
+ describe('onInput', () => {
+ it.each`
+ size | fileError
+ ${2000000000} | ${'Maximum file size is 2MB. Please select a smaller file.'}
+ ${200} | ${null}
+ `('validates the file correctly', ({ size, fileError }) => {
+ triggerInputEvent(size);
+
+ expect(wrapper.vm.fileError).toBe(fileError);
+ });
+ });
+
+ it('emits input event when file is valid', () => {
+ const file = triggerInputEvent(200);
+
+ expect(wrapper.emitted('input')).toEqual([[file]]);
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image_modal_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image_modal_spec.js
deleted file mode 100644
index 4889bc8538d..00000000000
--- a/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image_modal_spec.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlModal } from '@gitlab/ui';
-import AddImageModal from '~/vue_shared/components/rich_content_editor/modals/add_image_modal.vue';
-
-describe('Add Image Modal', () => {
- let wrapper;
-
- const findModal = () => wrapper.find(GlModal);
- const findUrlInput = () => wrapper.find({ ref: 'urlInput' });
- const findDescriptionInput = () => wrapper.find({ ref: 'descriptionInput' });
-
- beforeEach(() => {
- wrapper = shallowMount(AddImageModal);
- });
-
- describe('when content is loaded', () => {
- it('renders a modal component', () => {
- expect(findModal().exists()).toBe(true);
- });
-
- it('renders an input to add an image URL', () => {
- expect(findUrlInput().exists()).toBe(true);
- });
-
- it('renders an input to add an image description', () => {
- expect(findDescriptionInput().exists()).toBe(true);
- });
- });
-
- describe('add image', () => {
- it('emits an addImage event when a valid URL is specified', () => {
- const preventDefault = jest.fn();
- const mockImage = { imageUrl: '/some/valid/url.png', altText: 'some description' };
- wrapper.setData({ ...mockImage });
-
- findModal().vm.$emit('ok', { preventDefault });
- expect(preventDefault).not.toHaveBeenCalled();
- expect(wrapper.emitted('addImage')).toEqual([[mockImage]]);
- });
- });
-});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js
index 0db10389df4..b6ff6aa767c 100644
--- a/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js
+++ b/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import RichContentEditor from '~/vue_shared/components/rich_content_editor/rich_content_editor.vue';
-import AddImageModal from '~/vue_shared/components/rich_content_editor/modals/add_image_modal.vue';
+import AddImageModal from '~/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue';
import {
EDITOR_OPTIONS,
EDITOR_TYPES,
@@ -13,25 +13,28 @@ import {
addCustomEventListener,
removeCustomEventListener,
addImage,
-} from '~/vue_shared/components/rich_content_editor/editor_service';
+ registerHTMLToMarkdownRenderer,
+} from '~/vue_shared/components/rich_content_editor/services/editor_service';
-jest.mock('~/vue_shared/components/rich_content_editor/editor_service', () => ({
- ...jest.requireActual('~/vue_shared/components/rich_content_editor/editor_service'),
+jest.mock('~/vue_shared/components/rich_content_editor/services/editor_service', () => ({
+ ...jest.requireActual('~/vue_shared/components/rich_content_editor/services/editor_service'),
addCustomEventListener: jest.fn(),
removeCustomEventListener: jest.fn(),
addImage: jest.fn(),
+ registerHTMLToMarkdownRenderer: jest.fn(),
}));
describe('Rich Content Editor', () => {
let wrapper;
- const value = '## Some Markdown';
+ const content = '## Some Markdown';
+ const imageRoot = 'path/to/root/';
const findEditor = () => wrapper.find({ ref: 'editor' });
const findAddImageModal = () => wrapper.find(AddImageModal);
beforeEach(() => {
wrapper = shallowMount(RichContentEditor, {
- propsData: { value },
+ propsData: { content, imageRoot },
});
});
@@ -41,7 +44,7 @@ describe('Rich Content Editor', () => {
});
it('renders the correct content', () => {
- expect(findEditor().props().initialValue).toBe(value);
+ expect(findEditor().props().initialValue).toBe(content);
});
it('provides the correct editor options', () => {
@@ -73,17 +76,37 @@ describe('Rich Content Editor', () => {
});
});
+ describe('when content is reset', () => {
+ it('should reset the content via setMarkdown', () => {
+ const newContent = 'Just the body content excluding the front matter for example';
+ const mockInstance = { invoke: jest.fn() };
+ wrapper.vm.$refs.editor = mockInstance;
+
+ wrapper.vm.resetInitialValue(newContent);
+
+ expect(mockInstance.invoke).toHaveBeenCalledWith('setMarkdown', newContent);
+ });
+ });
+
describe('when editor is loaded', () => {
- it('adds the CUSTOM_EVENTS.openAddImageModal custom event listener', () => {
- const mockEditorApi = { eventManager: { addEventType: jest.fn(), listen: jest.fn() } };
+ let mockEditorApi;
+
+ beforeEach(() => {
+ mockEditorApi = { eventManager: { addEventType: jest.fn(), listen: jest.fn() } };
findEditor().vm.$emit('load', mockEditorApi);
+ });
+ it('adds the CUSTOM_EVENTS.openAddImageModal custom event listener', () => {
expect(addCustomEventListener).toHaveBeenCalledWith(
mockEditorApi,
CUSTOM_EVENTS.openAddImageModal,
wrapper.vm.onOpenAddImageModal,
);
});
+
+ it('registers HTML to markdown renderer', () => {
+ expect(registerHTMLToMarkdownRenderer).toHaveBeenCalledWith(mockEditorApi);
+ });
});
describe('when editor is destroyed', () => {
@@ -107,7 +130,7 @@ describe('Rich Content Editor', () => {
});
it('calls the onAddImage method when the addImage event is emitted', () => {
- const mockImage = { imageUrl: 'some/url.png', description: 'some description' };
+ const mockImage = { imageUrl: 'some/url.png', altText: 'some description' };
const mockInstance = { exec: jest.fn() };
wrapper.vm.$refs.editor = mockInstance;
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/build_custom_renderer_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/build_custom_renderer_spec.js
new file mode 100644
index 00000000000..cafe53e6bb2
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/build_custom_renderer_spec.js
@@ -0,0 +1,29 @@
+import buildCustomHTMLRenderer from '~/vue_shared/components/rich_content_editor/services/build_custom_renderer';
+
+describe('Build Custom Renderer Service', () => {
+ describe('buildCustomHTMLRenderer', () => {
+ it('should return an object with the default renderer functions when lacking arguments', () => {
+ expect(buildCustomHTMLRenderer()).toEqual(
+ expect.objectContaining({
+ list: expect.any(Function),
+ text: expect.any(Function),
+ }),
+ );
+ });
+
+ it('should return an object with both custom and default renderer functions when passed customRenderers', () => {
+ const mockHtmlCustomRenderer = jest.fn();
+ const customRenderers = {
+ html: [mockHtmlCustomRenderer],
+ };
+
+ expect(buildCustomHTMLRenderer(customRenderers)).toEqual(
+ expect.objectContaining({
+ html: expect.any(Function),
+ list: expect.any(Function),
+ text: expect.any(Function),
+ }),
+ );
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer_spec.js
new file mode 100644
index 00000000000..0e8610a22f5
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer_spec.js
@@ -0,0 +1,50 @@
+import buildHTMLToMarkdownRenderer from '~/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer';
+
+describe('HTMLToMarkdownRenderer', () => {
+ let baseRenderer;
+ let htmlToMarkdownRenderer;
+ const NODE = { nodeValue: 'mock_node' };
+
+ beforeEach(() => {
+ baseRenderer = {
+ trim: jest.fn(input => `trimmed ${input}`),
+ getSpaceCollapsedText: jest.fn(input => `space collapsed ${input}`),
+ getSpaceControlled: jest.fn(input => `space controlled ${input}`),
+ convert: jest.fn(),
+ };
+ });
+
+ describe('TEXT_NODE visitor', () => {
+ it('composes getSpaceControlled, getSpaceCollapsedText, and trim services', () => {
+ htmlToMarkdownRenderer = buildHTMLToMarkdownRenderer(baseRenderer);
+
+ expect(htmlToMarkdownRenderer.TEXT_NODE(NODE)).toBe(
+ `space controlled trimmed space collapsed ${NODE.nodeValue}`,
+ );
+ });
+ });
+
+ describe('LI OL, LI UL visitor', () => {
+ const oneLevelNestedList = '\n * List item 1\n * List item 2';
+ const twoLevelNestedList = '\n * List item 1\n * List item 2';
+ const spaceInContentList = '\n * List item 1\n * List item 2';
+
+ it.each`
+ list | indentSpaces | result
+ ${oneLevelNestedList} | ${2} | ${'\n * List item 1\n * List item 2'}
+ ${oneLevelNestedList} | ${3} | ${'\n * List item 1\n * List item 2'}
+ ${oneLevelNestedList} | ${6} | ${'\n * List item 1\n * List item 2'}
+ ${twoLevelNestedList} | ${4} | ${'\n * List item 1\n * List item 2'}
+ ${spaceInContentList} | ${1} | ${'\n * List item 1\n * List item 2'}
+ `('changes the list indentation to $indentSpaces spaces', ({ list, indentSpaces, result }) => {
+ htmlToMarkdownRenderer = buildHTMLToMarkdownRenderer(baseRenderer, {
+ subListIndentSpaces: indentSpaces,
+ });
+
+ baseRenderer.convert.mockReturnValueOnce(list);
+
+ expect(htmlToMarkdownRenderer['LI OL, LI UL'](NODE, list)).toBe(result);
+ expect(baseRenderer.convert).toHaveBeenCalledWith(NODE, list);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token_spec.js
new file mode 100644
index 00000000000..18dff0a39bb
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token_spec.js
@@ -0,0 +1,88 @@
+import {
+ buildTextToken,
+ buildUneditableOpenTokens,
+ buildUneditableCloseToken,
+ buildUneditableCloseTokens,
+ buildUneditableTokens,
+ buildUneditableInlineTokens,
+ buildUneditableHtmlAsTextTokens,
+} from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
+
+import {
+ originInlineToken,
+ originToken,
+ uneditableOpenTokens,
+ uneditableCloseToken,
+ uneditableCloseTokens,
+ uneditableBlockTokens,
+ uneditableInlineTokens,
+ uneditableTokens,
+} from './mock_data';
+
+describe('Build Uneditable Token renderer helper', () => {
+ describe('buildTextToken', () => {
+ it('returns an object literal representing a text token', () => {
+ const text = originToken.content;
+ expect(buildTextToken(text)).toStrictEqual(originToken);
+ });
+ });
+
+ describe('buildUneditableOpenTokens', () => {
+ it('returns a 2-item array of tokens with the originToken appended to an open token', () => {
+ const result = buildUneditableOpenTokens(originToken);
+
+ expect(result).toHaveLength(2);
+ expect(result).toStrictEqual(uneditableOpenTokens);
+ });
+ });
+
+ describe('buildUneditableCloseToken', () => {
+ it('returns an object literal representing the uneditable close token', () => {
+ expect(buildUneditableCloseToken()).toStrictEqual(uneditableCloseToken);
+ });
+ });
+
+ describe('buildUneditableCloseTokens', () => {
+ it('returns a 2-item array of tokens with the originToken prepended to a close token', () => {
+ const result = buildUneditableCloseTokens(originToken);
+
+ expect(result).toHaveLength(2);
+ expect(result).toStrictEqual(uneditableCloseTokens);
+ });
+ });
+
+ describe('buildUneditableTokens', () => {
+ it('returns a 3-item array of tokens with the originToken wrapped in the middle of block tokens', () => {
+ const result = buildUneditableTokens(originToken);
+
+ expect(result).toHaveLength(3);
+ expect(result).toStrictEqual(uneditableTokens);
+ });
+ });
+
+ describe('buildUneditableInlineTokens', () => {
+ it('returns a 3-item array of tokens with the originInlineToken wrapped in the middle of inline tokens', () => {
+ const result = buildUneditableInlineTokens(originInlineToken);
+
+ expect(result).toHaveLength(3);
+ expect(result).toStrictEqual(uneditableInlineTokens);
+ });
+ });
+
+ describe('buildUneditableHtmlAsTextTokens', () => {
+ it('returns a 3-item array of tokens with the htmlBlockNode wrapped as a text token in the middle of block tokens', () => {
+ const htmlBlockNode = {
+ type: 'htmlBlock',
+ literal: '<div data-tomark-pass ><h1>Some header</h1><p>Some paragraph</p></div>',
+ };
+ const result = buildUneditableHtmlAsTextTokens(htmlBlockNode);
+ const { type, content } = result[1];
+
+ expect(type).toBe('text');
+ expect(content).not.toMatch(/ data-tomark-pass /);
+
+ expect(result).toHaveLength(3);
+ expect(result).toStrictEqual(uneditableBlockTokens);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/mock_data.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/mock_data.js
new file mode 100644
index 00000000000..660c21281fd
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/mock_data.js
@@ -0,0 +1,58 @@
+// Node spec helpers
+
+export const buildMockTextNode = literal => {
+ return {
+ firstChild: null,
+ literal,
+ type: 'text',
+ };
+};
+
+export const normalTextNode = buildMockTextNode('This is just normal text.');
+
+// Token spec helpers
+
+const buildMockUneditableOpenToken = type => {
+ return {
+ type: 'openTag',
+ tagName: type,
+ attributes: { contenteditable: false },
+ classNames: [
+ 'gl-px-4 gl-py-2 gl-my-5 gl-opacity-5 gl-bg-gray-100 gl-user-select-none gl-cursor-not-allowed',
+ ],
+ };
+};
+
+const buildMockUneditableCloseToken = type => {
+ return { type: 'closeTag', tagName: type };
+};
+
+export const originToken = {
+ type: 'text',
+ tagName: null,
+ content: '{:.no_toc .hidden-md .hidden-lg}',
+};
+export const uneditableCloseToken = buildMockUneditableCloseToken('div');
+export const uneditableOpenTokens = [buildMockUneditableOpenToken('div'), originToken];
+export const uneditableCloseTokens = [originToken, uneditableCloseToken];
+export const uneditableTokens = [...uneditableOpenTokens, uneditableCloseToken];
+
+export const originInlineToken = {
+ type: 'text',
+ content: '<i>Inline</i> content',
+};
+export const uneditableInlineTokens = [
+ buildMockUneditableOpenToken('a'),
+ originInlineToken,
+ buildMockUneditableCloseToken('a'),
+];
+
+export const uneditableBlockTokens = [
+ buildMockUneditableOpenToken('div'),
+ {
+ type: 'text',
+ tagName: null,
+ content: '<div><h1>Some header</h1><p>Some paragraph</p></div>',
+ },
+ buildMockUneditableCloseToken('div'),
+];
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_embedded_ruby_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_embedded_ruby_spec.js
new file mode 100644
index 00000000000..b723ee8c8a0
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_embedded_ruby_spec.js
@@ -0,0 +1,30 @@
+import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_embedded_ruby_text';
+import { buildUneditableTokens } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
+
+import { buildMockTextNode, normalTextNode } from './mock_data';
+
+const embeddedRubyTextNode = buildMockTextNode('<%= partial("some/path") %>');
+
+describe('Render Embedded Ruby Text renderer', () => {
+ describe('canRender', () => {
+ it('should return true when the argument `literal` has embedded ruby syntax', () => {
+ expect(renderer.canRender(embeddedRubyTextNode)).toBe(true);
+ });
+
+ it('should return false when the argument `literal` lacks embedded ruby syntax', () => {
+ expect(renderer.canRender(normalTextNode)).toBe(false);
+ });
+ });
+
+ describe('render', () => {
+ const origin = jest.fn();
+
+ it('should return uneditable tokens', () => {
+ const context = { origin };
+
+ expect(renderer.render(embeddedRubyTextNode, context)).toStrictEqual(
+ buildUneditableTokens(origin()),
+ );
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline_spec.js
new file mode 100644
index 00000000000..d6bb01259bb
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline_spec.js
@@ -0,0 +1,33 @@
+import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline';
+import { buildUneditableInlineTokens } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
+
+import { normalTextNode } from './mock_data';
+
+const fontAwesomeInlineHtmlNode = {
+ firstChild: null,
+ literal: '<i class="far fa-paper-plane" id="biz-tech-icons">',
+ type: 'html',
+};
+
+describe('Render Font Awesome Inline HTML renderer', () => {
+ describe('canRender', () => {
+ it('should return true when the argument `literal` has font awesome inline html syntax', () => {
+ expect(renderer.canRender(fontAwesomeInlineHtmlNode)).toBe(true);
+ });
+
+ it('should return false when the argument `literal` lacks font awesome inline html syntax', () => {
+ expect(renderer.canRender(normalTextNode)).toBe(false);
+ });
+ });
+
+ describe('render', () => {
+ it('should return uneditable inline tokens', () => {
+ const token = { type: 'text', tagName: null, content: fontAwesomeInlineHtmlNode.literal };
+ const context = { origin: () => token };
+
+ expect(renderer.render(fontAwesomeInlineHtmlNode, context)).toStrictEqual(
+ buildUneditableInlineTokens(token),
+ );
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_html_block_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_html_block_spec.js
new file mode 100644
index 00000000000..a6c712eeb31
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_html_block_spec.js
@@ -0,0 +1,38 @@
+import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_html_block';
+import { buildUneditableHtmlAsTextTokens } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
+
+import { normalTextNode } from './mock_data';
+
+const htmlBlockNode = {
+ firstChild: null,
+ literal: '<div><h1>Heading</h1><p>Paragraph.</p></div>',
+ type: 'htmlBlock',
+};
+
+describe('Render HTML renderer', () => {
+ describe('canRender', () => {
+ it('should return true when the argument is an html block', () => {
+ expect(renderer.canRender(htmlBlockNode)).toBe(true);
+ });
+
+ it('should return false when the argument is not an html block', () => {
+ expect(renderer.canRender(normalTextNode)).toBe(false);
+ });
+ });
+
+ describe('render', () => {
+ const htmlBlockNodeToMark = {
+ firstChild: null,
+ literal: '<div data-to-mark ></div>',
+ type: 'htmlBlock',
+ };
+
+ it.each`
+ node
+ ${htmlBlockNode}
+ ${htmlBlockNodeToMark}
+ `('should return uneditable tokens wrapping the $node as a token', ({ node }) => {
+ expect(renderer.render(node)).toStrictEqual(buildUneditableHtmlAsTextTokens(node));
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text_spec.js
new file mode 100644
index 00000000000..2897929f1bf
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text_spec.js
@@ -0,0 +1,55 @@
+import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text';
+import { buildUneditableInlineTokens } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
+
+import { buildMockTextNode, normalTextNode } from './mock_data';
+
+const mockTextStart = 'Majority example ';
+const mockTextMiddle = '[environment terraform plans][terraform]';
+const mockTextEnd = '.';
+const identifierInstanceStartTextNode = buildMockTextNode(mockTextStart);
+const identifierInstanceEndTextNode = buildMockTextNode(mockTextEnd);
+
+describe('Render Identifier Instance Text renderer', () => {
+ describe('canRender', () => {
+ it.each`
+ node | target
+ ${normalTextNode} | ${false}
+ ${identifierInstanceStartTextNode} | ${false}
+ ${identifierInstanceEndTextNode} | ${false}
+ ${buildMockTextNode(mockTextMiddle)} | ${true}
+ ${buildMockTextNode('Minority example [environment terraform plans][]')} | ${true}
+ ${buildMockTextNode('Minority example [environment terraform plans]')} | ${true}
+ `(
+ 'should return $target when the $node validates against identifier instance syntax',
+ ({ node, target }) => {
+ expect(renderer.canRender(node)).toBe(target);
+ },
+ );
+ });
+
+ describe('render', () => {
+ it.each`
+ start | middle | end
+ ${mockTextStart} | ${mockTextMiddle} | ${mockTextEnd}
+ ${mockTextStart} | ${'[environment terraform plans][]'} | ${mockTextEnd}
+ ${mockTextStart} | ${'[environment terraform plans]'} | ${mockTextEnd}
+ `(
+ 'should return inline editable, uneditable, and editable tokens in sequence',
+ ({ start, middle, end }) => {
+ const buildMockTextToken = content => ({ type: 'text', tagName: null, content });
+
+ const startToken = buildMockTextToken(start);
+ const middleToken = buildMockTextToken(middle);
+ const endToken = buildMockTextToken(end);
+
+ const content = `${start}${middle}${end}`;
+ const contentToken = buildMockTextToken(content);
+ const contentNode = buildMockTextNode(content);
+ const context = { origin: jest.fn().mockReturnValueOnce(contentToken) };
+ expect(renderer.render(contentNode, context)).toStrictEqual(
+ [startToken, buildUneditableInlineTokens(middleToken), endToken].flat(),
+ );
+ },
+ );
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph_spec.js
new file mode 100644
index 00000000000..320589e4de3
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph_spec.js
@@ -0,0 +1,65 @@
+import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph';
+import {
+ buildUneditableOpenTokens,
+ buildUneditableCloseToken,
+} from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
+
+import { buildMockTextNode } from './mock_data';
+
+const buildMockParagraphNode = literal => {
+ return {
+ firstChild: buildMockTextNode(literal),
+ type: 'paragraph',
+ };
+};
+
+const normalParagraphNode = buildMockParagraphNode(
+ 'This is just normal paragraph. It has multiple sentences.',
+);
+const identifierParagraphNode = buildMockParagraphNode(
+ `[another-identifier]: https://example.com "This example has a title" [identifier]: http://example1.com [this link]: http://example2.com`,
+);
+
+describe('Render Identifier Paragraph renderer', () => {
+ describe('canRender', () => {
+ it.each`
+ node | paragraph | target
+ ${identifierParagraphNode} | ${'[Some text]: https://link.com'} | ${true}
+ ${normalParagraphNode} | ${'Normal non-identifier text. Another sentence.'} | ${false}
+ `(
+ 'should return $target when the $node matches $paragraph syntax',
+ ({ node, paragraph, target }) => {
+ const context = {
+ entering: true,
+ getChildrenText: jest.fn().mockReturnValueOnce(paragraph),
+ };
+
+ expect(renderer.canRender(node, context)).toBe(target);
+ },
+ );
+ });
+
+ describe('render', () => {
+ let origin;
+
+ beforeEach(() => {
+ origin = jest.fn();
+ });
+
+ it('should return uneditable open tokens when entering', () => {
+ const context = { entering: true, origin };
+
+ expect(renderer.render(identifierParagraphNode, context)).toStrictEqual(
+ buildUneditableOpenTokens(origin()),
+ );
+ });
+
+ it('should return an uneditable close tokens when exiting', () => {
+ const context = { entering: false, origin };
+
+ expect(renderer.render(identifierParagraphNode, context)).toStrictEqual(
+ buildUneditableCloseToken(origin()),
+ );
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_list_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_list_spec.js
new file mode 100644
index 00000000000..e60bf1c8c92
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_list_spec.js
@@ -0,0 +1,55 @@
+import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_list';
+import {
+ buildUneditableOpenTokens,
+ buildUneditableCloseToken,
+} from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
+
+import { buildMockTextNode } from './mock_data';
+
+const buildMockListNode = literal => {
+ return {
+ firstChild: {
+ firstChild: {
+ firstChild: buildMockTextNode(literal),
+ type: 'paragraph',
+ },
+ type: 'item',
+ },
+ type: 'list',
+ };
+};
+
+const normalListNode = buildMockListNode('Just another bullet point');
+const kramdownListNode = buildMockListNode('TOC');
+
+describe('Render Kramdown List renderer', () => {
+ describe('canRender', () => {
+ it('should return true when the argument is a special kramdown TOC ordered/unordered list', () => {
+ expect(renderer.canRender(kramdownListNode)).toBe(true);
+ });
+
+ it('should return false when the argument is a normal ordered/unordered list', () => {
+ expect(renderer.canRender(normalListNode)).toBe(false);
+ });
+ });
+
+ describe('render', () => {
+ const origin = jest.fn();
+
+ it('should return uneditable open tokens when entering', () => {
+ const context = { entering: true, origin };
+
+ expect(renderer.render(kramdownListNode, context)).toStrictEqual(
+ buildUneditableOpenTokens(origin()),
+ );
+ });
+
+ it('should return an uneditable close tokens when exiting', () => {
+ const context = { entering: false, origin };
+
+ expect(renderer.render(kramdownListNode, context)).toStrictEqual(
+ buildUneditableCloseToken(origin()),
+ );
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_text_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_text_spec.js
new file mode 100644
index 00000000000..97ff9794e69
--- /dev/null
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_text_spec.js
@@ -0,0 +1,30 @@
+import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_text';
+import { buildUneditableTokens } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
+
+import { buildMockTextNode, normalTextNode } from './mock_data';
+
+const kramdownTextNode = buildMockTextNode('{:toc}');
+
+describe('Render Kramdown Text renderer', () => {
+ describe('canRender', () => {
+ it('should return true when the argument `literal` has kramdown syntax', () => {
+ expect(renderer.canRender(kramdownTextNode)).toBe(true);
+ });
+
+ it('should return false when the argument `literal` lacks kramdown syntax', () => {
+ expect(renderer.canRender(normalTextNode)).toBe(false);
+ });
+ });
+
+ describe('render', () => {
+ const origin = jest.fn();
+
+ it('should return uneditable tokens', () => {
+ const context = { origin };
+
+ expect(renderer.render(kramdownTextNode, context)).toStrictEqual(
+ buildUneditableTokens(origin()),
+ );
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js
index d02d924bd2b..79851e5db05 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js
@@ -82,10 +82,9 @@ describe('DropdownButtonComponent', () => {
});
it('renders dropdown button icon', () => {
- const dropdownIconEl = vm.$el.querySelector('i.fa');
+ const dropdownIconEl = vm.$el.querySelector('.dropdown-menu-toggle .gl-icon');
expect(dropdownIconEl).not.toBeNull();
- expect(dropdownIconEl.classList.contains('fa-chevron-down')).toBe(true);
});
});
});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js
index 035af946d75..510e537b1cd 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js
@@ -29,13 +29,11 @@ describe('DropdownSearchInputComponent', () => {
});
it('renders search icon element', () => {
- expect(vm.$el.querySelector('.fa-search.dropdown-input-search')).not.toBeNull();
+ expect(vm.$el.querySelector('.dropdown-input-search')).not.toBeNull();
});
it('renders clear search icon element', () => {
- expect(
- vm.$el.querySelector('.fa-times.dropdown-input-clear.js-dropdown-input-clear'),
- ).not.toBeNull();
+ expect(vm.$el.querySelector('.dropdown-input-clear.js-dropdown-input-clear')).not.toBeNull();
});
});
});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js
index 214eb239432..68c9d26bb1a 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js
@@ -1,18 +1,19 @@
import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
-import { GlIcon } from '@gitlab/ui';
+import { GlIcon, GlButton } from '@gitlab/ui';
import DropdownButton from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue';
import labelSelectModule from '~/vue_shared/components/sidebar/labels_select_vue/store';
import { mockConfig } from './mock_data';
+let store;
const localVue = createLocalVue();
localVue.use(Vuex);
const createComponent = (initialState = mockConfig) => {
- const store = new Vuex.Store(labelSelectModule());
+ store = new Vuex.Store(labelSelectModule());
store.dispatch('setInitialState', initialState);
@@ -33,26 +34,32 @@ describe('DropdownButton', () => {
wrapper.destroy();
});
+ const findDropdownButton = () => wrapper.find(GlButton);
+ const findDropdownText = () => wrapper.find('.dropdown-toggle-text');
+ const findDropdownIcon = () => wrapper.find(GlIcon);
+
describe('methods', () => {
describe('handleButtonClick', () => {
- it('calls action `toggleDropdownContents` and stops event propagation when `state.variant` is "standalone"', () => {
- const event = {
- stopPropagation: jest.fn(),
- };
- wrapper = createComponent({
- ...mockConfig,
- variant: 'standalone',
- });
-
- jest.spyOn(wrapper.vm, 'toggleDropdownContents');
-
- wrapper.vm.handleButtonClick(event);
-
- expect(wrapper.vm.toggleDropdownContents).toHaveBeenCalled();
- expect(event.stopPropagation).toHaveBeenCalled();
-
- wrapper.destroy();
- });
+ it.each`
+ variant
+ ${'standalone'}
+ ${'embedded'}
+ `(
+ 'toggles dropdown content and stops event propagation when `state.variant` is "$variant"',
+ ({ variant }) => {
+ const event = { stopPropagation: jest.fn() };
+
+ wrapper = createComponent({
+ ...mockConfig,
+ variant,
+ });
+
+ findDropdownButton().vm.$emit('click', event);
+
+ expect(store.state.showDropdownContents).toBe(true);
+ expect(event.stopPropagation).toHaveBeenCalled();
+ },
+ );
});
});
@@ -61,15 +68,24 @@ describe('DropdownButton', () => {
expect(wrapper.is('gl-button-stub')).toBe(true);
});
- it('renders button text element', () => {
- const dropdownTextEl = wrapper.find('.dropdown-toggle-text');
+ it('renders default button text element', () => {
+ const dropdownTextEl = findDropdownText();
expect(dropdownTextEl.exists()).toBe(true);
expect(dropdownTextEl.text()).toBe('Label');
});
+ it('renders provided button text element', () => {
+ store.state.dropdownButtonText = 'Custom label';
+ const dropdownTextEl = findDropdownText();
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(dropdownTextEl.text()).toBe('Custom label');
+ });
+ });
+
it('renders chevron icon element', () => {
- const iconEl = wrapper.find(GlIcon);
+ const iconEl = findDropdownIcon();
expect(iconEl.exists()).toBe(true);
expect(iconEl.props('name')).toBe('chevron-down');
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js
index 1504e1521d3..9b01e0b9637 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js
@@ -44,6 +44,7 @@ const createComponent = (initialState = mockConfig) => {
describe('DropdownContentsLabelsView', () => {
let wrapper;
let wrapperStandalone;
+ let wrapperEmbedded;
beforeEach(() => {
wrapper = createComponent();
@@ -51,11 +52,16 @@ describe('DropdownContentsLabelsView', () => {
...mockConfig,
variant: 'standalone',
});
+ wrapperEmbedded = createComponent({
+ ...mockConfig,
+ variant: 'embedded',
+ });
});
afterEach(() => {
wrapper.destroy();
wrapperStandalone.destroy();
+ wrapperEmbedded.destroy();
});
describe('computed', () => {
@@ -211,6 +217,10 @@ describe('DropdownContentsLabelsView', () => {
expect(wrapperStandalone.find('.dropdown-title').exists()).toBe(false);
});
+ it('renders dropdown title element when `state.variant` is "embedded"', () => {
+ expect(wrapperEmbedded.find('.dropdown-title').exists()).toBe(true);
+ });
+
it('renders dropdown close button element', () => {
const closeButtonEl = wrapper.find('.dropdown-title').find(GlButton);
@@ -291,5 +301,9 @@ describe('DropdownContentsLabelsView', () => {
it('does not render footer list items when `state.variant` is "standalone"', () => {
expect(wrapperStandalone.find('.dropdown-footer').exists()).toBe(false);
});
+
+ it('renders footer list items when `state.variant` is "embedded"', () => {
+ expect(wrapperEmbedded.find('.dropdown-footer').exists()).toBe(true);
+ });
});
});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js
index ee4e9090e5d..6e97b046be2 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js
@@ -89,18 +89,23 @@ describe('LabelsSelectRoot', () => {
expect(wrapper.attributes('class')).toContain('labels-select-wrapper position-relative');
});
- it('renders component root element with CSS class `is-standalone` when `state.variant` is "standalone"', () => {
- const wrapperStandalone = createComponent({
- ...mockConfig,
- variant: 'standalone',
- });
-
- return wrapperStandalone.vm.$nextTick(() => {
- expect(wrapperStandalone.classes()).toContain('is-standalone');
-
- wrapperStandalone.destroy();
- });
- });
+ it.each`
+ variant | cssClass
+ ${'standalone'} | ${'is-standalone'}
+ ${'embedded'} | ${'is-embedded'}
+ `(
+ 'renders component root element with CSS class `$cssClass` when `state.variant` is "$variant"',
+ ({ variant, cssClass }) => {
+ wrapper = createComponent({
+ ...mockConfig,
+ variant,
+ });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.classes()).toContain(cssClass);
+ });
+ },
+ );
it('renders `dropdown-value-collapsed` component when `allowLabelCreate` prop is `true`', () => {
expect(wrapper.find(DropdownValueCollapsed).exists()).toBe(true);
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/getters_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/getters_spec.js
index b866117efcf..52116f757c5 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/getters_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/getters_spec.js
@@ -2,13 +2,20 @@ import * as getters from '~/vue_shared/components/sidebar/labels_select_vue/stor
describe('LabelsSelect Getters', () => {
describe('dropdownButtonText', () => {
- it('returns string "Label" when state.labels has no selected labels', () => {
- const labels = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }];
-
- expect(getters.dropdownButtonText({ labels }, { isDropdownVariantSidebar: true })).toBe(
- 'Label',
- );
- });
+ it.each`
+ labelType | dropdownButtonText | expected
+ ${'default'} | ${''} | ${'Label'}
+ ${'custom'} | ${'Custom label'} | ${'Custom label'}
+ `(
+ 'returns $labelType text when state.labels has no selected labels',
+ ({ dropdownButtonText, expected }) => {
+ const labels = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }];
+ const selectedLabels = [];
+ const state = { labels, selectedLabels, dropdownButtonText };
+
+ expect(getters.dropdownButtonText(state, {})).toBe(expected);
+ },
+ );
it('returns label title when state.labels has only 1 label', () => {
const labels = [{ id: 1, title: 'Foobar', set: true }];
diff --git a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
index 2c7fce714f0..a4ff6ac0c16 100644
--- a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
+++ b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
@@ -4,7 +4,6 @@ import UserPopover from '~/vue_shared/components/user_popover/user_popover.vue';
import Icon from '~/vue_shared/components/icon.vue';
const DEFAULT_PROPS = {
- loaded: true,
user: {
username: 'root',
name: 'Administrator',
@@ -12,6 +11,7 @@ const DEFAULT_PROPS = {
bio: null,
workInformation: null,
status: null,
+ loaded: true,
},
};
@@ -46,28 +46,21 @@ describe('User Popover Component', () => {
});
};
- describe('Empty', () => {
- beforeEach(() => {
- createWrapper(
- {},
- {
- propsData: {
- target: findTarget(),
- user: {
- name: null,
- username: null,
- location: null,
- bio: null,
- workInformation: null,
- status: null,
- },
- },
+ describe('when user is loading', () => {
+ it('displays skeleton loaders', () => {
+ createWrapper({
+ user: {
+ name: null,
+ username: null,
+ location: null,
+ bio: null,
+ workInformation: null,
+ status: null,
+ loaded: false,
},
- );
- });
+ });
- it('should return skeleton loaders', () => {
- expect(wrapper.find(GlSkeletonLoading).exists()).toBe(true);
+ expect(wrapper.findAll(GlSkeletonLoading)).toHaveLength(4);
});
});
@@ -90,9 +83,10 @@ describe('User Popover Component', () => {
describe('job data', () => {
const findWorkInformation = () => wrapper.find({ ref: 'workInformation' });
const findBio = () => wrapper.find({ ref: 'bio' });
+ const bio = 'My super interesting bio';
it('should show only bio if work information is not available', () => {
- const user = { ...DEFAULT_PROPS.user, bio: 'My super interesting bio' };
+ const user = { ...DEFAULT_PROPS.user, bio, bioHtml: bio };
createWrapper({ user });
@@ -114,7 +108,8 @@ describe('User Popover Component', () => {
it('should display bio and work information in separate lines', () => {
const user = {
...DEFAULT_PROPS.user,
- bio: 'My super interesting bio',
+ bio,
+ bioHtml: bio,
workInformation: 'Frontend Engineer at GitLab',
};
@@ -127,12 +122,13 @@ describe('User Popover Component', () => {
it('should not encode special characters in bio', () => {
const user = {
...DEFAULT_PROPS.user,
- bio: 'I like <html> & CSS',
+ bio: 'I like CSS',
+ bioHtml: 'I like <b>CSS</b>',
};
createWrapper({ user });
- expect(findBio().text()).toBe('I like <html> & CSS');
+ expect(findBio().html()).toContain('I like <b>CSS</b>');
});
it('shows icon for bio', () => {
diff --git a/spec/frontend/wikis_spec.js b/spec/frontend/wikis_spec.js
index 8c68edafd16..3469be4da1c 100644
--- a/spec/frontend/wikis_spec.js
+++ b/spec/frontend/wikis_spec.js
@@ -1,4 +1,6 @@
+import { escape } from 'lodash';
import Wikis from '~/pages/shared/wikis/wikis';
+import Tracking from '~/tracking';
import { setHTMLFixture } from './helpers/fixtures';
describe('Wikis', () => {
@@ -122,4 +124,32 @@ describe('Wikis', () => {
});
});
});
+
+ describe('trackPageView', () => {
+ const trackingPage = 'projects:wikis:show';
+ const trackingContext = { foo: 'bar' };
+ const showPageHtmlFixture = `
+ <div class="js-wiki-page-content" data-tracking-context="${escape(
+ JSON.stringify(trackingContext),
+ )}"></div>
+ `;
+
+ beforeEach(() => {
+ setHTMLFixture(showPageHtmlFixture);
+ document.body.dataset.page = trackingPage;
+ jest.spyOn(Tracking, 'event').mockImplementation();
+
+ Wikis.trackPageView();
+ });
+
+ it('sends the tracking event and context', () => {
+ expect(Tracking.event).toHaveBeenCalledWith(trackingPage, 'view_wiki_page', {
+ label: 'view_wiki_page',
+ context: {
+ schema: 'iglu:com.gitlab/wiki_page_context/jsonschema/1-0-0',
+ data: trackingContext,
+ },
+ });
+ });
+ });
});
diff --git a/spec/frontend_integration/ide/__snapshots__/ide_integration_spec.js.snap b/spec/frontend_integration/ide/__snapshots__/ide_integration_spec.js.snap
index fe714924c2b..6beb5eab6db 100644
--- a/spec/frontend_integration/ide/__snapshots__/ide_integration_spec.js.snap
+++ b/spec/frontend_integration/ide/__snapshots__/ide_integration_spec.js.snap
@@ -112,7 +112,6 @@ exports[`WebIDE runs 1`] = `
class="gl-spinner-container"
>
<span
- aria-hidden="true"
aria-label="Loading"
class="align-text-bottom gl-spinner gl-spinner-orange gl-spinner-md"
/>
diff --git a/spec/graphql/mutations/alert_management/alerts/set_assignees_spec.rb b/spec/graphql/mutations/alert_management/alerts/set_assignees_spec.rb
index a025b3d344a..f8b61c5064a 100644
--- a/spec/graphql/mutations/alert_management/alerts/set_assignees_spec.rb
+++ b/spec/graphql/mutations/alert_management/alerts/set_assignees_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::AlertManagement::Alerts::SetAssignees do
+RSpec.describe Mutations::AlertManagement::Alerts::SetAssignees do
let_it_be(:starting_assignee) { create(:user) }
let_it_be(:unassigned_user) { create(:user) }
let_it_be(:alert) { create(:alert_management_alert, assignees: [starting_assignee]) }
diff --git a/spec/graphql/mutations/alert_management/alerts/todo/create_spec.rb b/spec/graphql/mutations/alert_management/alerts/todo/create_spec.rb
new file mode 100644
index 00000000000..11ee40a4c7e
--- /dev/null
+++ b/spec/graphql/mutations/alert_management/alerts/todo/create_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::AlertManagement::Alerts::Todo::Create do
+ subject(:mutation) { described_class.new(object: project, context: { current_user: current_user }, field: nil) }
+
+ let_it_be(:alert) { create(:alert_management_alert) }
+ let_it_be(:project) { alert.project }
+ let(:current_user) { project.owner }
+
+ let(:args) { { project_path: project.full_path, iid: alert.iid } }
+
+ specify { expect(described_class).to require_graphql_authorizations(:update_alert_management_alert) }
+
+ describe '#resolve' do
+ subject(:resolve) { mutation.resolve(args) }
+
+ context 'when user does not have permissions' do
+ let(:current_user) { nil }
+
+ specify { expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) }
+ end
+
+ context 'when project is invalid' do
+ let(:args) { { project_path: 'bunk/path', iid: alert.iid } }
+
+ specify { expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) }
+ end
+
+ context 'when alert is invalid' do
+ let(:args) { { project_path: project.full_path, iid: "-1" } }
+
+ specify { expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) }
+ end
+
+ context 'when the create service yields errors' do
+ let(:error_response) { double(error?: true, message: 'error', payload: { alert: {} }) }
+
+ before do
+ allow_next_instance_of(::AlertManagement::Alerts::Todo::CreateService) do |service|
+ allow(service).to receive(:execute).and_return(error_response)
+ end
+ end
+
+ specify { expect { resolve }.not_to change(Todo, :count) }
+ specify { expect(resolve[:errors]).to eq([error_response.message]) }
+ end
+
+ context 'with valid inputs' do
+ it 'creates a new todo' do
+ expect { resolve }.to change { Todo.where(user: current_user, action: Todo::MARKED).count }.by(1)
+ end
+
+ it { is_expected.to eq(alert: alert, todo: Todo.last, errors: []) }
+ end
+ end
+end
diff --git a/spec/graphql/mutations/alert_management/update_alert_status_spec.rb b/spec/graphql/mutations/alert_management/update_alert_status_spec.rb
index 68513c02040..a224b564de9 100644
--- a/spec/graphql/mutations/alert_management/update_alert_status_spec.rb
+++ b/spec/graphql/mutations/alert_management/update_alert_status_spec.rb
@@ -39,7 +39,7 @@ RSpec.describe Mutations::AlertManagement::UpdateAlertStatus do
allow(alert).to receive(:save).and_return(false)
allow(alert).to receive(:errors).and_return(
- double(full_messages: %w(foo bar))
+ double(full_messages: %w(foo bar), :[] => nil)
)
expect(resolve).to eq(
alert: alert,
diff --git a/spec/graphql/mutations/concerns/mutations/resolves_issuable_spec.rb b/spec/graphql/mutations/concerns/mutations/resolves_issuable_spec.rb
index 706a54931ea..8af3868ba98 100644
--- a/spec/graphql/mutations/concerns/mutations/resolves_issuable_spec.rb
+++ b/spec/graphql/mutations/concerns/mutations/resolves_issuable_spec.rb
@@ -3,26 +3,31 @@
require 'spec_helper'
RSpec.describe Mutations::ResolvesIssuable do
+ include GraphqlHelpers
+
let_it_be(:mutation_class) do
Class.new(Mutations::BaseMutation) do
include Mutations::ResolvesIssuable
end
end
- let_it_be(:project) { create(:project) }
+ let_it_be(:project) { create(:project, :empty_repo) }
let_it_be(:user) { create(:user) }
let_it_be(:context) { { current_user: user } }
- let_it_be(:mutation) { mutation_class.new(object: nil, context: context, field: nil) }
+ let(:mutation) { mutation_class.new(object: nil, context: context, field: nil) }
let(:parent) { issuable.project }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:merge_request) { create(:merge_request, source_project: project) }
+
context 'with issues' do
- let(:issuable) { create(:issue, project: project) }
+ let(:issuable) { issue }
it_behaves_like 'resolving an issuable in GraphQL', :issue
end
context 'with merge requests' do
- let(:issuable) { create(:merge_request, source_project: project) }
+ let(:issuable) { merge_request }
it_behaves_like 'resolving an issuable in GraphQL', :merge_request
end
diff --git a/spec/graphql/mutations/container_expiration_policies/update_spec.rb b/spec/graphql/mutations/container_expiration_policies/update_spec.rb
index fc90f437576..6aedaab3b53 100644
--- a/spec/graphql/mutations/container_expiration_policies/update_spec.rb
+++ b/spec/graphql/mutations/container_expiration_policies/update_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::ContainerExpirationPolicies::Update do
+RSpec.describe Mutations::ContainerExpirationPolicies::Update do
using RSpec::Parameterized::TableSyntax
let_it_be(:project, reload: true) { create(:project) }
diff --git a/spec/graphql/mutations/issues/set_locked_spec.rb b/spec/graphql/mutations/issues/set_locked_spec.rb
new file mode 100644
index 00000000000..10438226c17
--- /dev/null
+++ b/spec/graphql/mutations/issues/set_locked_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Issues::SetLocked do
+ let_it_be(:issue) { create(:issue) }
+ let_it_be(:user) { create(:user) }
+
+ subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
+
+ specify { expect(described_class).to require_graphql_authorizations(:update_issue) }
+
+ describe '#resolve' do
+ let(:locked) { true }
+
+ subject { mutation.resolve(project_path: issue.project.full_path, iid: issue.iid, locked: locked) }
+
+ it 'raises an error if the resource is not accessible to the user' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+
+ context 'when the user can update the issue' do
+ let(:mutated_issue) { subject[:issue] }
+
+ before do
+ issue.project.add_developer(user)
+ end
+
+ it 'returns the issue as discussion locked' do
+ expect(mutated_issue).to eq(issue)
+ expect(mutated_issue).to be_discussion_locked
+ expect(subject[:errors]).to be_empty
+ end
+
+ context 'when passing locked as false' do
+ let(:locked) { false }
+
+ it 'unlocks the discussion' do
+ issue.update!(discussion_locked: true)
+
+ expect(mutated_issue).not_to be_discussion_locked
+ end
+ end
+ end
+ end
+end
diff --git a/spec/graphql/mutations/issues/update_spec.rb b/spec/graphql/mutations/issues/update_spec.rb
index 8c3d01918fd..9a847476e2e 100644
--- a/spec/graphql/mutations/issues/update_spec.rb
+++ b/spec/graphql/mutations/issues/update_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::Issues::Update do
+RSpec.describe Mutations::Issues::Update do
let(:issue) { create(:issue) }
let(:user) { create(:user) }
let(:expected_attributes) do
diff --git a/spec/graphql/mutations/merge_requests/create_spec.rb b/spec/graphql/mutations/merge_requests/create_spec.rb
index 88acd3ed5b6..ae31790f1f9 100644
--- a/spec/graphql/mutations/merge_requests/create_spec.rb
+++ b/spec/graphql/mutations/merge_requests/create_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::MergeRequests::Create do
+RSpec.describe Mutations::MergeRequests::Create do
subject(:mutation) { described_class.new(object: nil, context: context, field: nil) }
let_it_be(:project) { create(:project, :public, :repository) }
diff --git a/spec/graphql/mutations/merge_requests/set_assignees_spec.rb b/spec/graphql/mutations/merge_requests/set_assignees_spec.rb
index d88c5db05c9..0e7abb849c4 100644
--- a/spec/graphql/mutations/merge_requests/set_assignees_spec.rb
+++ b/spec/graphql/mutations/merge_requests/set_assignees_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::MergeRequests::SetAssignees do
+RSpec.describe Mutations::MergeRequests::SetAssignees do
let(:merge_request) { create(:merge_request) }
let(:user) { create(:user) }
diff --git a/spec/graphql/mutations/merge_requests/set_labels_spec.rb b/spec/graphql/mutations/merge_requests/set_labels_spec.rb
index 0fd2c20a5c8..62a7f650f84 100644
--- a/spec/graphql/mutations/merge_requests/set_labels_spec.rb
+++ b/spec/graphql/mutations/merge_requests/set_labels_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::MergeRequests::SetLabels do
+RSpec.describe Mutations::MergeRequests::SetLabels do
let(:merge_request) { create(:merge_request) }
let(:user) { create(:user) }
diff --git a/spec/graphql/mutations/merge_requests/set_locked_spec.rb b/spec/graphql/mutations/merge_requests/set_locked_spec.rb
index d5219c781fd..aca7df5445f 100644
--- a/spec/graphql/mutations/merge_requests/set_locked_spec.rb
+++ b/spec/graphql/mutations/merge_requests/set_locked_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::MergeRequests::SetLocked do
+RSpec.describe Mutations::MergeRequests::SetLocked do
let(:merge_request) { create(:merge_request) }
let(:user) { create(:user) }
diff --git a/spec/graphql/mutations/merge_requests/set_milestone_spec.rb b/spec/graphql/mutations/merge_requests/set_milestone_spec.rb
index d77ec4de4d0..1c0d655ee83 100644
--- a/spec/graphql/mutations/merge_requests/set_milestone_spec.rb
+++ b/spec/graphql/mutations/merge_requests/set_milestone_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::MergeRequests::SetMilestone do
+RSpec.describe Mutations::MergeRequests::SetMilestone do
let(:merge_request) { create(:merge_request) }
let(:user) { create(:user) }
diff --git a/spec/graphql/mutations/merge_requests/set_subscription_spec.rb b/spec/graphql/mutations/merge_requests/set_subscription_spec.rb
index cf569a74aa9..20cfed9dd3d 100644
--- a/spec/graphql/mutations/merge_requests/set_subscription_spec.rb
+++ b/spec/graphql/mutations/merge_requests/set_subscription_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::MergeRequests::SetSubscription do
+RSpec.describe Mutations::MergeRequests::SetSubscription do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.project }
let(:user) { create(:user) }
diff --git a/spec/graphql/mutations/merge_requests/set_wip_spec.rb b/spec/graphql/mutations/merge_requests/set_wip_spec.rb
index 7255d0fe7d7..b6cb49724fa 100644
--- a/spec/graphql/mutations/merge_requests/set_wip_spec.rb
+++ b/spec/graphql/mutations/merge_requests/set_wip_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::MergeRequests::SetWip do
+RSpec.describe Mutations::MergeRequests::SetWip do
let(:merge_request) { create(:merge_request) }
let(:user) { create(:user) }
diff --git a/spec/graphql/mutations/merge_requests/update_spec.rb b/spec/graphql/mutations/merge_requests/update_spec.rb
new file mode 100644
index 00000000000..4a1fdf6e74b
--- /dev/null
+++ b/spec/graphql/mutations/merge_requests/update_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::MergeRequests::Update do
+ let(:merge_request) { create(:merge_request) }
+ let(:user) { create(:user) }
+
+ subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
+
+ specify { expect(described_class).to require_graphql_authorizations(:update_merge_request) }
+
+ describe '#resolve' do
+ let(:attributes) { { title: 'new title', description: 'new description', target_branch: 'new-branch' } }
+ let(:mutated_merge_request) { subject[:merge_request] }
+
+ subject do
+ mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, **attributes)
+ end
+
+ it 'raises an error if the resource is not accessible to the user' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+
+ context 'when the user can update the merge request' do
+ before do
+ merge_request.project.add_developer(user)
+ end
+
+ it 'applies all attributes' do
+ expect(mutated_merge_request).to eq(merge_request)
+ expect(mutated_merge_request).to have_attributes(attributes)
+ expect(subject[:errors]).to be_empty
+ end
+
+ context 'the merge request is invalid' do
+ before do
+ merge_request.allow_broken = true
+ merge_request.update!(source_project: nil)
+ end
+
+ it 'returns error information, and changes were not applied' do
+ expect(mutated_merge_request).not_to have_attributes(attributes)
+ expect(subject[:errors]).not_to be_empty
+ end
+ end
+
+ context 'our change is invalid' do
+ let(:attributes) { { target_branch: 'this is not a branch' } }
+
+ it 'returns error information, and changes were not applied' do
+ expect(mutated_merge_request).not_to have_attributes(attributes)
+ expect(subject[:errors]).not_to be_empty
+ end
+ end
+
+ context 'when passing subset of attributes' do
+ let(:attributes) { { title: 'no, this title' } }
+
+ it 'only changes the mentioned attributes' do
+ expect { subject }.not_to change { merge_request.reset.description }
+
+ expect(mutated_merge_request).to have_attributes(attributes)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/graphql/mutations/todos/mark_all_done_spec.rb b/spec/graphql/mutations/todos/mark_all_done_spec.rb
index 4af00307969..2f167164050 100644
--- a/spec/graphql/mutations/todos/mark_all_done_spec.rb
+++ b/spec/graphql/mutations/todos/mark_all_done_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::Todos::MarkAllDone do
+RSpec.describe Mutations::Todos::MarkAllDone do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
@@ -21,7 +21,7 @@ describe Mutations::Todos::MarkAllDone do
describe '#resolve' do
it 'marks all pending todos as done' do
- updated_todo_ids = mutation_for(current_user).resolve.dig(:updated_ids)
+ updated_todo_ids, todos = mutation_for(current_user).resolve.values_at(:updated_ids, :todos)
expect(todo1.reload.state).to eq('done')
expect(todo2.reload.state).to eq('done')
@@ -29,6 +29,7 @@ describe Mutations::Todos::MarkAllDone do
expect(other_user_todo.reload.state).to eq('pending')
expect(updated_todo_ids).to contain_exactly(global_id_of(todo1), global_id_of(todo3))
+ expect(todos).to contain_exactly(todo1, todo3)
end
it 'behaves as expected if there are no todos for the requesting user' do
diff --git a/spec/graphql/mutations/todos/mark_done_spec.rb b/spec/graphql/mutations/todos/mark_done_spec.rb
index 44065f83f74..51ad3e1a5d7 100644
--- a/spec/graphql/mutations/todos/mark_done_spec.rb
+++ b/spec/graphql/mutations/todos/mark_done_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::Todos::MarkDone do
+RSpec.describe Mutations::Todos::MarkDone do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/graphql/mutations/todos/restore_many_spec.rb b/spec/graphql/mutations/todos/restore_many_spec.rb
index 8f4a8985f9e..b3b3e057745 100644
--- a/spec/graphql/mutations/todos/restore_many_spec.rb
+++ b/spec/graphql/mutations/todos/restore_many_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::Todos::RestoreMany do
+RSpec.describe Mutations::Todos::RestoreMany do
let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) }
@@ -25,6 +25,8 @@ describe Mutations::Todos::RestoreMany do
todo_ids = result[:updated_ids]
expect(todo_ids.size).to eq(1)
expect(todo_ids.first).to eq(todo1.to_global_id.to_s)
+
+ expect(result[:todos]).to contain_exactly(todo1)
end
it 'handles a todo which is already pending as expected' do
@@ -33,6 +35,7 @@ describe Mutations::Todos::RestoreMany do
expect_states_were_not_changed
expect(result[:updated_ids]).to eq([])
+ expect(result[:todos]).to be_empty
end
it 'ignores requests for todos which do not belong to the current user' do
@@ -56,6 +59,7 @@ describe Mutations::Todos::RestoreMany do
returned_todo_ids = result[:updated_ids]
expect(returned_todo_ids).to contain_exactly(todo1.to_global_id.to_s, todo4.to_global_id.to_s)
+ expect(result[:todos]).to contain_exactly(todo1, todo4)
expect(todo1.reload.state).to eq('pending')
expect(todo2.reload.state).to eq('pending')
diff --git a/spec/graphql/mutations/todos/restore_spec.rb b/spec/graphql/mutations/todos/restore_spec.rb
index 949ab6a164b..9043d7a44a8 100644
--- a/spec/graphql/mutations/todos/restore_spec.rb
+++ b/spec/graphql/mutations/todos/restore_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::Todos::Restore do
+RSpec.describe Mutations::Todos::Restore do
let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) }
diff --git a/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb b/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb
index 6c12f765e69..0c1ba5aab2c 100644
--- a/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb
+++ b/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::AlertManagement::AlertResolver do
+RSpec.describe Resolvers::AlertManagement::AlertResolver do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/graphql/resolvers/alert_management/alert_status_counts_resolver_spec.rb b/spec/graphql/resolvers/alert_management/alert_status_counts_resolver_spec.rb
index 8eb28c8c945..b72e692f2e8 100644
--- a/spec/graphql/resolvers/alert_management/alert_status_counts_resolver_spec.rb
+++ b/spec/graphql/resolvers/alert_management/alert_status_counts_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::AlertManagement::AlertStatusCountsResolver do
+RSpec.describe Resolvers::AlertManagement::AlertStatusCountsResolver do
include GraphqlHelpers
describe '#resolve' do
diff --git a/spec/graphql/resolvers/base_resolver_spec.rb b/spec/graphql/resolvers/base_resolver_spec.rb
index 6c384349577..40dc2370052 100644
--- a/spec/graphql/resolvers/base_resolver_spec.rb
+++ b/spec/graphql/resolvers/base_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::BaseResolver do
+RSpec.describe Resolvers::BaseResolver do
include GraphqlHelpers
let(:resolver) do
diff --git a/spec/graphql/resolvers/board_lists_resolver_spec.rb b/spec/graphql/resolvers/board_lists_resolver_spec.rb
index 5f6c440a8ed..f662e9a0f62 100644
--- a/spec/graphql/resolvers/board_lists_resolver_spec.rb
+++ b/spec/graphql/resolvers/board_lists_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::BoardListsResolver do
+RSpec.describe Resolvers::BoardListsResolver do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
diff --git a/spec/graphql/resolvers/boards_resolver_spec.rb b/spec/graphql/resolvers/boards_resolver_spec.rb
index 02d6f808118..f121e8a4083 100644
--- a/spec/graphql/resolvers/boards_resolver_spec.rb
+++ b/spec/graphql/resolvers/boards_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::BoardsResolver do
+RSpec.describe Resolvers::BoardsResolver do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
diff --git a/spec/graphql/resolvers/branch_commit_resolver_spec.rb b/spec/graphql/resolvers/branch_commit_resolver_spec.rb
index 22e1de8f375..78d4959c3f9 100644
--- a/spec/graphql/resolvers/branch_commit_resolver_spec.rb
+++ b/spec/graphql/resolvers/branch_commit_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::BranchCommitResolver do
+RSpec.describe Resolvers::BranchCommitResolver do
include GraphqlHelpers
subject(:commit) { resolve(described_class, obj: branch) }
diff --git a/spec/graphql/resolvers/ci_configuration/sast_resolver_spec.rb b/spec/graphql/resolvers/ci_configuration/sast_resolver_spec.rb
new file mode 100644
index 00000000000..de69ad5d450
--- /dev/null
+++ b/spec/graphql/resolvers/ci_configuration/sast_resolver_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::CiConfiguration::SastResolver do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+
+ describe '#resolve' do
+ subject(:sast_config) { resolve(described_class, ctx: { current_user: user }, obj: project) }
+
+ it 'returns global variable informations related to SAST' do
+ expect(sast_config['global'].first['field']).to eql("SECURE_ANALYZERS_PREFIX")
+ expect(sast_config['global'].first['label']).to eql("Image prefix")
+ expect(sast_config['global'].first['type']).to eql("string")
+
+ expect(sast_config['pipeline'].first['field']).to eql("stage")
+ expect(sast_config['pipeline'].first['label']).to eql("Stage")
+ expect(sast_config['pipeline'].first['type']).to eql("dropdown")
+
+ expect(sast_config['analyzers'].first['name']).to eql("brakeman")
+ expect(sast_config['analyzers'].first['label']).to eql("Brakeman")
+ expect(sast_config['analyzers'].first['enabled']).to be true
+ end
+ end
+end
diff --git a/spec/graphql/resolvers/commit_pipelines_resolver_spec.rb b/spec/graphql/resolvers/commit_pipelines_resolver_spec.rb
index 93da877d714..20a0cb842a4 100644
--- a/spec/graphql/resolvers/commit_pipelines_resolver_spec.rb
+++ b/spec/graphql/resolvers/commit_pipelines_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::CommitPipelinesResolver do
+RSpec.describe Resolvers::CommitPipelinesResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/graphql/resolvers/concerns/looks_ahead_spec.rb b/spec/graphql/resolvers/concerns/looks_ahead_spec.rb
index 8b83f887846..f13823085b8 100644
--- a/spec/graphql/resolvers/concerns/looks_ahead_spec.rb
+++ b/spec/graphql/resolvers/concerns/looks_ahead_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LooksAhead do
+RSpec.describe LooksAhead do
include GraphqlHelpers
let_it_be(:the_user) { create(:user) }
diff --git a/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb b/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb
index 03ff1e11d85..3dffda75e08 100644
--- a/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb
+++ b/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResolvesPipelines do
+RSpec.describe ResolvesPipelines do
include GraphqlHelpers
subject(:resolver) do
diff --git a/spec/graphql/resolvers/concerns/resolves_project_spec.rb b/spec/graphql/resolvers/concerns/resolves_project_spec.rb
index f29f54483d6..1748d8a81a3 100644
--- a/spec/graphql/resolvers/concerns/resolves_project_spec.rb
+++ b/spec/graphql/resolvers/concerns/resolves_project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResolvesProject do
+RSpec.describe ResolvesProject do
include GraphqlHelpers
let(:implementing_class) do
diff --git a/spec/graphql/resolvers/design_management/design_at_version_resolver_spec.rb b/spec/graphql/resolvers/design_management/design_at_version_resolver_spec.rb
index a5054ae3ebf..4bdef49499c 100644
--- a/spec/graphql/resolvers/design_management/design_at_version_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/design_at_version_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::DesignManagement::DesignAtVersionResolver do
+RSpec.describe Resolvers::DesignManagement::DesignAtVersionResolver do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/graphql/resolvers/design_management/design_resolver_spec.rb b/spec/graphql/resolvers/design_management/design_resolver_spec.rb
index 857acc3d371..02d7f94612c 100644
--- a/spec/graphql/resolvers/design_management/design_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/design_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::DesignManagement::DesignResolver do
+RSpec.describe Resolvers::DesignManagement::DesignResolver do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/graphql/resolvers/design_management/designs_resolver_spec.rb b/spec/graphql/resolvers/design_management/designs_resolver_spec.rb
index 28fc9e2151d..cfa37d34fd9 100644
--- a/spec/graphql/resolvers/design_management/designs_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/designs_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::DesignManagement::DesignsResolver do
+RSpec.describe Resolvers::DesignManagement::DesignsResolver do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/graphql/resolvers/design_management/version/design_at_version_resolver_spec.rb b/spec/graphql/resolvers/design_management/version/design_at_version_resolver_spec.rb
index cc9c0436885..850b9f8cc87 100644
--- a/spec/graphql/resolvers/design_management/version/design_at_version_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/version/design_at_version_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::DesignManagement::Version::DesignAtVersionResolver do
+RSpec.describe Resolvers::DesignManagement::Version::DesignAtVersionResolver do
include GraphqlHelpers
include_context 'four designs in three versions'
diff --git a/spec/graphql/resolvers/design_management/version/designs_at_version_resolver_spec.rb b/spec/graphql/resolvers/design_management/version/designs_at_version_resolver_spec.rb
index 123b26862d0..c038216ce0b 100644
--- a/spec/graphql/resolvers/design_management/version/designs_at_version_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/version/designs_at_version_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::DesignManagement::Version::DesignsAtVersionResolver do
+RSpec.describe Resolvers::DesignManagement::Version::DesignsAtVersionResolver do
include GraphqlHelpers
include_context 'four designs in three versions'
diff --git a/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb b/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb
index ef50598d241..8ad928e9854 100644
--- a/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::DesignManagement::VersionInCollectionResolver do
+RSpec.describe Resolvers::DesignManagement::VersionInCollectionResolver do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/graphql/resolvers/design_management/version_resolver_spec.rb b/spec/graphql/resolvers/design_management/version_resolver_spec.rb
index e7c09351204..af1e6a73d09 100644
--- a/spec/graphql/resolvers/design_management/version_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/version_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::DesignManagement::VersionResolver do
+RSpec.describe Resolvers::DesignManagement::VersionResolver do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/graphql/resolvers/design_management/versions_resolver_spec.rb b/spec/graphql/resolvers/design_management/versions_resolver_spec.rb
index d5bab025e45..5bc1c555e9a 100644
--- a/spec/graphql/resolvers/design_management/versions_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/versions_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::DesignManagement::VersionsResolver do
+RSpec.describe Resolvers::DesignManagement::VersionsResolver do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/graphql/resolvers/echo_resolver_spec.rb b/spec/graphql/resolvers/echo_resolver_spec.rb
index 466501a4227..2182ac221f6 100644
--- a/spec/graphql/resolvers/echo_resolver_spec.rb
+++ b/spec/graphql/resolvers/echo_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::EchoResolver do
+RSpec.describe Resolvers::EchoResolver do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/graphql/resolvers/environments_resolver_spec.rb b/spec/graphql/resolvers/environments_resolver_spec.rb
index 75fd7aff39c..6c999e5d0e7 100644
--- a/spec/graphql/resolvers/environments_resolver_spec.rb
+++ b/spec/graphql/resolvers/environments_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::EnvironmentsResolver do
+RSpec.describe Resolvers::EnvironmentsResolver do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb b/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb
index 8b2e33cdfda..7e531910184 100644
--- a/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb
+++ b/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::ErrorTracking::SentryDetailedErrorResolver do
+RSpec.describe Resolvers::ErrorTracking::SentryDetailedErrorResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb b/spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb
index 3bb8a5c389d..02e0420be2a 100644
--- a/spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb
+++ b/spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::ErrorTracking::SentryErrorCollectionResolver do
+RSpec.describe Resolvers::ErrorTracking::SentryErrorCollectionResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb b/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb
index 93f89d077d7..554873a6e21 100644
--- a/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb
+++ b/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::ErrorTracking::SentryErrorsResolver do
+RSpec.describe Resolvers::ErrorTracking::SentryErrorsResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/graphql/resolvers/group_resolver_spec.rb b/spec/graphql/resolvers/group_resolver_spec.rb
index 70b1102d363..a03e7854177 100644
--- a/spec/graphql/resolvers/group_resolver_spec.rb
+++ b/spec/graphql/resolvers/group_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::GroupResolver do
+RSpec.describe Resolvers::GroupResolver do
include GraphqlHelpers
let_it_be(:group1) { create(:group) }
diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb
index b7cc9bc6d71..eb17e94a450 100644
--- a/spec/graphql/resolvers/issues_resolver_spec.rb
+++ b/spec/graphql/resolvers/issues_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::IssuesResolver do
+RSpec.describe Resolvers::IssuesResolver do
include GraphqlHelpers
let(:current_user) { create(:user) }
@@ -101,12 +101,10 @@ describe Resolvers::IssuesResolver do
end
it 'uses project search optimization' do
- expected_arguments = {
+ expected_arguments = a_hash_including(
search: 'foo',
- attempt_project_search_optimizations: true,
- iids: [],
- project_id: project.id
- }
+ attempt_project_search_optimizations: true
+ )
expect(IssuesFinder).to receive(:new).with(anything, expected_arguments).and_call_original
resolve_issues(search: 'foo')
@@ -217,16 +215,32 @@ describe Resolvers::IssuesResolver do
expect(resolve_issues).to contain_exactly(issue1, issue2)
end
- it 'finds a specific issue with iid' do
- expect(resolve_issues(iid: issue1.iid)).to contain_exactly(issue1)
+ it 'finds a specific issue with iid', :request_store do
+ result = batch_sync(max_queries: 2) { resolve_issues(iid: issue1.iid) }
+
+ expect(result).to contain_exactly(issue1)
+ end
+
+ it 'batches queries that only include IIDs', :request_store do
+ result = batch_sync(max_queries: 2) do
+ resolve_issues(iid: issue1.iid) + resolve_issues(iids: issue2.iid)
+ end
+
+ expect(result).to contain_exactly(issue1, issue2)
end
- it 'finds a specific issue with iids' do
- expect(resolve_issues(iids: issue1.iid)).to contain_exactly(issue1)
+ it 'finds a specific issue with iids', :request_store do
+ result = batch_sync(max_queries: 2) do
+ resolve_issues(iids: [issue1.iid])
+ end
+
+ expect(result).to contain_exactly(issue1)
end
it 'finds multiple issues with iids' do
- expect(resolve_issues(iids: [issue1.iid, issue2.iid]))
+ create(:issue, project: project, author: current_user)
+
+ expect(batch_sync { resolve_issues(iids: [issue1.iid, issue2.iid]) })
.to contain_exactly(issue1, issue2)
end
@@ -238,7 +252,7 @@ describe Resolvers::IssuesResolver do
create(:issue, project: another_project, iid: iid)
end
- expect(resolve_issues(iids: iids)).to contain_exactly(issue1, issue2)
+ expect(batch_sync { resolve_issues(iids: iids) }).to contain_exactly(issue1, issue2)
end
end
end
diff --git a/spec/graphql/resolvers/last_commit_resolver_spec.rb b/spec/graphql/resolvers/last_commit_resolver_spec.rb
index 15b09b77a10..5ac6ad59864 100644
--- a/spec/graphql/resolvers/last_commit_resolver_spec.rb
+++ b/spec/graphql/resolvers/last_commit_resolver_spec.rb
@@ -2,10 +2,12 @@
require 'spec_helper'
-describe Resolvers::LastCommitResolver do
+RSpec.describe Resolvers::LastCommitResolver do
include GraphqlHelpers
+ include RepoHelpers
- let(:repository) { create(:project, :repository).repository }
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
let(:tree) { repository.tree(ref, path) }
let(:commit) { resolve(described_class, obj: tree) }
@@ -29,6 +31,28 @@ describe Resolvers::LastCommitResolver do
end
end
+ context 'last commit for a wildcard pathspec' do
+ let(:ref) { 'fix' }
+ let(:path) { 'files/*' }
+
+ it 'returns nil' do
+ expect(commit).to be_nil
+ end
+ end
+
+ context 'last commit with pathspec characters' do
+ let(:ref) { 'fix' }
+ let(:path) { ':wq' }
+
+ before do
+ create_file_in_repo(project, ref, ref, path, 'Test file')
+ end
+
+ it 'resolves commit' do
+ expect(commit).to eq(repository.commits(ref, path: path, limit: 1).last)
+ end
+ end
+
context 'last commit does not exist' do
let(:ref) { 'master' }
let(:path) { 'does-not-exist' }
diff --git a/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb b/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb
index b894dce3e17..2fe3e86ec14 100644
--- a/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb
+++ b/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::MergeRequestPipelinesResolver do
+RSpec.describe Resolvers::MergeRequestPipelinesResolver do
include GraphqlHelpers
let_it_be(:merge_request) { create(:merge_request) }
diff --git a/spec/graphql/resolvers/merge_requests_resolver_spec.rb b/spec/graphql/resolvers/merge_requests_resolver_spec.rb
index 6ff7e1ecac6..0a8fd82613a 100644
--- a/spec/graphql/resolvers/merge_requests_resolver_spec.rb
+++ b/spec/graphql/resolvers/merge_requests_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::MergeRequestsResolver do
+RSpec.describe Resolvers::MergeRequestsResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
@@ -22,9 +22,12 @@ describe Resolvers::MergeRequestsResolver do
before do
project.add_developer(current_user)
+ other_project.add_developer(current_user)
end
describe '#resolve' do
+ let(:queries_per_project) { 3 }
+
context 'no arguments' do
it 'returns all merge requests' do
result = resolve_mr(project, {})
@@ -40,24 +43,34 @@ describe Resolvers::MergeRequestsResolver do
end
context 'by iid alone' do
- it 'batch-resolves by target project full path and individual IID' do
- result = batch_sync(max_queries: 2) do
+ it 'batch-resolves by target project full path and individual IID', :request_store do
+ # 1 query for project_authorizations, and 1 for merge_requests
+ result = batch_sync(max_queries: queries_per_project) do
[iid_1, iid_2].map { |iid| resolve_mr_single(project, iid) }
end
expect(result).to contain_exactly(merge_request_1, merge_request_2)
end
- it 'batch-resolves by target project full path and IIDS' do
- result = batch_sync(max_queries: 2) do
+ it 'batch-resolves by target project full path and IIDS', :request_store do
+ result = batch_sync(max_queries: queries_per_project) do
resolve_mr(project, iids: [iid_1, iid_2])
end
expect(result).to contain_exactly(merge_request_1, merge_request_2)
end
+ it 'batch-resolves by target project full path and IIDS, single or plural', :request_store do
+ result = batch_sync(max_queries: queries_per_project) do
+ [resolve_mr_single(project, merge_request_3.iid), *resolve_mr(project, iids: [iid_1, iid_2])]
+ end
+
+ expect(result).to contain_exactly(merge_request_1, merge_request_2, merge_request_3)
+ end
+
it 'can batch-resolve merge requests from different projects' do
- result = batch_sync(max_queries: 3) do
+ # 2 queries for project_authorizations, and 2 for merge_requests
+ result = batch_sync(max_queries: queries_per_project * 2) do
resolve_mr(project, iids: iid_1) +
resolve_mr(project, iids: iid_2) +
resolve_mr(other_project, iids: other_iid)
diff --git a/spec/graphql/resolvers/metadata_resolver_spec.rb b/spec/graphql/resolvers/metadata_resolver_spec.rb
index afff9eabb97..20556941de4 100644
--- a/spec/graphql/resolvers/metadata_resolver_spec.rb
+++ b/spec/graphql/resolvers/metadata_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::MetadataResolver do
+RSpec.describe Resolvers::MetadataResolver do
include GraphqlHelpers
describe '#resolve' do
diff --git a/spec/graphql/resolvers/metrics/dashboard_resolver_spec.rb b/spec/graphql/resolvers/metrics/dashboard_resolver_spec.rb
index 6a8eb8a65af..4112e3d4fe6 100644
--- a/spec/graphql/resolvers/metrics/dashboard_resolver_spec.rb
+++ b/spec/graphql/resolvers/metrics/dashboard_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::Metrics::DashboardResolver do
+RSpec.describe Resolvers::Metrics::DashboardResolver do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/graphql/resolvers/metrics/dashboards/annotation_resolver_spec.rb b/spec/graphql/resolvers/metrics/dashboards/annotation_resolver_spec.rb
index c06fbef53b6..f90869c52bc 100644
--- a/spec/graphql/resolvers/metrics/dashboards/annotation_resolver_spec.rb
+++ b/spec/graphql/resolvers/metrics/dashboards/annotation_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::Metrics::Dashboards::AnnotationResolver do
+RSpec.describe Resolvers::Metrics::Dashboards::AnnotationResolver do
include GraphqlHelpers
describe '#resolve' do
diff --git a/spec/graphql/resolvers/milestone_resolver_spec.rb b/spec/graphql/resolvers/milestone_resolver_spec.rb
index 8e2c67fdc03..36dd5ef03e2 100644
--- a/spec/graphql/resolvers/milestone_resolver_spec.rb
+++ b/spec/graphql/resolvers/milestone_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::MilestoneResolver do
+RSpec.describe Resolvers::MilestoneResolver do
include GraphqlHelpers
describe '#resolve' do
diff --git a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
index 639cc69650b..699269b47e0 100644
--- a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
+++ b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::NamespaceProjectsResolver do
+RSpec.describe Resolvers::NamespaceProjectsResolver do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/graphql/resolvers/packages_resolver_spec.rb b/spec/graphql/resolvers/packages_resolver_spec.rb
new file mode 100644
index 00000000000..9aec2c7e036
--- /dev/null
+++ b/spec/graphql/resolvers/packages_resolver_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::PackagesResolver do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:package) { create(:package, project: project) }
+
+ describe '#resolve' do
+ subject(:packages) { resolve(described_class, ctx: { current_user: user }, obj: project) }
+
+ it { is_expected.to contain_exactly(package) }
+ end
+end
diff --git a/spec/graphql/resolvers/project_members_resolver_spec.rb b/spec/graphql/resolvers/project_members_resolver_spec.rb
index 3209838850b..602225cf632 100644
--- a/spec/graphql/resolvers/project_members_resolver_spec.rb
+++ b/spec/graphql/resolvers/project_members_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::ProjectMembersResolver do
+RSpec.describe Resolvers::ProjectMembersResolver do
include GraphqlHelpers
context "with a group" do
diff --git a/spec/graphql/resolvers/project_pipeline_resolver_spec.rb b/spec/graphql/resolvers/project_pipeline_resolver_spec.rb
index 72049f16d7d..a659b3bdb6e 100644
--- a/spec/graphql/resolvers/project_pipeline_resolver_spec.rb
+++ b/spec/graphql/resolvers/project_pipeline_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::ProjectPipelineResolver do
+RSpec.describe Resolvers::ProjectPipelineResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/graphql/resolvers/project_pipelines_resolver_spec.rb b/spec/graphql/resolvers/project_pipelines_resolver_spec.rb
index 2a14796fdfa..b2e8fed2441 100644
--- a/spec/graphql/resolvers/project_pipelines_resolver_spec.rb
+++ b/spec/graphql/resolvers/project_pipelines_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::ProjectPipelinesResolver do
+RSpec.describe Resolvers::ProjectPipelinesResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/graphql/resolvers/project_resolver_spec.rb b/spec/graphql/resolvers/project_resolver_spec.rb
index e9e38353156..72a01b1c574 100644
--- a/spec/graphql/resolvers/project_resolver_spec.rb
+++ b/spec/graphql/resolvers/project_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::ProjectResolver do
+RSpec.describe Resolvers::ProjectResolver do
include GraphqlHelpers
let_it_be(:project1) { create(:project) }
diff --git a/spec/graphql/resolvers/projects/grafana_integration_resolver_spec.rb b/spec/graphql/resolvers/projects/grafana_integration_resolver_spec.rb
index 416a90a841f..854e763fbdd 100644
--- a/spec/graphql/resolvers/projects/grafana_integration_resolver_spec.rb
+++ b/spec/graphql/resolvers/projects/grafana_integration_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::Projects::GrafanaIntegrationResolver do
+RSpec.describe Resolvers::Projects::GrafanaIntegrationResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb b/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb
index 9811075a613..0775c1c31d1 100644
--- a/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb
+++ b/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::Projects::JiraImportsResolver do
+RSpec.describe Resolvers::Projects::JiraImportsResolver do
include GraphqlHelpers
describe '#resolve' do
diff --git a/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb b/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb
index 364e2aa6ca8..4038bcb3e5d 100644
--- a/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb
+++ b/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::Projects::JiraProjectsResolver do
+RSpec.describe Resolvers::Projects::JiraProjectsResolver do
include GraphqlHelpers
describe '#resolve' do
@@ -63,7 +63,7 @@ describe Resolvers::Projects::JiraProjectsResolver do
context 'when Jira connection is not valid' do
before do
- WebMock.stub_request(:get, 'https://jira.example.com/rest/api/2/project/search?maxResults=50&query=&startAt=0')
+ WebMock.stub_request(:get, 'https://jira.example.com/rest/api/2/project')
.to_raise(JIRA::HTTPError.new(double(message: 'Some failure.')))
end
diff --git a/spec/graphql/resolvers/projects/services_resolver_spec.rb b/spec/graphql/resolvers/projects/services_resolver_spec.rb
index 00045442ea0..8b6eff9e8b6 100644
--- a/spec/graphql/resolvers/projects/services_resolver_spec.rb
+++ b/spec/graphql/resolvers/projects/services_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::Projects::ServicesResolver do
+RSpec.describe Resolvers::Projects::ServicesResolver do
include GraphqlHelpers
describe '#resolve' do
diff --git a/spec/graphql/resolvers/projects/snippets_resolver_spec.rb b/spec/graphql/resolvers/projects/snippets_resolver_spec.rb
index 6d301b1c742..b4a5eb8ddb0 100644
--- a/spec/graphql/resolvers/projects/snippets_resolver_spec.rb
+++ b/spec/graphql/resolvers/projects/snippets_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::Projects::SnippetsResolver do
+RSpec.describe Resolvers::Projects::SnippetsResolver do
include GraphqlHelpers
describe '#resolve' do
diff --git a/spec/graphql/resolvers/projects_resolver_spec.rb b/spec/graphql/resolvers/projects_resolver_spec.rb
index 73ff99a2520..db7c9225c84 100644
--- a/spec/graphql/resolvers/projects_resolver_spec.rb
+++ b/spec/graphql/resolvers/projects_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::ProjectsResolver do
+RSpec.describe Resolvers::ProjectsResolver do
include GraphqlHelpers
describe '#resolve' do
diff --git a/spec/graphql/resolvers/release_resolver_spec.rb b/spec/graphql/resolvers/release_resolver_spec.rb
index 71aa4bbb439..666d54fbc3c 100644
--- a/spec/graphql/resolvers/release_resolver_spec.rb
+++ b/spec/graphql/resolvers/release_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::ReleaseResolver do
+RSpec.describe Resolvers::ReleaseResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project, :private) }
diff --git a/spec/graphql/resolvers/releases_resolver_spec.rb b/spec/graphql/resolvers/releases_resolver_spec.rb
index 9de539b417a..ee8b33fc748 100644
--- a/spec/graphql/resolvers/releases_resolver_spec.rb
+++ b/spec/graphql/resolvers/releases_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::ReleasesResolver do
+RSpec.describe Resolvers::ReleasesResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project, :private) }
diff --git a/spec/graphql/resolvers/snippets_resolver_spec.rb b/spec/graphql/resolvers/snippets_resolver_spec.rb
index 89c350020f0..180be8e8624 100644
--- a/spec/graphql/resolvers/snippets_resolver_spec.rb
+++ b/spec/graphql/resolvers/snippets_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::SnippetsResolver do
+RSpec.describe Resolvers::SnippetsResolver do
include GraphqlHelpers
describe '#resolve' do
diff --git a/spec/graphql/resolvers/todo_resolver_spec.rb b/spec/graphql/resolvers/todo_resolver_spec.rb
index 5a09ec40e64..0775cb8dae7 100644
--- a/spec/graphql/resolvers/todo_resolver_spec.rb
+++ b/spec/graphql/resolvers/todo_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::TodoResolver do
+RSpec.describe Resolvers::TodoResolver do
include GraphqlHelpers
describe '#resolve' do
@@ -10,9 +10,9 @@ describe Resolvers::TodoResolver do
let_it_be(:author1) { create(:user) }
let_it_be(:author2) { create(:user) }
- let_it_be(:todo1) { create(:todo, user: current_user, target_type: 'MergeRequest', state: :pending, action: Todo::MENTIONED, author: author1) }
- let_it_be(:todo2) { create(:todo, user: current_user, state: :done, action: Todo::ASSIGNED, author: author2) }
- let_it_be(:todo3) { create(:todo, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1) }
+ let_it_be(:merge_request_todo_pending) { create(:todo, user: current_user, target_type: 'MergeRequest', state: :pending, action: Todo::MENTIONED, author: author1) }
+ let_it_be(:issue_todo_done) { create(:todo, user: current_user, state: :done, action: Todo::ASSIGNED, author: author2) }
+ let_it_be(:issue_todo_pending) { create(:todo, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1) }
it 'calls TodosFinder' do
expect_next_instance_of(TodosFinder) do |finder|
@@ -23,22 +23,30 @@ describe Resolvers::TodoResolver do
end
context 'when using no filter' do
- it 'returns expected todos' do
- expect(resolve_todos).to contain_exactly(todo1, todo3)
+ it 'returns pending todos' do
+ expect(resolve_todos).to contain_exactly(merge_request_todo_pending, issue_todo_pending)
end
end
context 'when using filters' do
it 'returns the todos for multiple states' do
- todos = resolve_todos({ state: [:done, :pending] })
+ todos = resolve_todos(state: [:done, :pending])
- expect(todos).to contain_exactly(todo1, todo2, todo3)
+ expect(todos).to contain_exactly(merge_request_todo_pending, issue_todo_done, issue_todo_pending)
end
- it 'returns the todos for multiple types' do
- todos = resolve_todos({ type: %w[Issue MergeRequest] })
+ it 'returns the todos for multiple filters' do
+ design_todo_pending = create(:todo, target_type: 'DesignManagement::Design', user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1)
- expect(todos).to contain_exactly(todo1, todo3)
+ todos = resolve_todos(type: ['MergeRequest', 'DesignManagement::Design'])
+
+ expect(todos).to contain_exactly(merge_request_todo_pending, design_todo_pending)
+ end
+
+ it 'returns the todos for single filter' do
+ todos = resolve_todos(type: 'MergeRequest')
+
+ expect(todos).to contain_exactly(merge_request_todo_pending)
end
it 'returns the todos for multiple groups' do
@@ -53,7 +61,7 @@ describe Resolvers::TodoResolver do
todo5 = create(:todo, group: group2, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1)
create(:todo, group: group3, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1)
- todos = resolve_todos({ group_id: [group2.id, group1.id] })
+ todos = resolve_todos(group_id: [group2.id, group1.id])
expect(todos).to contain_exactly(todo4, todo5)
end
@@ -61,20 +69,19 @@ describe Resolvers::TodoResolver do
it 'returns the todos for multiple authors' do
author3 = create(:user)
- todo4 = create(:todo, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author2)
create(:todo, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author3)
- todos = resolve_todos({ author_id: [author2.id, author1.id] })
+ todos = resolve_todos(author_id: [author2.id, author1.id])
- expect(todos).to contain_exactly(todo1, todo3, todo4)
+ expect(todos).to contain_exactly(merge_request_todo_pending, issue_todo_pending)
end
it 'returns the todos for multiple actions' do
create(:todo, user: current_user, state: :pending, action: Todo::DIRECTLY_ADDRESSED, author: author1)
- todos = resolve_todos({ action: [Todo::MENTIONED, Todo::ASSIGNED] })
+ todos = resolve_todos(action: [Todo::MENTIONED, Todo::ASSIGNED])
- expect(todos).to contain_exactly(todo1, todo3)
+ expect(todos).to contain_exactly(merge_request_todo_pending, issue_todo_pending)
end
it 'returns the todos for multiple projects' do
@@ -86,7 +93,7 @@ describe Resolvers::TodoResolver do
todo5 = create(:todo, project: project2, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1)
create(:todo, project: project3, user: current_user, state: :pending, action: Todo::ASSIGNED, author: author1)
- todos = resolve_todos({ project_id: [project2.id, project1.id] })
+ todos = resolve_todos(project_id: [project2.id, project1.id])
expect(todos).to contain_exactly(todo4, todo5)
end
diff --git a/spec/graphql/resolvers/tree_resolver_spec.rb b/spec/graphql/resolvers/tree_resolver_spec.rb
index 0ea4e6eeaad..7818c25fe47 100644
--- a/spec/graphql/resolvers/tree_resolver_spec.rb
+++ b/spec/graphql/resolvers/tree_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::TreeResolver do
+RSpec.describe Resolvers::TreeResolver do
include GraphqlHelpers
let(:repository) { create(:project, :repository).repository }
diff --git a/spec/graphql/resolvers/user_resolver_spec.rb b/spec/graphql/resolvers/user_resolver_spec.rb
index 45a8816bf26..3ee9f63d832 100644
--- a/spec/graphql/resolvers/user_resolver_spec.rb
+++ b/spec/graphql/resolvers/user_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::UserResolver do
+RSpec.describe Resolvers::UserResolver do
include GraphqlHelpers
describe '#resolve' do
diff --git a/spec/graphql/resolvers/users/snippets_resolver_spec.rb b/spec/graphql/resolvers/users/snippets_resolver_spec.rb
index 6412d77e02b..497b6b11b46 100644
--- a/spec/graphql/resolvers/users/snippets_resolver_spec.rb
+++ b/spec/graphql/resolvers/users/snippets_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::Users::SnippetsResolver do
+RSpec.describe Resolvers::Users::SnippetsResolver do
include GraphqlHelpers
describe '#resolve' do
diff --git a/spec/graphql/resolvers/users_resolver_spec.rb b/spec/graphql/resolvers/users_resolver_spec.rb
index e752500d52f..e3d595e0790 100644
--- a/spec/graphql/resolvers/users_resolver_spec.rb
+++ b/spec/graphql/resolvers/users_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::UsersResolver do
+RSpec.describe Resolvers::UsersResolver do
include GraphqlHelpers
let_it_be(:user1) { create(:user) }
diff --git a/spec/graphql/types/access_level_enum_spec.rb b/spec/graphql/types/access_level_enum_spec.rb
index 05a6d6d5545..eeb10a50b7e 100644
--- a/spec/graphql/types/access_level_enum_spec.rb
+++ b/spec/graphql/types/access_level_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['AccessLevelEnum'] do
+RSpec.describe GitlabSchema.types['AccessLevelEnum'] do
specify { expect(described_class.graphql_name).to eq('AccessLevelEnum') }
it 'exposes all the existing access levels' do
diff --git a/spec/graphql/types/access_level_type_spec.rb b/spec/graphql/types/access_level_type_spec.rb
index b9711a9aa4b..25707ae160d 100644
--- a/spec/graphql/types/access_level_type_spec.rb
+++ b/spec/graphql/types/access_level_type_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe GitlabSchema.types['AccessLevel'] do
+RSpec.describe GitlabSchema.types['AccessLevel'] do
specify { expect(described_class.graphql_name).to eq('AccessLevel') }
specify { expect(described_class).to require_graphql_authorizations(nil) }
diff --git a/spec/graphql/types/alert_management/alert_status_count_type_spec.rb b/spec/graphql/types/alert_management/alert_status_count_type_spec.rb
index 1c56028425e..6372d5dd915 100644
--- a/spec/graphql/types/alert_management/alert_status_count_type_spec.rb
+++ b/spec/graphql/types/alert_management/alert_status_count_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['AlertManagementAlertStatusCountsType'] do
+RSpec.describe GitlabSchema.types['AlertManagementAlertStatusCountsType'] do
specify { expect(described_class.graphql_name).to eq('AlertManagementAlertStatusCountsType') }
it 'exposes the expected fields' do
diff --git a/spec/graphql/types/alert_management/alert_type_spec.rb b/spec/graphql/types/alert_management/alert_type_spec.rb
index 5acbf8ebb7a..45ac673986d 100644
--- a/spec/graphql/types/alert_management/alert_type_spec.rb
+++ b/spec/graphql/types/alert_management/alert_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['AlertManagementAlert'] do
+RSpec.describe GitlabSchema.types['AlertManagementAlert'] do
specify { expect(described_class.graphql_name).to eq('AlertManagementAlert') }
specify { expect(described_class).to require_graphql_authorizations(:read_alert_management_alert) }
@@ -27,6 +27,7 @@ describe GitlabSchema.types['AlertManagementAlert'] do
assignees
notes
discussions
+ metrics_dashboard_url
]
expect(described_class).to have_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/alert_management/severity_enum_spec.rb b/spec/graphql/types/alert_management/severity_enum_spec.rb
index ca5aa826fe5..6c3c962b71d 100644
--- a/spec/graphql/types/alert_management/severity_enum_spec.rb
+++ b/spec/graphql/types/alert_management/severity_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['AlertManagementSeverity'] do
+RSpec.describe GitlabSchema.types['AlertManagementSeverity'] do
specify { expect(described_class.graphql_name).to eq('AlertManagementSeverity') }
it 'exposes all the severity values' do
diff --git a/spec/graphql/types/alert_management/status_enum_spec.rb b/spec/graphql/types/alert_management/status_enum_spec.rb
index 240d8863c97..ac7a8eb53f6 100644
--- a/spec/graphql/types/alert_management/status_enum_spec.rb
+++ b/spec/graphql/types/alert_management/status_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['AlertManagementStatus'] do
+RSpec.describe GitlabSchema.types['AlertManagementStatus'] do
specify { expect(described_class.graphql_name).to eq('AlertManagementStatus') }
describe 'statuses' do
diff --git a/spec/graphql/types/award_emojis/award_emoji_type_spec.rb b/spec/graphql/types/award_emojis/award_emoji_type_spec.rb
index 4e06329506d..3c43c5f8e42 100644
--- a/spec/graphql/types/award_emojis/award_emoji_type_spec.rb
+++ b/spec/graphql/types/award_emojis/award_emoji_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['AwardEmoji'] do
+RSpec.describe GitlabSchema.types['AwardEmoji'] do
specify { expect(described_class.graphql_name).to eq('AwardEmoji') }
specify { expect(described_class).to require_graphql_authorizations(:read_emoji) }
diff --git a/spec/graphql/types/base_enum_spec.rb b/spec/graphql/types/base_enum_spec.rb
index 3eadb492cf5..0d0f6346f2d 100644
--- a/spec/graphql/types/base_enum_spec.rb
+++ b/spec/graphql/types/base_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::BaseEnum do
+RSpec.describe Types::BaseEnum do
describe '#enum' do
let(:enum) do
Class.new(described_class) do
diff --git a/spec/graphql/types/base_field_spec.rb b/spec/graphql/types/base_field_spec.rb
index 3ec33c75803..73bb54e7ad0 100644
--- a/spec/graphql/types/base_field_spec.rb
+++ b/spec/graphql/types/base_field_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::BaseField do
+RSpec.describe Types::BaseField do
context 'when considering complexity' do
let(:resolver) do
Class.new(described_class) do
diff --git a/spec/graphql/types/blob_viewers/type_enum_spec.rb b/spec/graphql/types/blob_viewers/type_enum_spec.rb
index 09664382af9..57f052a4fd8 100644
--- a/spec/graphql/types/blob_viewers/type_enum_spec.rb
+++ b/spec/graphql/types/blob_viewers/type_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::BlobViewers::TypeEnum do
+RSpec.describe Types::BlobViewers::TypeEnum do
specify { expect(described_class.graphql_name).to eq('BlobViewersType') }
it 'exposes all tree entry types' do
diff --git a/spec/graphql/types/board_list_type_spec.rb b/spec/graphql/types/board_list_type_spec.rb
index 69597fc9617..046d1e92bfa 100644
--- a/spec/graphql/types/board_list_type_spec.rb
+++ b/spec/graphql/types/board_list_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['BoardList'] do
+RSpec.describe GitlabSchema.types['BoardList'] do
specify { expect(described_class.graphql_name).to eq('BoardList') }
it 'has specific fields' do
diff --git a/spec/graphql/types/board_type_spec.rb b/spec/graphql/types/board_type_spec.rb
index 5d87a1757b5..b02b342390d 100644
--- a/spec/graphql/types/board_type_spec.rb
+++ b/spec/graphql/types/board_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Board'] do
+RSpec.describe GitlabSchema.types['Board'] do
specify { expect(described_class.graphql_name).to eq('Board') }
specify { expect(described_class).to require_graphql_authorizations(:read_board) }
diff --git a/spec/graphql/types/branch_type_spec.rb b/spec/graphql/types/branch_type_spec.rb
index f58b514116d..a5838739a80 100644
--- a/spec/graphql/types/branch_type_spec.rb
+++ b/spec/graphql/types/branch_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Branch'] do
+RSpec.describe GitlabSchema.types['Branch'] do
it { expect(described_class.graphql_name).to eq('Branch') }
it { expect(described_class).to have_graphql_fields(:name, :commit) }
diff --git a/spec/graphql/types/ci/detailed_status_type_spec.rb b/spec/graphql/types/ci/detailed_status_type_spec.rb
index c62c8f23728..67199848df0 100644
--- a/spec/graphql/types/ci/detailed_status_type_spec.rb
+++ b/spec/graphql/types/ci/detailed_status_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::Ci::DetailedStatusType do
+RSpec.describe Types::Ci::DetailedStatusType do
specify { expect(described_class.graphql_name).to eq('DetailedStatus') }
it "has all fields" do
diff --git a/spec/graphql/types/ci/pipeline_type_spec.rb b/spec/graphql/types/ci/pipeline_type_spec.rb
index d56cff12105..f13f1c9afb2 100644
--- a/spec/graphql/types/ci/pipeline_type_spec.rb
+++ b/spec/graphql/types/ci/pipeline_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::Ci::PipelineType do
+RSpec.describe Types::Ci::PipelineType do
specify { expect(described_class.graphql_name).to eq('Pipeline') }
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Ci::Pipeline) }
diff --git a/spec/graphql/types/ci_configuration/sast/analyzers_entity_type_spec.rb b/spec/graphql/types/ci_configuration/sast/analyzers_entity_type_spec.rb
new file mode 100644
index 00000000000..34a22feeaf5
--- /dev/null
+++ b/spec/graphql/types/ci_configuration/sast/analyzers_entity_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['SastCiConfigurationAnalyzersEntity'] do
+ let(:fields) { %i[name label enabled description] }
+
+ it { expect(described_class.graphql_name).to eq('SastCiConfigurationAnalyzersEntity') }
+
+ it { expect(described_class).to have_graphql_fields(fields) }
+end
diff --git a/spec/graphql/types/ci_configuration/sast/entity_type_spec.rb b/spec/graphql/types/ci_configuration/sast/entity_type_spec.rb
new file mode 100644
index 00000000000..7c6ad013d4a
--- /dev/null
+++ b/spec/graphql/types/ci_configuration/sast/entity_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['SastCiConfigurationEntity'] do
+ let(:fields) { %i[field label description type options default_value value] }
+
+ it { expect(described_class.graphql_name).to eq('SastCiConfigurationEntity') }
+
+ it { expect(described_class).to have_graphql_fields(fields) }
+end
diff --git a/spec/graphql/types/ci_configuration/sast/options_entity_spec.rb b/spec/graphql/types/ci_configuration/sast/options_entity_spec.rb
new file mode 100644
index 00000000000..c60c8b9c84a
--- /dev/null
+++ b/spec/graphql/types/ci_configuration/sast/options_entity_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['SastCiConfigurationOptionsEntity'] do
+ let(:fields) { %i[label value] }
+
+ it { expect(described_class.graphql_name).to eq('SastCiConfigurationOptionsEntity') }
+
+ it { expect(described_class).to have_graphql_fields(fields) }
+end
diff --git a/spec/graphql/types/ci_configuration/sast/type_spec.rb b/spec/graphql/types/ci_configuration/sast/type_spec.rb
new file mode 100644
index 00000000000..e7a8cd436e4
--- /dev/null
+++ b/spec/graphql/types/ci_configuration/sast/type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['SastCiConfiguration'] do
+ let(:fields) { %i[global pipeline analyzers] }
+
+ it { expect(described_class.graphql_name).to eq('SastCiConfiguration') }
+
+ it { expect(described_class).to have_graphql_fields(fields) }
+end
diff --git a/spec/graphql/types/commit_action_mode_enum_spec.rb b/spec/graphql/types/commit_action_mode_enum_spec.rb
index 9e1a27ea254..cedd466e292 100644
--- a/spec/graphql/types/commit_action_mode_enum_spec.rb
+++ b/spec/graphql/types/commit_action_mode_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['CommitActionMode'] do
+RSpec.describe GitlabSchema.types['CommitActionMode'] do
it { expect(described_class.graphql_name).to eq('CommitActionMode') }
it 'exposes all the existing commit actions' do
diff --git a/spec/graphql/types/commit_encoding_enum_spec.rb b/spec/graphql/types/commit_encoding_enum_spec.rb
index 30686a0c712..7acfb73c06d 100644
--- a/spec/graphql/types/commit_encoding_enum_spec.rb
+++ b/spec/graphql/types/commit_encoding_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['CommitEncoding'] do
+RSpec.describe GitlabSchema.types['CommitEncoding'] do
it { expect(described_class.graphql_name).to eq('CommitEncoding') }
it 'exposes all the existing encoding option' do
diff --git a/spec/graphql/types/commit_type_spec.rb b/spec/graphql/types/commit_type_spec.rb
index 88b450e3924..75984786972 100644
--- a/spec/graphql/types/commit_type_spec.rb
+++ b/spec/graphql/types/commit_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Commit'] do
+RSpec.describe GitlabSchema.types['Commit'] do
specify { expect(described_class.graphql_name).to eq('Commit') }
specify { expect(described_class).to require_graphql_authorizations(:download_code) }
diff --git a/spec/graphql/types/container_expiration_policy_cadence_enum_spec.rb b/spec/graphql/types/container_expiration_policy_cadence_enum_spec.rb
index 08c777cd365..9bd1fe3ea76 100644
--- a/spec/graphql/types/container_expiration_policy_cadence_enum_spec.rb
+++ b/spec/graphql/types/container_expiration_policy_cadence_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['ContainerExpirationPolicyCadenceEnum'] do
+RSpec.describe GitlabSchema.types['ContainerExpirationPolicyCadenceEnum'] do
let_it_be(:expected_values) { %w[EVERY_DAY EVERY_WEEK EVERY_TWO_WEEKS EVERY_MONTH EVERY_THREE_MONTHS] }
it_behaves_like 'exposing container expiration policy option', :cadence
diff --git a/spec/graphql/types/container_expiration_policy_keep_enum_spec.rb b/spec/graphql/types/container_expiration_policy_keep_enum_spec.rb
index 1a5b4bdd3bb..35385545fe3 100644
--- a/spec/graphql/types/container_expiration_policy_keep_enum_spec.rb
+++ b/spec/graphql/types/container_expiration_policy_keep_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['ContainerExpirationPolicyKeepEnum'] do
+RSpec.describe GitlabSchema.types['ContainerExpirationPolicyKeepEnum'] do
let_it_be(:expected_values) { %w[ONE_TAG FIVE_TAGS TEN_TAGS TWENTY_FIVE_TAGS FIFTY_TAGS ONE_HUNDRED_TAGS] }
it_behaves_like 'exposing container expiration policy option', :keep_n
diff --git a/spec/graphql/types/container_expiration_policy_older_than_enum_spec.rb b/spec/graphql/types/container_expiration_policy_older_than_enum_spec.rb
index 47f0ca22522..72ab605f2e6 100644
--- a/spec/graphql/types/container_expiration_policy_older_than_enum_spec.rb
+++ b/spec/graphql/types/container_expiration_policy_older_than_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['ContainerExpirationPolicyOlderThanEnum'] do
+RSpec.describe GitlabSchema.types['ContainerExpirationPolicyOlderThanEnum'] do
let_it_be(:expected_values) { %w[SEVEN_DAYS FOURTEEN_DAYS THIRTY_DAYS NINETY_DAYS] }
it_behaves_like 'exposing container expiration policy option', :older_than
diff --git a/spec/graphql/types/container_expiration_policy_type_spec.rb b/spec/graphql/types/container_expiration_policy_type_spec.rb
index 8924ab67847..9e9ddaf1cb0 100644
--- a/spec/graphql/types/container_expiration_policy_type_spec.rb
+++ b/spec/graphql/types/container_expiration_policy_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['ContainerExpirationPolicy'] do
+RSpec.describe GitlabSchema.types['ContainerExpirationPolicy'] do
specify { expect(described_class.graphql_name).to eq('ContainerExpirationPolicy') }
specify { expect(described_class.description).to eq('A tag expiration policy designed to keep only the images that matter most') }
@@ -24,4 +24,20 @@ describe GitlabSchema.types['ContainerExpirationPolicy'] do
is_expected.to have_graphql_type(Types::ContainerExpirationPolicyKeepEnum)
end
end
+
+ describe 'name_regex field' do
+ subject { described_class.fields['nameRegex'] }
+
+ it 'returns untrusted regexp type' do
+ is_expected.to have_graphql_type(Types::UntrustedRegexp)
+ end
+ end
+
+ describe 'name_regex_keep field' do
+ subject { described_class.fields['nameRegexKeep'] }
+
+ it 'returns untrusted regexp type' do
+ is_expected.to have_graphql_type(Types::UntrustedRegexp)
+ end
+ end
end
diff --git a/spec/graphql/types/design_management/design_at_version_type_spec.rb b/spec/graphql/types/design_management/design_at_version_type_spec.rb
index 1453d73d59c..5a6292c924a 100644
--- a/spec/graphql/types/design_management/design_at_version_type_spec.rb
+++ b/spec/graphql/types/design_management/design_at_version_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['DesignAtVersion'] do
+RSpec.describe GitlabSchema.types['DesignAtVersion'] do
it_behaves_like 'a GraphQL type with design fields' do
let(:extra_design_fields) { %i[version design] }
let_it_be(:design) { create(:design, :with_versions) }
diff --git a/spec/graphql/types/design_management/design_collection_type_spec.rb b/spec/graphql/types/design_management/design_collection_type_spec.rb
index 65150f0971d..6b1d3a87c2d 100644
--- a/spec/graphql/types/design_management/design_collection_type_spec.rb
+++ b/spec/graphql/types/design_management/design_collection_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['DesignCollection'] do
+RSpec.describe GitlabSchema.types['DesignCollection'] do
it { expect(described_class).to require_graphql_authorizations(:read_design) }
it 'has the expected fields' do
diff --git a/spec/graphql/types/design_management/design_type_spec.rb b/spec/graphql/types/design_management/design_type_spec.rb
index 75b4cd66d5e..7a38b397965 100644
--- a/spec/graphql/types/design_management/design_type_spec.rb
+++ b/spec/graphql/types/design_management/design_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Design'] do
+RSpec.describe GitlabSchema.types['Design'] do
it_behaves_like 'a GraphQL type with design fields' do
let(:extra_design_fields) { %i[notes discussions versions] }
let_it_be(:design) { create(:design, :with_versions) }
diff --git a/spec/graphql/types/design_management/design_version_event_enum_spec.rb b/spec/graphql/types/design_management/design_version_event_enum_spec.rb
index a65f1bb5990..06576336231 100644
--- a/spec/graphql/types/design_management/design_version_event_enum_spec.rb
+++ b/spec/graphql/types/design_management/design_version_event_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['DesignVersionEvent'] do
+RSpec.describe GitlabSchema.types['DesignVersionEvent'] do
it { expect(described_class.graphql_name).to eq('DesignVersionEvent') }
it 'exposes the correct event states' do
diff --git a/spec/graphql/types/design_management/version_type_spec.rb b/spec/graphql/types/design_management/version_type_spec.rb
index 3317c4c6571..017cc1775a1 100644
--- a/spec/graphql/types/design_management/version_type_spec.rb
+++ b/spec/graphql/types/design_management/version_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['DesignVersion'] do
+RSpec.describe GitlabSchema.types['DesignVersion'] do
it { expect(described_class).to require_graphql_authorizations(:read_design) }
it 'has the expected fields' do
diff --git a/spec/graphql/types/design_management_type_spec.rb b/spec/graphql/types/design_management_type_spec.rb
index a6204f20f23..e9162feec74 100644
--- a/spec/graphql/types/design_management_type_spec.rb
+++ b/spec/graphql/types/design_management_type_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe GitlabSchema.types['DesignManagement'] do
+RSpec.describe GitlabSchema.types['DesignManagement'] do
it { expect(described_class).to have_graphql_fields(:version, :design_at_version) }
end
diff --git a/spec/graphql/types/diff_refs_type_spec.rb b/spec/graphql/types/diff_refs_type_spec.rb
index 3165e642452..894191c55ba 100644
--- a/spec/graphql/types/diff_refs_type_spec.rb
+++ b/spec/graphql/types/diff_refs_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['DiffRefs'] do
+RSpec.describe GitlabSchema.types['DiffRefs'] do
specify { expect(described_class.graphql_name).to eq('DiffRefs') }
specify { expect(described_class).to have_graphql_fields(:head_sha, :base_sha, :start_sha).only }
diff --git a/spec/graphql/types/environment_type_spec.rb b/spec/graphql/types/environment_type_spec.rb
index 0e5cbac05df..f7522cb3e2c 100644
--- a/spec/graphql/types/environment_type_spec.rb
+++ b/spec/graphql/types/environment_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Environment'] do
+RSpec.describe GitlabSchema.types['Environment'] do
specify { expect(described_class.graphql_name).to eq('Environment') }
it 'has the expected fields' do
diff --git a/spec/graphql/types/error_tracking/sentry_detailed_error_type_spec.rb b/spec/graphql/types/error_tracking/sentry_detailed_error_type_spec.rb
index 0a094e9e188..8723c212486 100644
--- a/spec/graphql/types/error_tracking/sentry_detailed_error_type_spec.rb
+++ b/spec/graphql/types/error_tracking/sentry_detailed_error_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['SentryDetailedError'] do
+RSpec.describe GitlabSchema.types['SentryDetailedError'] do
specify { expect(described_class.graphql_name).to eq('SentryDetailedError') }
specify { expect(described_class).to require_graphql_authorizations(:read_sentry_issue) }
@@ -31,6 +31,8 @@ describe GitlabSchema.types['SentryDetailedError'] do
lastReleaseLastCommit
firstReleaseShortVersion
lastReleaseShortVersion
+ firstReleaseVersion
+ lastReleaseVersion
gitlabIssuePath
gitlabCommit
gitlabCommitPath
diff --git a/spec/graphql/types/error_tracking/sentry_error_collection_type_spec.rb b/spec/graphql/types/error_tracking/sentry_error_collection_type_spec.rb
index 793da2db960..3a8ff7a73be 100644
--- a/spec/graphql/types/error_tracking/sentry_error_collection_type_spec.rb
+++ b/spec/graphql/types/error_tracking/sentry_error_collection_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['SentryErrorCollection'] do
+RSpec.describe GitlabSchema.types['SentryErrorCollection'] do
specify { expect(described_class.graphql_name).to eq('SentryErrorCollection') }
specify { expect(described_class).to require_graphql_authorizations(:read_sentry_issue) }
diff --git a/spec/graphql/types/error_tracking/sentry_error_stack_trace_entry_type_spec.rb b/spec/graphql/types/error_tracking/sentry_error_stack_trace_entry_type_spec.rb
index b65398fccc9..54d0d8edbc6 100644
--- a/spec/graphql/types/error_tracking/sentry_error_stack_trace_entry_type_spec.rb
+++ b/spec/graphql/types/error_tracking/sentry_error_stack_trace_entry_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['SentryErrorStackTraceEntry'] do
+RSpec.describe GitlabSchema.types['SentryErrorStackTraceEntry'] do
specify { expect(described_class.graphql_name).to eq('SentryErrorStackTraceEntry') }
it 'exposes the expected fields' do
diff --git a/spec/graphql/types/error_tracking/sentry_error_stack_trace_type_spec.rb b/spec/graphql/types/error_tracking/sentry_error_stack_trace_type_spec.rb
index 2cec8865764..07ddbd83d0a 100644
--- a/spec/graphql/types/error_tracking/sentry_error_stack_trace_type_spec.rb
+++ b/spec/graphql/types/error_tracking/sentry_error_stack_trace_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['SentryErrorStackTrace'] do
+RSpec.describe GitlabSchema.types['SentryErrorStackTrace'] do
specify { expect(described_class.graphql_name).to eq('SentryErrorStackTrace') }
specify { expect(described_class).to require_graphql_authorizations(:read_sentry_issue) }
diff --git a/spec/graphql/types/error_tracking/sentry_error_type_spec.rb b/spec/graphql/types/error_tracking/sentry_error_type_spec.rb
index f8cc801e35e..6cbcc8888c6 100644
--- a/spec/graphql/types/error_tracking/sentry_error_type_spec.rb
+++ b/spec/graphql/types/error_tracking/sentry_error_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['SentryError'] do
+RSpec.describe GitlabSchema.types['SentryError'] do
specify { expect(described_class.graphql_name).to eq('SentryError') }
it 'exposes the expected fields' do
diff --git a/spec/graphql/types/evidence_type_spec.rb b/spec/graphql/types/evidence_type_spec.rb
index 4a11f7bcda9..92134e74d51 100644
--- a/spec/graphql/types/evidence_type_spec.rb
+++ b/spec/graphql/types/evidence_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['ReleaseEvidence'] do
+RSpec.describe GitlabSchema.types['ReleaseEvidence'] do
it { expect(described_class).to require_graphql_authorizations(:download_code) }
it 'has the expected fields' do
diff --git a/spec/graphql/types/global_id_type_spec.rb b/spec/graphql/types/global_id_type_spec.rb
new file mode 100644
index 00000000000..2a7b26f66b0
--- /dev/null
+++ b/spec/graphql/types/global_id_type_spec.rb
@@ -0,0 +1,215 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::GlobalIDType do
+ let_it_be(:project) { create(:project) }
+ let(:gid) { project.to_global_id }
+ let(:foreign_gid) { GlobalID.new(::URI::GID.build(app: 'otherapp', model_name: 'Project', model_id: project.id, params: nil)) }
+
+ it 'is has the correct name' do
+ expect(described_class.to_graphql.name).to eq('GlobalID')
+ end
+
+ describe '.coerce_result' do
+ it 'can coerce results' do
+ expect(described_class.coerce_isolated_result(gid)).to eq(gid.to_s)
+ end
+
+ it 'rejects integer IDs' do
+ expect { described_class.coerce_isolated_result(project.id) }
+ .to raise_error(ArgumentError)
+ end
+
+ it 'rejects strings' do
+ expect { described_class.coerce_isolated_result('not a GID') }
+ .to raise_error(ArgumentError)
+ end
+ end
+
+ describe '.coerce_input' do
+ it 'can coerce valid input' do
+ coerced = described_class.coerce_isolated_input(gid.to_s)
+
+ expect(coerced).to eq(gid)
+ end
+
+ it 'handles all valid application GIDs' do
+ expect { described_class.coerce_isolated_input(build_stubbed(:user).to_global_id.to_s) }
+ .not_to raise_error
+ end
+
+ it 'rejects invalid input' do
+ expect { described_class.coerce_isolated_input('not valid') }
+ .to raise_error(GraphQL::CoercionError)
+ end
+
+ it 'rejects nil' do
+ expect { described_class.coerce_isolated_input(nil) }
+ .to raise_error(GraphQL::CoercionError)
+ end
+
+ it 'rejects gids from different apps' do
+ expect { described_class.coerce_isolated_input(foreign_gid) }
+ .to raise_error(GraphQL::CoercionError)
+ end
+ end
+
+ describe 'a parameterized type' do
+ let(:type) { ::Types::GlobalIDType[::Project] }
+
+ it 'is has the correct name' do
+ expect(type.to_graphql.name).to eq('ProjectID')
+ end
+
+ context 'the GID is appropriate' do
+ it 'can coerce results' do
+ expect(type.coerce_isolated_result(gid)).to eq(gid.to_s)
+ end
+
+ it 'can coerce IDs to a GlobalIDType' do
+ expect(type.coerce_isolated_result(project.id)).to eq(gid.to_s)
+ end
+
+ it 'can coerce valid input' do
+ expect(type.coerce_isolated_input(gid.to_s)).to eq(gid)
+ end
+ end
+
+ context 'the GID is not for an appropriate type' do
+ let(:gid) { build_stubbed(:user).to_global_id }
+
+ it 'raises errors when coercing results' do
+ expect { type.coerce_isolated_result(gid) }.to raise_error(GraphQL::CoercionError)
+ end
+
+ it 'will not coerce invalid input, even if its a valid GID' do
+ expect { type.coerce_isolated_input(gid.to_s) }
+ .to raise_error(GraphQL::CoercionError)
+ end
+ end
+ end
+
+ describe 'a parameterized type with a namespace' do
+ let(:type) { ::Types::GlobalIDType[::Ci::Build] }
+
+ it 'is has a valid GraphQL identifier for a name' do
+ expect(type.to_graphql.name).to eq('CiBuildID')
+ end
+ end
+
+ describe 'compatibility' do
+ # Simplified schema to test compatibility
+
+ def query(doc, vars)
+ GraphQL::Query.new(schema, document: doc, context: {}, variables: vars)
+ end
+
+ def run_query(gql_query, vars)
+ query(GraphQL.parse(gql_query), vars).result
+ end
+
+ all_types = [::GraphQL::ID_TYPE, ::Types::GlobalIDType, ::Types::GlobalIDType[::Project]]
+
+ shared_examples 'a working query' do
+ let!(:schema) do
+ # capture values so they can be closed over
+ arg_type = argument_type
+ res_type = result_type
+
+ project = Class.new(GraphQL::Schema::Object) do
+ graphql_name 'Project'
+ field :name, String, null: false
+ field :id, res_type, null: false, resolver_method: :global_id
+
+ def global_id
+ object.to_global_id
+ end
+ end
+
+ Class.new(GraphQL::Schema) do
+ query(Class.new(GraphQL::Schema::Object) do
+ graphql_name 'Query'
+
+ field :project_by_id, project, null: true do
+ argument :id, arg_type, required: true
+ end
+
+ def project_by_id(id:)
+ gid = ::Types::GlobalIDType[::Project].coerce_isolated_input(id)
+ gid.model_class.find(gid.model_id)
+ end
+ end)
+ end
+ end
+
+ it 'works' do
+ res = run_query(document, 'projectId' => project.to_global_id.to_s)
+
+ expect(res['errors']).to be_blank
+ expect(res.dig('data', 'project', 'name')).to eq(project.name)
+ expect(res.dig('data', 'project', 'id')).to eq(project.to_global_id.to_s)
+ end
+ end
+
+ context 'when the argument is declared as ID' do
+ let(:document) do
+ <<-GRAPHQL
+ query($projectId: ID!){
+ project: projectById(id: $projectId) {
+ name, id
+ }
+ }
+ GRAPHQL
+ end
+
+ let(:argument_type) { ::GraphQL::ID_TYPE }
+
+ where(:result_type) { all_types }
+
+ with_them do
+ it_behaves_like 'a working query'
+ end
+ end
+
+ context 'when the argument is declared as GlobalID' do
+ let(:document) do
+ <<-GRAPHQL
+ query($projectId: GlobalID!) {
+ project: projectById(id: $projectId) {
+ name, id
+ }
+ }
+ GRAPHQL
+ end
+
+ let(:argument_type) { ::Types::GlobalIDType }
+
+ where(:result_type) { all_types }
+
+ with_them do
+ it_behaves_like 'a working query'
+ end
+ end
+
+ context 'when the argument is declared as ProjectID' do
+ let(:document) do
+ <<-GRAPHQL
+ query($projectId: ProjectID!) {
+ project: projectById(id: $projectId) {
+ name, id
+ }
+ }
+ GRAPHQL
+ end
+
+ let(:argument_type) { ::Types::GlobalIDType[::Project] }
+
+ where(:result_type) { all_types }
+
+ with_them do
+ it_behaves_like 'a working query'
+ end
+ end
+ end
+end
diff --git a/spec/graphql/types/grafana_integration_type_spec.rb b/spec/graphql/types/grafana_integration_type_spec.rb
index 429b5bdffe6..b4658db08d7 100644
--- a/spec/graphql/types/grafana_integration_type_spec.rb
+++ b/spec/graphql/types/grafana_integration_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['GrafanaIntegration'] do
+RSpec.describe GitlabSchema.types['GrafanaIntegration'] do
let(:expected_fields) do
%i[
id
diff --git a/spec/graphql/types/group_member_type_spec.rb b/spec/graphql/types/group_member_type_spec.rb
index 5d09e60d21c..b1cb8e572ad 100644
--- a/spec/graphql/types/group_member_type_spec.rb
+++ b/spec/graphql/types/group_member_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::GroupMemberType do
+RSpec.describe Types::GroupMemberType do
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Group) }
specify { expect(described_class.graphql_name).to eq('GroupMember') }
diff --git a/spec/graphql/types/group_type_spec.rb b/spec/graphql/types/group_type_spec.rb
index c56cd40ef12..fb79e9bb85b 100644
--- a/spec/graphql/types/group_type_spec.rb
+++ b/spec/graphql/types/group_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Group'] do
+RSpec.describe GitlabSchema.types['Group'] do
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Group) }
specify { expect(described_class.graphql_name).to eq('Group') }
diff --git a/spec/graphql/types/issuable_sort_enum_spec.rb b/spec/graphql/types/issuable_sort_enum_spec.rb
index 35c42d8194c..e5237256a2b 100644
--- a/spec/graphql/types/issuable_sort_enum_spec.rb
+++ b/spec/graphql/types/issuable_sort_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::IssuableSortEnum do
+RSpec.describe Types::IssuableSortEnum do
specify { expect(described_class.graphql_name).to eq('IssuableSort') }
it 'exposes all the existing issuable sort values' do
diff --git a/spec/graphql/types/issuable_state_enum_spec.rb b/spec/graphql/types/issuable_state_enum_spec.rb
index f974ed5f5fb..a63b8cddb14 100644
--- a/spec/graphql/types/issuable_state_enum_spec.rb
+++ b/spec/graphql/types/issuable_state_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['IssuableState'] do
+RSpec.describe GitlabSchema.types['IssuableState'] do
specify { expect(described_class.graphql_name).to eq('IssuableState') }
it_behaves_like 'issuable state'
diff --git a/spec/graphql/types/issue_connection_type_spec.rb b/spec/graphql/types/issue_connection_type_spec.rb
new file mode 100644
index 00000000000..af34611ecfe
--- /dev/null
+++ b/spec/graphql/types/issue_connection_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['IssueConnection'] do
+ it 'has the expected fields' do
+ expected_fields = %i[count page_info edges nodes]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/issue_sort_enum_spec.rb b/spec/graphql/types/issue_sort_enum_spec.rb
index c496b897cdb..9313d3aee84 100644
--- a/spec/graphql/types/issue_sort_enum_spec.rb
+++ b/spec/graphql/types/issue_sort_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['IssueSort'] do
+RSpec.describe GitlabSchema.types['IssueSort'] do
specify { expect(described_class.graphql_name).to eq('IssueSort') }
it_behaves_like 'common sort values'
diff --git a/spec/graphql/types/issue_state_enum_spec.rb b/spec/graphql/types/issue_state_enum_spec.rb
index a18c5f5d317..a9dd287e196 100644
--- a/spec/graphql/types/issue_state_enum_spec.rb
+++ b/spec/graphql/types/issue_state_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['IssueState'] do
+RSpec.describe GitlabSchema.types['IssueState'] do
specify { expect(described_class.graphql_name).to eq('IssueState') }
it_behaves_like 'issuable state'
diff --git a/spec/graphql/types/issue_type_spec.rb b/spec/graphql/types/issue_type_spec.rb
index a8f7edcfe8e..4a86b07ab1c 100644
--- a/spec/graphql/types/issue_type_spec.rb
+++ b/spec/graphql/types/issue_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Issue'] do
+RSpec.describe GitlabSchema.types['Issue'] do
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Issue) }
specify { expect(described_class.graphql_name).to eq('Issue') }
@@ -12,7 +12,7 @@ describe GitlabSchema.types['Issue'] do
specify { expect(described_class.interfaces).to include(Types::Notes::NoteableType) }
it 'has specific fields' do
- fields = %i[iid title description state reference author assignees participants labels milestone due_date
+ fields = %i[id iid title description state reference author assignees participants labels milestone due_date
confidential discussion_locked upvotes downvotes user_notes_count web_path web_url relative_position
subscribed time_estimate total_time_spent closed_at created_at updated_at task_completion_status
designs design_collection]
@@ -22,6 +22,104 @@ describe GitlabSchema.types['Issue'] do
end
end
+ describe 'pagination and count' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:now) { Time.now.change(usec: 0) }
+ let_it_be(:issues) { create_list(:issue, 10, project: project, created_at: now) }
+
+ let(:count_path) { %w(data project issues count) }
+ let(:page_size) { 3 }
+ let(:query) do
+ <<~GRAPHQL
+ query project($fullPath: ID!, $first: Int, $after: String) {
+ project(fullPath: $fullPath) {
+ issues(first: $first, after: $after) {
+ count
+ edges {
+ node {
+ iid
+ }
+ }
+ pageInfo {
+ endCursor
+ hasNextPage
+ }
+ }
+ }
+ }
+ GRAPHQL
+ end
+
+ subject do
+ GitlabSchema.execute(
+ query,
+ context: { current_user: user },
+ variables: {
+ fullPath: project.full_path,
+ first: page_size
+ }
+ ).to_h
+ end
+
+ context 'when user does not have the permission' do
+ it 'returns no data' do
+ allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(false)
+
+ expect(subject.dig(:data, :project)).to eq(nil)
+ end
+ end
+
+ context 'count' do
+ let(:end_cursor) { %w(data project issues pageInfo endCursor) }
+ let(:issues_edges) { %w(data project issues edges) }
+
+ it 'returns total count' do
+ expect(subject.dig(*count_path)).to eq(issues.count)
+ end
+
+ it 'total count does not change between pages' do
+ old_count = subject.dig(*count_path)
+ new_cursor = subject.dig(*end_cursor)
+
+ new_page = GitlabSchema.execute(
+ query,
+ context: { current_user: user },
+ variables: {
+ fullPath: project.full_path,
+ first: page_size,
+ after: new_cursor
+ }
+ ).to_h
+
+ new_count = new_page.dig(*count_path)
+ expect(old_count).to eq(new_count)
+ end
+
+ context 'pagination' do
+ let(:page_size) { 9 }
+
+ it 'returns new ids during pagination' do
+ old_edges = subject.dig(*issues_edges)
+ new_cursor = subject.dig(*end_cursor)
+
+ new_edges = GitlabSchema.execute(
+ query,
+ context: { current_user: user },
+ variables: {
+ fullPath: project.full_path,
+ first: page_size,
+ after: new_cursor
+ }
+ ).to_h.dig(*issues_edges)
+
+ expect(old_edges.count).to eq(9)
+ expect(new_edges.count).to eq(1)
+ end
+ end
+ end
+ end
+
describe "issue notes" do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
diff --git a/spec/graphql/types/jira_import_type_spec.rb b/spec/graphql/types/jira_import_type_spec.rb
index fa1152aec41..b44525d3304 100644
--- a/spec/graphql/types/jira_import_type_spec.rb
+++ b/spec/graphql/types/jira_import_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['JiraImport'] do
+RSpec.describe GitlabSchema.types['JiraImport'] do
specify { expect(described_class.graphql_name).to eq('JiraImport') }
it 'has the expected fields' do
diff --git a/spec/graphql/types/jira_user_type_spec.rb b/spec/graphql/types/jira_user_type_spec.rb
new file mode 100644
index 00000000000..6e55efb42f4
--- /dev/null
+++ b/spec/graphql/types/jira_user_type_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['JiraUser'] do
+ specify { expect(described_class.graphql_name).to eq('JiraUser') }
+
+ it 'has the expected fields' do
+ expect(described_class).to have_graphql_fields(
+ :jira_account_id, :jira_display_name, :jira_email, :gitlab_id, :gitlab_username, :gitlab_name
+ )
+ end
+end
diff --git a/spec/graphql/types/label_type_spec.rb b/spec/graphql/types/label_type_spec.rb
index 026c63906ef..6a999a2e925 100644
--- a/spec/graphql/types/label_type_spec.rb
+++ b/spec/graphql/types/label_type_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe GitlabSchema.types['Label'] do
+RSpec.describe GitlabSchema.types['Label'] do
it 'has the correct fields' do
expected_fields = [:id, :description, :description_html, :title, :color, :text_color]
diff --git a/spec/graphql/types/merge_request_state_enum_spec.rb b/spec/graphql/types/merge_request_state_enum_spec.rb
index 2abc7b298b1..6fc5803a5d0 100644
--- a/spec/graphql/types/merge_request_state_enum_spec.rb
+++ b/spec/graphql/types/merge_request_state_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['MergeRequestState'] do
+RSpec.describe GitlabSchema.types['MergeRequestState'] do
specify { expect(described_class.graphql_name).to eq('MergeRequestState') }
it_behaves_like 'issuable state'
diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb
index 0f48264c99f..b3dccde8ce3 100644
--- a/spec/graphql/types/merge_request_type_spec.rb
+++ b/spec/graphql/types/merge_request_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['MergeRequest'] do
+RSpec.describe GitlabSchema.types['MergeRequest'] do
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::MergeRequest) }
specify { expect(described_class).to require_graphql_authorizations(:read_merge_request) }
@@ -15,7 +15,8 @@ describe GitlabSchema.types['MergeRequest'] do
description_html state created_at updated_at source_project target_project
project project_id source_project_id target_project_id source_branch
target_branch work_in_progress merge_when_pipeline_succeeds diff_head_sha
- merge_commit_sha user_notes_count should_remove_source_branch diff_refs
+ merge_commit_sha user_notes_count should_remove_source_branch
+ diff_refs diff_stats diff_stats_summary
force_remove_source_branch merge_status in_progress_merge_commit_sha
merge_error allow_collaboration should_be_rebased rebase_commit_sha
rebase_in_progress merge_commit_message default_merge_commit_message
diff --git a/spec/graphql/types/metadata_type_spec.rb b/spec/graphql/types/metadata_type_spec.rb
index 75369ec9c3c..a1efb0a03d5 100644
--- a/spec/graphql/types/metadata_type_spec.rb
+++ b/spec/graphql/types/metadata_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Metadata'] do
+RSpec.describe GitlabSchema.types['Metadata'] do
specify { expect(described_class.graphql_name).to eq('Metadata') }
specify { expect(described_class).to require_graphql_authorizations(:read_instance_metadata) }
end
diff --git a/spec/graphql/types/metrics/dashboard_type_spec.rb b/spec/graphql/types/metrics/dashboard_type_spec.rb
index 0dbd0d8b38d..30dccc7c0be 100644
--- a/spec/graphql/types/metrics/dashboard_type_spec.rb
+++ b/spec/graphql/types/metrics/dashboard_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['MetricsDashboard'] do
+RSpec.describe GitlabSchema.types['MetricsDashboard'] do
specify { expect(described_class.graphql_name).to eq('MetricsDashboard') }
it 'has the expected fields' do
diff --git a/spec/graphql/types/metrics/dashboards/annotation_type_spec.rb b/spec/graphql/types/metrics/dashboards/annotation_type_spec.rb
index dbb8b04dbd7..12c5eec937c 100644
--- a/spec/graphql/types/metrics/dashboards/annotation_type_spec.rb
+++ b/spec/graphql/types/metrics/dashboards/annotation_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['MetricsDashboardAnnotation'] do
+RSpec.describe GitlabSchema.types['MetricsDashboardAnnotation'] do
specify { expect(described_class.graphql_name).to eq('MetricsDashboardAnnotation') }
it 'has the expected fields' do
diff --git a/spec/graphql/types/milestone_stats_type_spec.rb b/spec/graphql/types/milestone_stats_type_spec.rb
new file mode 100644
index 00000000000..1ccb275c922
--- /dev/null
+++ b/spec/graphql/types/milestone_stats_type_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['MilestoneStats'] do
+ it { expect(described_class).to require_graphql_authorizations(:read_milestone) }
+
+ it 'has the expected fields' do
+ expected_fields = %w[
+ total_issues_count closed_issues_count
+ ]
+
+ expect(described_class).to include_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/milestone_type_spec.rb b/spec/graphql/types/milestone_type_spec.rb
index 4c3d9f50a64..2315c10433b 100644
--- a/spec/graphql/types/milestone_type_spec.rb
+++ b/spec/graphql/types/milestone_type_spec.rb
@@ -2,8 +2,25 @@
require 'spec_helper'
-describe GitlabSchema.types['Milestone'] do
+RSpec.describe GitlabSchema.types['Milestone'] do
specify { expect(described_class.graphql_name).to eq('Milestone') }
specify { expect(described_class).to require_graphql_authorizations(:read_milestone) }
+
+ it 'has the expected fields' do
+ expected_fields = %w[
+ id title description state web_path
+ due_date start_date created_at updated_at
+ project_milestone group_milestone subgroup_milestone
+ stats
+ ]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+
+ describe 'stats field' do
+ subject { described_class.fields['stats'] }
+
+ it { is_expected.to have_graphql_type(Types::MilestoneStatsType) }
+ end
end
diff --git a/spec/graphql/types/mutation_type_spec.rb b/spec/graphql/types/mutation_type_spec.rb
index a67d83b1edf..41993327577 100644
--- a/spec/graphql/types/mutation_type_spec.rb
+++ b/spec/graphql/types/mutation_type_spec.rb
@@ -2,8 +2,34 @@
require 'spec_helper'
-describe Types::MutationType do
+RSpec.describe Types::MutationType do
it 'is expected to have the MergeRequestSetWip' do
expect(described_class).to have_graphql_mutation(Mutations::MergeRequests::SetWip)
end
+
+ describe 'deprecated and aliased mutations' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:alias_name, :canonical_name) do
+ 'AddAwardEmoji' | 'AwardEmojiAdd'
+ 'RemoveAwardEmoji' | 'AwardEmojiRemove'
+ 'ToggleAwardEmoji' | 'AwardEmojiToggle'
+ end
+
+ with_them do
+ let(:alias_field) { get_field(alias_name) }
+ let(:canonical_field) { get_field(canonical_name) }
+
+ it { expect(alias_field).to be_present }
+ it { expect(canonical_field).to be_present }
+ it { expect(alias_field.deprecation_reason).to be_present }
+ it { expect(canonical_field.deprecation_reason).not_to be_present }
+ it { expect(alias_field.resolver.fields).to eq(canonical_field.resolver.fields) }
+ it { expect(alias_field.resolver.arguments).to eq(canonical_field.resolver.arguments) }
+ end
+
+ def get_field(name)
+ described_class.fields[GraphqlHelpers.fieldnamerize(name)]
+ end
+ end
end
diff --git a/spec/graphql/types/namespace_type_spec.rb b/spec/graphql/types/namespace_type_spec.rb
index 741698021e7..2ed1ee3e8c4 100644
--- a/spec/graphql/types/namespace_type_spec.rb
+++ b/spec/graphql/types/namespace_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Namespace'] do
+RSpec.describe GitlabSchema.types['Namespace'] do
specify { expect(described_class.graphql_name).to eq('Namespace') }
it 'has the expected fields' do
@@ -11,7 +11,7 @@ describe GitlabSchema.types['Namespace'] do
lfs_enabled request_access_enabled projects root_storage_statistics
]
- expect(described_class).to have_graphql_fields(*expected_fields)
+ expect(described_class).to include_graphql_fields(*expected_fields)
end
specify { expect(described_class).to require_graphql_authorizations(:read_namespace) }
diff --git a/spec/graphql/types/notes/diff_position_type_spec.rb b/spec/graphql/types/notes/diff_position_type_spec.rb
index 87f3810d55c..1515c2f66d7 100644
--- a/spec/graphql/types/notes/diff_position_type_spec.rb
+++ b/spec/graphql/types/notes/diff_position_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['DiffPosition'] do
+RSpec.describe GitlabSchema.types['DiffPosition'] do
it 'exposes the expected fields' do
expected_fields = %i[
diff_refs
diff --git a/spec/graphql/types/notes/discussion_type_spec.rb b/spec/graphql/types/notes/discussion_type_spec.rb
index 177000b01b2..37ed861d069 100644
--- a/spec/graphql/types/notes/discussion_type_spec.rb
+++ b/spec/graphql/types/notes/discussion_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Discussion'] do
+RSpec.describe GitlabSchema.types['Discussion'] do
it 'exposes the expected fields' do
expected_fields = %i[
created_at
diff --git a/spec/graphql/types/notes/note_type_spec.rb b/spec/graphql/types/notes/note_type_spec.rb
index d6cd0800234..180d13d35d2 100644
--- a/spec/graphql/types/notes/note_type_spec.rb
+++ b/spec/graphql/types/notes/note_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Note'] do
+RSpec.describe GitlabSchema.types['Note'] do
it 'exposes the expected fields' do
expected_fields = %i[
author
@@ -19,6 +19,7 @@ describe GitlabSchema.types['Note'] do
resolved_at
resolved_by
system
+ system_note_icon_name
updated_at
user_permissions
]
diff --git a/spec/graphql/types/notes/noteable_type_spec.rb b/spec/graphql/types/notes/noteable_type_spec.rb
index 88d8eae56d1..fad24c6fed4 100644
--- a/spec/graphql/types/notes/noteable_type_spec.rb
+++ b/spec/graphql/types/notes/noteable_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::Notes::NoteableType do
+RSpec.describe Types::Notes::NoteableType do
it 'exposes the expected fields' do
expected_fields = %i[
discussions
diff --git a/spec/graphql/types/package_type_enum_spec.rb b/spec/graphql/types/package_type_enum_spec.rb
new file mode 100644
index 00000000000..fadec9744ed
--- /dev/null
+++ b/spec/graphql/types/package_type_enum_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['PackageTypeEnum'] do
+ it 'exposes all package types' do
+ expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER])
+ end
+end
diff --git a/spec/graphql/types/package_type_spec.rb b/spec/graphql/types/package_type_spec.rb
new file mode 100644
index 00000000000..22048e7a693
--- /dev/null
+++ b/spec/graphql/types/package_type_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['Package'] do
+ it { expect(described_class.graphql_name).to eq('Package') }
+
+ it 'includes all the package fields' do
+ expected_fields = %w[
+ id name version created_at updated_at package_type
+ ]
+
+ expect(described_class).to include_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/permission_types/base_permission_type_spec.rb b/spec/graphql/types/permission_types/base_permission_type_spec.rb
index 4c6d5fd369a..2ce02f1520c 100644
--- a/spec/graphql/types/permission_types/base_permission_type_spec.rb
+++ b/spec/graphql/types/permission_types/base_permission_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::PermissionTypes::BasePermissionType do
+RSpec.describe Types::PermissionTypes::BasePermissionType do
let(:permitable) { double('permittable') }
let(:current_user) { build(:user) }
let(:context) { { current_user: current_user } }
diff --git a/spec/graphql/types/permission_types/issue_spec.rb b/spec/graphql/types/permission_types/issue_spec.rb
index a7a3dd00f11..58c5808cbcc 100644
--- a/spec/graphql/types/permission_types/issue_spec.rb
+++ b/spec/graphql/types/permission_types/issue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::PermissionTypes::Issue do
+RSpec.describe Types::PermissionTypes::Issue do
it do
expected_permissions = [
:read_issue, :admin_issue, :update_issue, :reopen_issue,
diff --git a/spec/graphql/types/permission_types/merge_request_spec.rb b/spec/graphql/types/permission_types/merge_request_spec.rb
index e0f8bdd4712..73a178540a6 100644
--- a/spec/graphql/types/permission_types/merge_request_spec.rb
+++ b/spec/graphql/types/permission_types/merge_request_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::PermissionTypes::MergeRequest do
+RSpec.describe Types::PermissionTypes::MergeRequest do
it do
expected_permissions = [
:read_merge_request, :admin_merge_request, :update_merge_request,
diff --git a/spec/graphql/types/permission_types/merge_request_type_spec.rb b/spec/graphql/types/permission_types/merge_request_type_spec.rb
index 7e9752cdc46..7dd1d3bca06 100644
--- a/spec/graphql/types/permission_types/merge_request_type_spec.rb
+++ b/spec/graphql/types/permission_types/merge_request_type_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Types::MergeRequestType do
+RSpec.describe Types::MergeRequestType do
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::MergeRequest) }
end
diff --git a/spec/graphql/types/permission_types/note_spec.rb b/spec/graphql/types/permission_types/note_spec.rb
index a2becb6892b..9769c7b3aa3 100644
--- a/spec/graphql/types/permission_types/note_spec.rb
+++ b/spec/graphql/types/permission_types/note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['NotePermissions'] do
+RSpec.describe GitlabSchema.types['NotePermissions'] do
it 'has the expected fields' do
expected_permissions = [
:read_note, :create_note, :admin_note, :resolve_note, :award_emoji
diff --git a/spec/graphql/types/permission_types/project_spec.rb b/spec/graphql/types/permission_types/project_spec.rb
index 2789464d29c..c6853a0eadc 100644
--- a/spec/graphql/types/permission_types/project_spec.rb
+++ b/spec/graphql/types/permission_types/project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::PermissionTypes::Project do
+RSpec.describe Types::PermissionTypes::Project do
it do
expected_permissions = [
:change_namespace, :change_visibility_level, :rename_project, :remove_project, :archive_project,
diff --git a/spec/graphql/types/permission_types/snippet_spec.rb b/spec/graphql/types/permission_types/snippet_spec.rb
index 66e9fa6dfdb..e2caa1c52c2 100644
--- a/spec/graphql/types/permission_types/snippet_spec.rb
+++ b/spec/graphql/types/permission_types/snippet_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::PermissionTypes::Snippet do
+RSpec.describe Types::PermissionTypes::Snippet do
it 'returns the snippets permissions' do
expected_permissions = [
:create_note, :award_emoji, :read_snippet, :update_snippet, :admin_snippet, :report_snippet
diff --git a/spec/graphql/types/permission_types/user_spec.rb b/spec/graphql/types/permission_types/user_spec.rb
index 1e8201db568..6f0d380f763 100644
--- a/spec/graphql/types/permission_types/user_spec.rb
+++ b/spec/graphql/types/permission_types/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::PermissionTypes::User do
+RSpec.describe Types::PermissionTypes::User do
it 'returns user permissions' do
expected_permissions = [
:create_snippet
diff --git a/spec/graphql/types/project_member_type_spec.rb b/spec/graphql/types/project_member_type_spec.rb
index 1b1f6c24a32..65c89557ec1 100644
--- a/spec/graphql/types/project_member_type_spec.rb
+++ b/spec/graphql/types/project_member_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::ProjectMemberType do
+RSpec.describe Types::ProjectMemberType do
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Project) }
specify { expect(described_class.graphql_name).to eq('ProjectMember') }
diff --git a/spec/graphql/types/project_statistics_type_spec.rb b/spec/graphql/types/project_statistics_type_spec.rb
index fbea780494b..e6cffd407de 100644
--- a/spec/graphql/types/project_statistics_type_spec.rb
+++ b/spec/graphql/types/project_statistics_type_spec.rb
@@ -2,10 +2,10 @@
require 'spec_helper'
-describe GitlabSchema.types['ProjectStatistics'] do
- it "has all the required fields" do
+RSpec.describe GitlabSchema.types['ProjectStatistics'] do
+ it 'has all the required fields' do
expect(described_class).to have_graphql_fields(:storage_size, :repository_size, :lfs_objects_size,
:build_artifacts_size, :packages_size, :commit_count,
- :wiki_size)
+ :wiki_size, :snippets_size)
end
end
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index 8ee9aa9cf3a..ea88ed6a3f5 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Project'] do
+RSpec.describe GitlabSchema.types['Project'] do
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Project) }
specify { expect(described_class.graphql_name).to eq('Project') }
@@ -26,7 +26,7 @@ describe GitlabSchema.types['Project'] do
grafanaIntegration autocloseReferencedIssues suggestion_commit_message environments
boards jira_import_status jira_imports services releases release
alert_management_alerts alert_management_alert alert_management_alert_status_counts
- container_expiration_policy
+ container_expiration_policy sast_ci_configuration service_desk_enabled service_desk_address
]
expect(described_class).to include_graphql_fields(*expected_fields)
@@ -140,5 +140,93 @@ describe GitlabSchema.types['Project'] do
it { is_expected.to have_graphql_type(Types::ContainerExpirationPolicyType) }
end
+ describe 'sast_ci_configuration' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ sastCiConfiguration {
+ global {
+ nodes {
+ type
+ options {
+ nodes {
+ label
+ value
+ }
+ }
+ field
+ label
+ defaultValue
+ value
+ }
+ }
+ pipeline {
+ nodes {
+ type
+ options {
+ nodes {
+ label
+ value
+ }
+ }
+ field
+ label
+ defaultValue
+ value
+ }
+ }
+ analyzers {
+ nodes {
+ name
+ label
+ enabled
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ subject { GitlabSchema.execute(query, context: { current_user: user }).as_json }
+
+ before do
+ project.add_developer(user)
+ end
+
+ it "returns the project's sast configuration for global variables" do
+ query_result = subject.dig('data', 'project', 'sastCiConfiguration', 'global', 'nodes')
+ first_config = query_result.first
+ fourth_config = query_result[3]
+ expect(first_config['type']).to eq('string')
+ expect(first_config['field']).to eq('SECURE_ANALYZERS_PREFIX')
+ expect(first_config['label']).to eq('Image prefix')
+ expect(first_config['defaultValue']).to eq('registry.gitlab.com/gitlab-org/security-products/analyzers')
+ expect(first_config['value']).to eq('')
+ expect(first_config['options']).to be_nil
+ expect(fourth_config['options']['nodes']).to match([{ "value" => "true", "label" => "true (disables SAST)" },
+ { "value" => "false", "label" => "false (enables SAST)" }])
+ end
+
+ it "returns the project's sast configuration for pipeline variables" do
+ configuration = subject.dig('data', 'project', 'sastCiConfiguration', 'pipeline', 'nodes').first
+ expect(configuration['type']).to eq('dropdown')
+ expect(configuration['field']).to eq('stage')
+ expect(configuration['label']).to eq('Stage')
+ expect(configuration['defaultValue']).to eq('test')
+ expect(configuration['value']).to eq('')
+ end
+
+ it "returns the project's sast configuration for analyzer variables" do
+ configuration = subject.dig('data', 'project', 'sastCiConfiguration', 'analyzers', 'nodes').first
+ expect(configuration['name']).to eq('brakeman')
+ expect(configuration['label']).to eq('Brakeman')
+ expect(configuration['enabled']).to eq(true)
+ end
+ end
+
it_behaves_like 'a GraphQL type with labels'
end
diff --git a/spec/graphql/types/projects/base_service_type_spec.rb b/spec/graphql/types/projects/base_service_type_spec.rb
index 4fcb9fe1a73..423cea860d7 100644
--- a/spec/graphql/types/projects/base_service_type_spec.rb
+++ b/spec/graphql/types/projects/base_service_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['BaseService'] do
+RSpec.describe GitlabSchema.types['BaseService'] do
specify { expect(described_class.graphql_name).to eq('BaseService') }
it 'has basic expected fields' do
diff --git a/spec/graphql/types/projects/jira_project_type_spec.rb b/spec/graphql/types/projects/jira_project_type_spec.rb
index cbb01117717..60b4c5c5276 100644
--- a/spec/graphql/types/projects/jira_project_type_spec.rb
+++ b/spec/graphql/types/projects/jira_project_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['JiraProject'] do
+RSpec.describe GitlabSchema.types['JiraProject'] do
it { expect(described_class.graphql_name).to eq('JiraProject') }
it 'has basic expected fields' do
diff --git a/spec/graphql/types/projects/jira_service_type_spec.rb b/spec/graphql/types/projects/jira_service_type_spec.rb
index fad0c91caab..9db580ac963 100644
--- a/spec/graphql/types/projects/jira_service_type_spec.rb
+++ b/spec/graphql/types/projects/jira_service_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['JiraService'] do
+RSpec.describe GitlabSchema.types['JiraService'] do
specify { expect(described_class.graphql_name).to eq('JiraService') }
it 'has basic expected fields' do
diff --git a/spec/graphql/types/projects/service_type_spec.rb b/spec/graphql/types/projects/service_type_spec.rb
index f6758d17d18..f110322ac89 100644
--- a/spec/graphql/types/projects/service_type_spec.rb
+++ b/spec/graphql/types/projects/service_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::Projects::ServiceType do
+RSpec.describe Types::Projects::ServiceType do
specify { expect(described_class).to have_graphql_fields(:type, :active) }
describe ".resolve_type" do
diff --git a/spec/graphql/types/projects/services_enum_spec.rb b/spec/graphql/types/projects/services_enum_spec.rb
index 91e398e8d81..dac1213daf3 100644
--- a/spec/graphql/types/projects/services_enum_spec.rb
+++ b/spec/graphql/types/projects/services_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['ServiceType'] do
+RSpec.describe GitlabSchema.types['ServiceType'] do
specify { expect(described_class.graphql_name).to eq('ServiceType') }
it 'exposes all the existing project services' do
diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb
index 1194391c26a..081f99a8307 100644
--- a/spec/graphql/types/query_type_spec.rb
+++ b/spec/graphql/types/query_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Query'] do
+RSpec.describe GitlabSchema.types['Query'] do
it 'is called Query' do
expect(described_class.graphql_name).to eq('Query')
end
diff --git a/spec/graphql/types/release_asset_link_type_spec.rb b/spec/graphql/types/release_asset_link_type_spec.rb
new file mode 100644
index 00000000000..679431012cf
--- /dev/null
+++ b/spec/graphql/types/release_asset_link_type_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['ReleaseAssetLink'] do
+ it { expect(described_class).to require_graphql_authorizations(:read_release) }
+
+ it 'has the expected fields' do
+ expected_fields = %w[
+ id name url external link_type
+ ]
+
+ expect(described_class).to include_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/release_assets_type_spec.rb b/spec/graphql/types/release_assets_type_spec.rb
index 58f0f7ee697..0166f87bf47 100644
--- a/spec/graphql/types/release_assets_type_spec.rb
+++ b/spec/graphql/types/release_assets_type_spec.rb
@@ -2,12 +2,12 @@
require 'spec_helper'
-describe GitlabSchema.types['ReleaseAssets'] do
+RSpec.describe GitlabSchema.types['ReleaseAssets'] do
it { expect(described_class).to require_graphql_authorizations(:read_release) }
it 'has the expected fields' do
expected_fields = %w[
- assets_count links sources
+ count links sources
]
expect(described_class).to include_graphql_fields(*expected_fields)
@@ -16,7 +16,7 @@ describe GitlabSchema.types['ReleaseAssets'] do
describe 'links field' do
subject { described_class.fields['links'] }
- it { is_expected.to have_graphql_type(Types::ReleaseLinkType.connection_type) }
+ it { is_expected.to have_graphql_type(Types::ReleaseAssetLinkType.connection_type) }
end
describe 'sources field' do
diff --git a/spec/graphql/types/release_links_type_spec.rb b/spec/graphql/types/release_links_type_spec.rb
index 49e04e120f4..d505f0a4b5c 100644
--- a/spec/graphql/types/release_links_type_spec.rb
+++ b/spec/graphql/types/release_links_type_spec.rb
@@ -2,12 +2,15 @@
require 'spec_helper'
-describe GitlabSchema.types['ReleaseLink'] do
- it { expect(described_class).to require_graphql_authorizations(:read_release) }
+RSpec.describe GitlabSchema.types['ReleaseLinks'] do
+ it { expect(described_class).to require_graphql_authorizations(:download_code) }
it 'has the expected fields' do
expected_fields = %w[
- id name url external link_type
+ selfUrl
+ mergeRequestsUrl
+ issuesUrl
+ editUrl
]
expect(described_class).to include_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/release_source_type_spec.rb b/spec/graphql/types/release_source_type_spec.rb
index e471ac1a5ac..69a1ca30dbc 100644
--- a/spec/graphql/types/release_source_type_spec.rb
+++ b/spec/graphql/types/release_source_type_spec.rb
@@ -2,8 +2,8 @@
require 'spec_helper'
-describe GitlabSchema.types['ReleaseSource'] do
- it { expect(described_class).to require_graphql_authorizations(:read_release_sources) }
+RSpec.describe GitlabSchema.types['ReleaseSource'] do
+ it { expect(described_class).to require_graphql_authorizations(:download_code) }
it 'has the expected fields' do
expected_fields = %w[
diff --git a/spec/graphql/types/release_type_spec.rb b/spec/graphql/types/release_type_spec.rb
index feafe5ed519..0c05a68c5a6 100644
--- a/spec/graphql/types/release_type_spec.rb
+++ b/spec/graphql/types/release_type_spec.rb
@@ -2,14 +2,15 @@
require 'spec_helper'
-describe GitlabSchema.types['Release'] do
+RSpec.describe GitlabSchema.types['Release'] do
it { expect(described_class).to require_graphql_authorizations(:read_release) }
it 'has the expected fields' do
expected_fields = %w[
tag_name tag_path
description description_html
- name assets milestones evidences author commit
+ name milestones evidences author commit
+ assets links
created_at released_at
]
@@ -22,6 +23,12 @@ describe GitlabSchema.types['Release'] do
it { is_expected.to have_graphql_type(Types::ReleaseAssetsType) }
end
+ describe 'links field' do
+ subject { described_class.fields['links'] }
+
+ it { is_expected.to have_graphql_type(Types::ReleaseLinksType) }
+ end
+
describe 'milestones field' do
subject { described_class.fields['milestones'] }
@@ -44,6 +51,5 @@ describe GitlabSchema.types['Release'] do
subject { described_class.fields['commit'] }
it { is_expected.to have_graphql_type(Types::CommitType) }
- it { is_expected.to require_graphql_authorizations(:reporter_access) }
end
end
diff --git a/spec/graphql/types/repository_type_spec.rb b/spec/graphql/types/repository_type_spec.rb
index fb52839c712..27780476421 100644
--- a/spec/graphql/types/repository_type_spec.rb
+++ b/spec/graphql/types/repository_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Repository'] do
+RSpec.describe GitlabSchema.types['Repository'] do
specify { expect(described_class.graphql_name).to eq('Repository') }
specify { expect(described_class).to require_graphql_authorizations(:download_code) }
diff --git a/spec/graphql/types/resolvable_interface_spec.rb b/spec/graphql/types/resolvable_interface_spec.rb
index 231287f9969..25370f8d527 100644
--- a/spec/graphql/types/resolvable_interface_spec.rb
+++ b/spec/graphql/types/resolvable_interface_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::ResolvableInterface do
+RSpec.describe Types::ResolvableInterface do
it 'exposes the expected fields' do
expected_fields = %i[
resolvable
diff --git a/spec/graphql/types/root_storage_statistics_type_spec.rb b/spec/graphql/types/root_storage_statistics_type_spec.rb
index ebaa5a18623..f01c55cbccb 100644
--- a/spec/graphql/types/root_storage_statistics_type_spec.rb
+++ b/spec/graphql/types/root_storage_statistics_type_spec.rb
@@ -2,12 +2,12 @@
require 'spec_helper'
-describe GitlabSchema.types['RootStorageStatistics'] do
+RSpec.describe GitlabSchema.types['RootStorageStatistics'] do
specify { expect(described_class.graphql_name).to eq('RootStorageStatistics') }
it 'has all the required fields' do
expect(described_class).to have_graphql_fields(:storage_size, :repository_size, :lfs_objects_size,
- :build_artifacts_size, :packages_size, :wiki_size)
+ :build_artifacts_size, :packages_size, :wiki_size, :snippets_size)
end
specify { expect(described_class).to require_graphql_authorizations(:read_statistics) }
diff --git a/spec/graphql/types/snippet_type_spec.rb b/spec/graphql/types/snippet_type_spec.rb
index f24419ce9cc..0341ca2c733 100644
--- a/spec/graphql/types/snippet_type_spec.rb
+++ b/spec/graphql/types/snippet_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Snippet'] do
+RSpec.describe GitlabSchema.types['Snippet'] do
let_it_be(:user) { create(:user) }
it 'has the correct fields' do
diff --git a/spec/graphql/types/snippets/blob_type_spec.rb b/spec/graphql/types/snippets/blob_type_spec.rb
index fb8c6896732..bfac08f40d3 100644
--- a/spec/graphql/types/snippets/blob_type_spec.rb
+++ b/spec/graphql/types/snippets/blob_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['SnippetBlob'] do
+RSpec.describe GitlabSchema.types['SnippetBlob'] do
it 'has the correct fields' do
expected_fields = [:rich_data, :plain_data,
:raw_path, :size, :binary, :name, :path,
diff --git a/spec/graphql/types/snippets/blob_viewer_type_spec.rb b/spec/graphql/types/snippets/blob_viewer_type_spec.rb
index 841e22451db..8210eb9a95c 100644
--- a/spec/graphql/types/snippets/blob_viewer_type_spec.rb
+++ b/spec/graphql/types/snippets/blob_viewer_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['SnippetBlobViewer'] do
+RSpec.describe GitlabSchema.types['SnippetBlobViewer'] do
let_it_be(:snippet) { create(:personal_snippet, :repository) }
let_it_be(:blob) { snippet.repository.blob_at('HEAD', 'files/images/6049019_460s.jpg') }
diff --git a/spec/graphql/types/snippets/file_input_action_enum_spec.rb b/spec/graphql/types/snippets/file_input_action_enum_spec.rb
index 2ccc8b04b8f..ff9b706240b 100644
--- a/spec/graphql/types/snippets/file_input_action_enum_spec.rb
+++ b/spec/graphql/types/snippets/file_input_action_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::Snippets::FileInputActionEnum do
+RSpec.describe Types::Snippets::FileInputActionEnum do
specify { expect(described_class.graphql_name).to eq('SnippetFileInputActionEnum') }
it 'exposes all file input action types' do
diff --git a/spec/graphql/types/snippets/file_input_type_spec.rb b/spec/graphql/types/snippets/file_input_type_spec.rb
index 62e5caf20b7..c7d4909b542 100644
--- a/spec/graphql/types/snippets/file_input_type_spec.rb
+++ b/spec/graphql/types/snippets/file_input_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::Snippets::FileInputType do
+RSpec.describe Types::Snippets::FileInputType do
specify { expect(described_class.graphql_name).to eq('SnippetFileInputType') }
it 'has the correct arguments' do
diff --git a/spec/graphql/types/time_type_spec.rb b/spec/graphql/types/time_type_spec.rb
index 3c6e191e2fb..68d346766c2 100644
--- a/spec/graphql/types/time_type_spec.rb
+++ b/spec/graphql/types/time_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Time'] do
+RSpec.describe GitlabSchema.types['Time'] do
let(:iso) { "2018-06-04T15:23:50+02:00" }
let(:time) { Time.parse(iso) }
diff --git a/spec/graphql/types/todo_type_spec.rb b/spec/graphql/types/todo_type_spec.rb
index 87a5405f0e2..15b6195ec5c 100644
--- a/spec/graphql/types/todo_type_spec.rb
+++ b/spec/graphql/types/todo_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['Todo'] do
+RSpec.describe GitlabSchema.types['Todo'] do
it 'has the correct fields' do
expected_fields = [:id, :project, :group, :author, :action, :target_type, :body, :state, :created_at]
diff --git a/spec/graphql/types/tree/blob_type_spec.rb b/spec/graphql/types/tree/blob_type_spec.rb
index 547a03b5edf..73d61d4860c 100644
--- a/spec/graphql/types/tree/blob_type_spec.rb
+++ b/spec/graphql/types/tree/blob_type_spec.rb
@@ -2,8 +2,8 @@
require 'spec_helper'
-describe Types::Tree::BlobType do
+RSpec.describe Types::Tree::BlobType do
specify { expect(described_class.graphql_name).to eq('Blob') }
- specify { expect(described_class).to have_graphql_fields(:id, :sha, :name, :type, :path, :flat_path, :web_url, :lfs_oid) }
+ specify { expect(described_class).to have_graphql_fields(:id, :sha, :name, :type, :path, :flat_path, :web_url, :lfs_oid, :mode) }
end
diff --git a/spec/graphql/types/tree/submodule_type_spec.rb b/spec/graphql/types/tree/submodule_type_spec.rb
index b5cfe8eb812..ba2b06f1907 100644
--- a/spec/graphql/types/tree/submodule_type_spec.rb
+++ b/spec/graphql/types/tree/submodule_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::Tree::SubmoduleType do
+RSpec.describe Types::Tree::SubmoduleType do
specify { expect(described_class.graphql_name).to eq('Submodule') }
specify { expect(described_class).to have_graphql_fields(:id, :sha, :name, :type, :path, :flat_path, :web_url, :tree_url) }
diff --git a/spec/graphql/types/tree/tree_entry_type_spec.rb b/spec/graphql/types/tree/tree_entry_type_spec.rb
index 14826d06645..0e5caf66854 100644
--- a/spec/graphql/types/tree/tree_entry_type_spec.rb
+++ b/spec/graphql/types/tree/tree_entry_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::Tree::TreeEntryType do
+RSpec.describe Types::Tree::TreeEntryType do
specify { expect(described_class.graphql_name).to eq('TreeEntry') }
specify { expect(described_class).to have_graphql_fields(:id, :sha, :name, :type, :path, :flat_path, :web_url) }
diff --git a/spec/graphql/types/tree/tree_type_spec.rb b/spec/graphql/types/tree/tree_type_spec.rb
index 93faebd3602..362ecdfca91 100644
--- a/spec/graphql/types/tree/tree_type_spec.rb
+++ b/spec/graphql/types/tree/tree_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::Tree::TreeType do
+RSpec.describe Types::Tree::TreeType do
specify { expect(described_class.graphql_name).to eq('Tree') }
specify { expect(described_class).to have_graphql_fields(:trees, :submodules, :blobs, :last_commit) }
diff --git a/spec/graphql/types/tree/type_enum_spec.rb b/spec/graphql/types/tree/type_enum_spec.rb
index dcacd6073f9..f751bcf2a13 100644
--- a/spec/graphql/types/tree/type_enum_spec.rb
+++ b/spec/graphql/types/tree/type_enum_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Types::Tree::TypeEnum do
+RSpec.describe Types::Tree::TypeEnum do
specify { expect(described_class.graphql_name).to eq('EntryType') }
it 'exposes all tree entry types' do
diff --git a/spec/graphql/types/untrusted_regexp_spec.rb b/spec/graphql/types/untrusted_regexp_spec.rb
new file mode 100644
index 00000000000..c801c62fbdc
--- /dev/null
+++ b/spec/graphql/types/untrusted_regexp_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['UntrustedRegexp'] do
+ using RSpec::Parameterized::TableSyntax
+
+ specify { expect(described_class.graphql_name).to eq('UntrustedRegexp') }
+
+ specify { expect(described_class.description).to eq('A regexp containing patterns sourced from user input') }
+
+ describe '.coerce_input' do
+ subject { described_class.coerce_input(input, nil) }
+
+ where(:input, :expected_result) do
+ '.*' | '.*'
+ '(.*)' | '(.*)'
+ '[test*]+' | '[test*]+'
+ '*v1' | :raise_error
+ '[test*' | :raise_error
+ 'test*+' | :raise_error
+ end
+
+ with_them do
+ context "with input #{params[:input]}" do
+ if params[:expected_result] == :raise_error
+ it 'raises a coercion error' do
+ expect { subject }.to raise_error(GraphQL::CoercionError, /#{Regexp.quote(input)} is an invalid regexp/)
+ end
+ else
+ it { expect(subject).to eq(expected_result) }
+ end
+ end
+ end
+ end
+
+ describe '.coerce_result' do
+ subject { described_class.coerce_result(input, nil) }
+
+ where(:input, :expected_result) do
+ '1' | '1'
+ 1 | '1'
+ true | 'true'
+ end
+
+ with_them do
+ context "with input #{params[:input]}" do
+ it { expect(subject).to eq(expected_result) }
+ end
+ end
+ end
+end
diff --git a/spec/graphql/types/user_type_spec.rb b/spec/graphql/types/user_type_spec.rb
index 7b34588b0ff..6cc3f7bcaa1 100644
--- a/spec/graphql/types/user_type_spec.rb
+++ b/spec/graphql/types/user_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabSchema.types['User'] do
+RSpec.describe GitlabSchema.types['User'] do
specify { expect(described_class.graphql_name).to eq('User') }
specify { expect(described_class).to require_graphql_authorizations(:read_user) }
diff --git a/spec/haml_lint/linter/documentation_links_spec.rb b/spec/haml_lint/linter/documentation_links_spec.rb
new file mode 100644
index 00000000000..68de8317b82
--- /dev/null
+++ b/spec/haml_lint/linter/documentation_links_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'haml_lint'
+require 'haml_lint/spec'
+require Rails.root.join('haml_lint/linter/documentation_links')
+
+RSpec.describe HamlLint::Linter::DocumentationLinks do
+ include_context 'linter'
+
+ context 'when link_to points to the existing file path' do
+ let(:haml) { "= link_to 'Description', help_page_path('README.md')" }
+
+ it { is_expected.not_to report_lint }
+ end
+
+ context 'when link_to points to the existing file with valid anchor' do
+ let(:haml) { "= link_to 'Description', help_page_path('README.md', anchor: 'overview'), target: '_blank'" }
+
+ it { is_expected.not_to report_lint }
+ end
+
+ context 'when link_to points to the existing file path without .md extension' do
+ let(:haml) { "= link_to 'Description', help_page_path('README')" }
+
+ it { is_expected.not_to report_lint }
+ end
+
+ context 'when anchor is not correct' do
+ let(:haml) { "= link_to 'Description', help_page_path('README.md', anchor: 'wrong')" }
+
+ it { is_expected.to report_lint }
+
+ context 'when help_page_path has multiple options' do
+ let(:haml) { "= link_to 'Description', help_page_path('README.md', key: :value, anchor: 'wrong')" }
+
+ it { is_expected.to report_lint }
+ end
+ end
+
+ context 'when file path is wrong' do
+ let(:haml) { "= link_to 'Description', help_page_path('wrong.md'), target: '_blank'" }
+
+ it { is_expected.to report_lint }
+ end
+
+ context 'when link with wrong file path is assigned to a variable' do
+ let(:haml) { "- my_link = link_to 'Description', help_page_path('wrong.md')" }
+
+ it { is_expected.to report_lint }
+ end
+
+ context 'when it is a broken code' do
+ let(:haml) { "= I am broken! ]]]]" }
+
+ it { is_expected.not_to report_lint }
+ end
+
+ context 'when anchor belongs to a different element' do
+ let(:haml) { "= link_to 'Description', help_page_path('README.md'), target: (anchor: 'blank')" }
+
+ it { is_expected.not_to report_lint }
+ end
+
+ context 'when a simple help_page_path' do
+ let(:haml) { "- url = help_page_path('wrong.md')" }
+
+ it { is_expected.to report_lint }
+ end
+
+ context 'when link is not a string' do
+ let(:haml) { "- url = help_page_path(help_url)" }
+
+ it { is_expected.not_to report_lint }
+ end
+
+ context 'when link is a part of the tag' do
+ let(:haml) { ".data-form{ data: { url: help_page_path('wrong.md') } }" }
+
+ it { is_expected.to report_lint }
+ end
+end
diff --git a/spec/haml_lint/linter/no_plain_nodes_spec.rb b/spec/haml_lint/linter/no_plain_nodes_spec.rb
index dc647467db6..08f7e6131cc 100644
--- a/spec/haml_lint/linter/no_plain_nodes_spec.rb
+++ b/spec/haml_lint/linter/no_plain_nodes_spec.rb
@@ -5,7 +5,7 @@ require 'haml_lint'
require 'haml_lint/spec'
require Rails.root.join('haml_lint/linter/no_plain_nodes')
-describe HamlLint::Linter::NoPlainNodes do
+RSpec.describe HamlLint::Linter::NoPlainNodes do
include_context 'linter'
context 'reports when a tag has an inline plain node' do
diff --git a/spec/helpers/access_tokens_helper_spec.rb b/spec/helpers/access_tokens_helper_spec.rb
index 1d246d3f236..28041203447 100644
--- a/spec/helpers/access_tokens_helper_spec.rb
+++ b/spec/helpers/access_tokens_helper_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe AccessTokensHelper do
+RSpec.describe AccessTokensHelper do
describe "#scope_description" do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/helpers/analytics/unique_visits_helper_spec.rb b/spec/helpers/analytics/unique_visits_helper_spec.rb
new file mode 100644
index 00000000000..ff9769078c4
--- /dev/null
+++ b/spec/helpers/analytics/unique_visits_helper_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe Analytics::UniqueVisitsHelper do
+ include Devise::Test::ControllerHelpers
+
+ describe '#track_visit' do
+ let(:target_id) { 'p_analytics_valuestream' }
+ let(:current_user) { create(:user) }
+
+ before do
+ stub_feature_flags(track_unique_visits: true)
+ end
+
+ it 'does not track visits if feature flag disabled' do
+ stub_feature_flags(track_unique_visits: false)
+ sign_in(current_user)
+
+ expect_any_instance_of(Gitlab::Analytics::UniqueVisits).not_to receive(:track_visit)
+
+ helper.track_visit(target_id)
+ end
+
+ it 'does not track visits if usage ping is disabled' do
+ sign_in(current_user)
+ expect(Gitlab::CurrentSettings).to receive(:usage_ping_enabled?).and_return(false)
+
+ expect_any_instance_of(Gitlab::Analytics::UniqueVisits).not_to receive(:track_visit)
+
+ helper.track_visit(target_id)
+ end
+
+ it 'does not track visit if user is not logged in' do
+ expect_any_instance_of(Gitlab::Analytics::UniqueVisits).not_to receive(:track_visit)
+
+ helper.track_visit(target_id)
+ end
+
+ it 'tracks visit if user is logged in' do
+ sign_in(current_user)
+
+ expect_any_instance_of(Gitlab::Analytics::UniqueVisits).to receive(:track_visit)
+
+ helper.track_visit(target_id)
+ end
+
+ it 'tracks visit if user is not logged in, but has the cookie already' do
+ helper.request.cookies[:visitor_id] = { value: SecureRandom.uuid, expires: 24.months }
+
+ expect_any_instance_of(Gitlab::Analytics::UniqueVisits).to receive(:track_visit)
+
+ helper.track_visit(target_id)
+ end
+ end
+end
diff --git a/spec/helpers/appearances_helper_spec.rb b/spec/helpers/appearances_helper_spec.rb
index ed3e31b3c53..179c69b2a67 100644
--- a/spec/helpers/appearances_helper_spec.rb
+++ b/spec/helpers/appearances_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AppearancesHelper do
+RSpec.describe AppearancesHelper do
before do
user = create(:user)
allow(helper).to receive(:current_user).and_return(user)
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 75377356445..08107b841d7 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ApplicationHelper do
+RSpec.describe ApplicationHelper do
describe 'current_controller?' do
before do
stub_controller_name('foo')
@@ -209,6 +209,16 @@ describe ApplicationHelper do
end
end
+ describe '#page_startup_api_calls' do
+ it 'returns map containing JS Page Startup Calls' do
+ helper.add_page_startup_api_call("testURL")
+
+ startup_calls = helper.page_startup_api_calls
+
+ expect(startup_calls["testURL"]).to eq({})
+ end
+ end
+
describe '#autocomplete_data_sources' do
let(:project) { create(:project) }
let(:noteable_type) { Issue }
diff --git a/spec/helpers/application_settings_helper_spec.rb b/spec/helpers/application_settings_helper_spec.rb
index 3fb754f1090..c5fd88ada8f 100644
--- a/spec/helpers/application_settings_helper_spec.rb
+++ b/spec/helpers/application_settings_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ApplicationSettingsHelper do
+RSpec.describe ApplicationSettingsHelper do
context 'when all protocols in use' do
before do
stub_application_setting(enabled_git_access_protocol: '')
diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb
index 23f3449d9a7..1e843ee221b 100644
--- a/spec/helpers/auth_helper_spec.rb
+++ b/spec/helpers/auth_helper_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe AuthHelper do
+RSpec.describe AuthHelper do
describe "button_based_providers" do
it 'returns all enabled providers from devise' do
allow(helper).to receive(:auth_providers) { [:twitter, :github] }
diff --git a/spec/helpers/auto_devops_helper_spec.rb b/spec/helpers/auto_devops_helper_spec.rb
index e0fecb0c159..ad705dc5a7b 100644
--- a/spec/helpers/auto_devops_helper_spec.rb
+++ b/spec/helpers/auto_devops_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AutoDevopsHelper do
+RSpec.describe AutoDevopsHelper do
let_it_be(:project, reload: true) { create(:project) }
let_it_be(:user) { create(:user) }
@@ -93,6 +93,12 @@ describe AutoDevopsHelper do
end
end
+ describe '#auto_devops_settings_path' do
+ it 'returns auto devops settings path' do
+ expect(helper.auto_devops_settings_path(project)).to eql(project_settings_ci_cd_path(project, anchor: 'autodevops-settings'))
+ end
+ end
+
describe '#badge_for_auto_devops_scope' do
subject { helper.badge_for_auto_devops_scope(receiver) }
diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb
index 2a030742cb9..9e18ab34c1f 100644
--- a/spec/helpers/avatars_helper_spec.rb
+++ b/spec/helpers/avatars_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AvatarsHelper do
+RSpec.describe AvatarsHelper do
include UploadHelpers
let(:user) { create(:user) }
diff --git a/spec/helpers/award_emoji_helper_spec.rb b/spec/helpers/award_emoji_helper_spec.rb
index 3dee466a80c..51e0a1b9721 100644
--- a/spec/helpers/award_emoji_helper_spec.rb
+++ b/spec/helpers/award_emoji_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AwardEmojiHelper do
+RSpec.describe AwardEmojiHelper do
describe '.toggle_award_url' do
subject { helper.toggle_award_url(awardable) }
diff --git a/spec/helpers/blame_helper_spec.rb b/spec/helpers/blame_helper_spec.rb
index 8b5de040508..6371c2b63ce 100644
--- a/spec/helpers/blame_helper_spec.rb
+++ b/spec/helpers/blame_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlameHelper do
+RSpec.describe BlameHelper do
describe '#get_age_map_start_date' do
let(:dates) do
[Time.zone.local(2014, 3, 17, 0, 0, 0),
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
index 2631c219222..fe975aa7723 100644
--- a/spec/helpers/blob_helper_spec.rb
+++ b/spec/helpers/blob_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobHelper do
+RSpec.describe BlobHelper do
include TreeHelper
describe '#highlight' do
@@ -53,20 +53,12 @@ describe BlobHelper do
end
it 'returns a link with the proper route' do
- stub_feature_flags(web_ide_default: false)
link = helper.edit_blob_button(project, 'master', 'README.md')
expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/#{project.full_path}/-/edit/master/README.md")
end
- it 'returns a link with a Web IDE route' do
- link = helper.edit_blob_button(project, 'master', 'README.md')
-
- expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/-/ide/project/#{project.full_path}/edit/master/-/README.md")
- end
-
it 'returns a link with the passed link_opts on the expected route' do
- stub_feature_flags(web_ide_default: false)
link = helper.edit_blob_button(project, 'master', 'README.md', link_opts: { mr_id: 10 })
expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/#{project.full_path}/-/edit/master/README.md?mr_id=10")
diff --git a/spec/helpers/boards_helper_spec.rb b/spec/helpers/boards_helper_spec.rb
index cb9be9d5fb4..a805b96a8cc 100644
--- a/spec/helpers/boards_helper_spec.rb
+++ b/spec/helpers/boards_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BoardsHelper do
+RSpec.describe BoardsHelper do
let_it_be(:project) { create(:project) }
describe '#build_issue_link_base' do
diff --git a/spec/helpers/broadcast_messages_helper_spec.rb b/spec/helpers/broadcast_messages_helper_spec.rb
index 58cc03a9446..21fde35954e 100644
--- a/spec/helpers/broadcast_messages_helper_spec.rb
+++ b/spec/helpers/broadcast_messages_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BroadcastMessagesHelper do
+RSpec.describe BroadcastMessagesHelper do
describe 'current_broadcast_notification_message' do
subject { helper.current_broadcast_notification_message }
diff --git a/spec/helpers/button_helper_spec.rb b/spec/helpers/button_helper_spec.rb
index cf8887f9731..6a5cb73281e 100644
--- a/spec/helpers/button_helper_spec.rb
+++ b/spec/helpers/button_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ButtonHelper do
+RSpec.describe ButtonHelper do
describe 'http_clone_button' do
let(:user) { create(:user) }
let(:project) { build_stubbed(:project) }
diff --git a/spec/helpers/calendar_helper_spec.rb b/spec/helpers/calendar_helper_spec.rb
index 8dba6815e8d..ceed4191ef4 100644
--- a/spec/helpers/calendar_helper_spec.rb
+++ b/spec/helpers/calendar_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CalendarHelper do
+RSpec.describe CalendarHelper do
describe '#calendar_url_options' do
context 'when signed in' do
it "includes the current_user's feed_token" do
diff --git a/spec/helpers/ci/builds_helper_spec.rb b/spec/helpers/ci/builds_helper_spec.rb
new file mode 100644
index 00000000000..143d96cf632
--- /dev/null
+++ b/spec/helpers/ci/builds_helper_spec.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::BuildsHelper do
+ describe '#build_summary' do
+ subject { helper.build_summary(build, skip: skip) }
+
+ context 'when build has no trace' do
+ let(:build) { instance_double(Ci::Build, has_trace?: false) }
+
+ context 'when skip is false' do
+ let(:skip) { false }
+
+ it 'returns no job log' do
+ expect(subject).to eq('No job log')
+ end
+ end
+
+ context 'when skip is true' do
+ let(:skip) { true }
+
+ it 'returns no job log' do
+ expect(subject).to eq('No job log')
+ end
+ end
+ end
+
+ context 'when build has trace' do
+ let(:build) { create(:ci_build, :trace_live) }
+
+ context 'when skip is true' do
+ let(:skip) { true }
+
+ it 'returns link to logs' do
+ expect(subject).to include('View job log')
+ expect(subject).to include(pipeline_job_url(build.pipeline, build))
+ end
+ end
+
+ context 'when skip is false' do
+ let(:skip) { false }
+
+ it 'returns log lines' do
+ expect(subject).to include(build.trace.html(last_lines: 10).html_safe)
+ end
+ end
+ end
+ end
+
+ describe '#sidebar_build_class' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:build_id, :current_build_id, :retried, :expected_result) do
+ 1 | 1 | true | 'active retried'
+ 1 | 1 | false | 'active'
+ 1 | 2 | false | ''
+ 1 | 2 | true | 'retried'
+ end
+
+ let(:build) { instance_double(Ci::Build, retried?: retried, id: build_id) }
+ let(:current_build) { instance_double(Ci::Build, retried?: true, id: current_build_id ) }
+
+ subject { helper.sidebar_build_class(build, current_build) }
+
+ with_them do
+ it 'builds sidebar html class' do
+ expect(subject).to eq(expected_result)
+ end
+ end
+ end
+
+ describe '#javascript_build_options' do
+ subject { helper.javascript_build_options }
+
+ it 'returns build options' do
+ project = assign_project
+ ci_build = assign_build
+
+ expect(subject).to eq({
+ page_path: project_job_path(project, ci_build),
+ build_status: ci_build.status,
+ build_stage: ci_build.stage,
+ log_state: ''
+ })
+ end
+ end
+
+ describe '#build_failed_issue_options' do
+ subject { helper.build_failed_issue_options }
+
+ it 'returns failed title and description' do
+ project = assign_project
+ ci_build = assign_build
+
+ expect(subject).to eq(title: "Job Failed \##{ci_build.id}", description: project_job_url(project, ci_build))
+ end
+ end
+
+ def assign_project
+ build(:project).tap do |project|
+ assign(:project, project)
+ end
+ end
+
+ def assign_build
+ create(:ci_build).tap do |ci_build|
+ assign(:build, ci_build)
+ end
+ end
+end
diff --git a/spec/helpers/ci/pipeline_schedules_helper_spec.rb b/spec/helpers/ci/pipeline_schedules_helper_spec.rb
new file mode 100644
index 00000000000..2a81c2a44a0
--- /dev/null
+++ b/spec/helpers/ci/pipeline_schedules_helper_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::PipelineSchedulesHelper, :aggregate_failures do
+ describe '#timezone_data' do
+ subject { helper.timezone_data }
+
+ it 'matches schema' do
+ expect(subject).not_to be_empty
+ subject.each_with_index do |timzone_hash, i|
+ expect(timzone_hash.keys).to contain_exactly(:name, :offset, :identifier), "Failed at index #{i}"
+ end
+ end
+
+ it 'formats for display' do
+ first_timezone = ActiveSupport::TimeZone.all[0]
+
+ expect(subject[0][:name]).to eq(first_timezone.name)
+ expect(subject[0][:offset]).to eq(first_timezone.now.utc_offset)
+ expect(subject[0][:identifier]).to eq(first_timezone.tzinfo.identifier)
+ end
+ end
+end
diff --git a/spec/helpers/runners_helper_spec.rb b/spec/helpers/ci/runners_helper_spec.rb
index 042714d002e..a006933a2a5 100644
--- a/spec/helpers/runners_helper_spec.rb
+++ b/spec/helpers/ci/runners_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RunnersHelper do
+RSpec.describe Ci::RunnersHelper do
it "returns - not contacted yet" do
runner = FactoryBot.build :ci_runner
expect(runner_status_icon(runner)).to include("not connected yet")
diff --git a/spec/helpers/ci_status_helper_spec.rb b/spec/helpers/ci/status_helper_spec.rb
index 80be119b069..12a6acb1ecc 100644
--- a/spec/helpers/ci_status_helper_spec.rb
+++ b/spec/helpers/ci/status_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CiStatusHelper do
+RSpec.describe Ci::StatusHelper do
include IconsHelper
let(:success_commit) { double("Ci::Pipeline", status: 'success') }
diff --git a/spec/helpers/clusters_helper_spec.rb b/spec/helpers/clusters_helper_spec.rb
index c41d4f0ede7..dff83005c89 100644
--- a/spec/helpers/clusters_helper_spec.rb
+++ b/spec/helpers/clusters_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ClustersHelper do
+RSpec.describe ClustersHelper do
describe '#has_rbac_enabled?' do
context 'when kubernetes platform has been created' do
let(:platform_kubernetes) { build_stubbed(:cluster_platform_kubernetes) }
@@ -60,18 +60,24 @@ describe ClustersHelper do
end
describe '#js_clusters_list_data' do
- it 'displays endpoint path and images' do
- js_data = helper.js_clusters_list_data('/path')
+ subject { helper.js_clusters_list_data('/path') }
- expect(js_data[:endpoint]).to eq('/path')
+ it 'displays endpoint path' do
+ expect(subject[:endpoint]).to eq('/path')
+ end
+
+ it 'generates svg image data', :aggregate_failures do
+ expect(subject.dig(:img_tags, :aws, :path)).to match(%r(/illustrations/logos/amazon_eks|svg))
+ expect(subject.dig(:img_tags, :default, :path)).to match(%r(/illustrations/logos/kubernetes|svg))
+ expect(subject.dig(:img_tags, :gcp, :path)).to match(%r(/illustrations/logos/google_gke|svg))
- expect(js_data.dig(:img_tags, :aws, :path)).to match(%r(/illustrations/logos/amazon_eks|svg))
- expect(js_data.dig(:img_tags, :default, :path)).to match(%r(/illustrations/logos/kubernetes|svg))
- expect(js_data.dig(:img_tags, :gcp, :path)).to match(%r(/illustrations/logos/google_gke|svg))
+ expect(subject.dig(:img_tags, :aws, :text)).to eq('Amazon EKS')
+ expect(subject.dig(:img_tags, :default, :text)).to eq('Kubernetes Cluster')
+ expect(subject.dig(:img_tags, :gcp, :text)).to eq('Google GKE')
+ end
- expect(js_data.dig(:img_tags, :aws, :text)).to eq('Amazon EKS')
- expect(js_data.dig(:img_tags, :default, :text)).to eq('Kubernetes Cluster')
- expect(js_data.dig(:img_tags, :gcp, :text)).to eq('Google GKE')
+ it 'displays and ancestor_help_path' do
+ expect(subject[:ancestor_help_path]).to eq('/help/user/group/clusters/index#cluster-precedence')
end
end
diff --git a/spec/helpers/commits_helper_spec.rb b/spec/helpers/commits_helper_spec.rb
index e036e97f745..cc318a21fc9 100644
--- a/spec/helpers/commits_helper_spec.rb
+++ b/spec/helpers/commits_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CommitsHelper do
+RSpec.describe CommitsHelper do
describe 'commit_author_link' do
it 'escapes the author email' do
commit = double(
@@ -51,6 +51,20 @@ describe CommitsHelper do
end
end
+ describe '#view_file_button' do
+ let(:project) { build(:project) }
+ let(:path) { 'path/to/file' }
+ let(:sha) { '1234567890' }
+
+ subject do
+ helper.view_file_button(sha, path, project)
+ end
+
+ it 'links to project files' do
+ expect(subject).to have_link('1234567', href: helper.project_blob_path(project, "#{sha}/#{path}"))
+ end
+ end
+
describe '#view_on_environment_button' do
let(:project) { create(:project) }
let(:environment) { create(:environment, external_url: 'http://example.com') }
diff --git a/spec/helpers/components_helper_spec.rb b/spec/helpers/components_helper_spec.rb
index 703bee0ca92..4fa6d27993f 100644
--- a/spec/helpers/components_helper_spec.rb
+++ b/spec/helpers/components_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ComponentsHelper do
+RSpec.describe ComponentsHelper do
describe '#gitlab_workhorse_version' do
context 'without a Gitlab-Workhorse header' do
it 'shows the version from Gitlab::Workhorse.version' do
diff --git a/spec/helpers/container_expiration_policies_helper_spec.rb b/spec/helpers/container_expiration_policies_helper_spec.rb
index 6dcbadd89cb..b2a03f8d90f 100644
--- a/spec/helpers/container_expiration_policies_helper_spec.rb
+++ b/spec/helpers/container_expiration_policies_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerExpirationPoliciesHelper do
+RSpec.describe ContainerExpirationPoliciesHelper do
describe '#keep_n_options' do
it 'returns keep_n options formatted for dropdown usage' do
expected_result = [
diff --git a/spec/helpers/cookies_helper_spec.rb b/spec/helpers/cookies_helper_spec.rb
new file mode 100644
index 00000000000..c73e7d64987
--- /dev/null
+++ b/spec/helpers/cookies_helper_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe CookiesHelper do
+ describe '#set_secure_cookie' do
+ it 'creates an encrypted cookie with expected attributes' do
+ stub_config_setting(https: true)
+ expiration = 1.month.from_now
+ key = :secure_cookie
+ value = 'secure value'
+
+ expect_next_instance_of(ActionDispatch::Cookies::EncryptedKeyRotatingCookieJar) do |instance|
+ expect(instance).to receive(:[]=).with(key, httponly: true, secure: true, expires: expiration, value: value)
+ end
+
+ helper.set_secure_cookie(key, value, httponly: true, expires: expiration, type: CookiesHelper::COOKIE_TYPE_ENCRYPTED)
+ end
+
+ it 'creates a permanent cookie with expected attributes' do
+ key = :permanent_cookie
+ value = 'permanent value'
+
+ expect_next_instance_of(ActionDispatch::Cookies::PermanentCookieJar) do |instance|
+ expect(instance).to receive(:[]=).with(key, httponly: false, secure: false, expires: nil, value: value)
+ end
+
+ helper.set_secure_cookie(key, value, type: CookiesHelper::COOKIE_TYPE_PERMANENT)
+ end
+
+ it 'creates a regular cookie with expected attributes' do
+ key = :regular_cookie
+ value = 'regular value'
+
+ expect_next_instance_of(ActionDispatch::Cookies::CookieJar) do |instance|
+ expect(instance).to receive(:[]=).with(key, httponly: false, secure: false, expires: nil, value: value)
+ end
+
+ helper.set_secure_cookie(key, value)
+ end
+ end
+end
diff --git a/spec/helpers/dashboard_helper_spec.rb b/spec/helpers/dashboard_helper_spec.rb
index 8a4ea33ac7c..65182dcb729 100644
--- a/spec/helpers/dashboard_helper_spec.rb
+++ b/spec/helpers/dashboard_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DashboardHelper do
+RSpec.describe DashboardHelper do
let(:user) { build(:user) }
before do
diff --git a/spec/helpers/defer_script_tag_helper_spec.rb b/spec/helpers/defer_script_tag_helper_spec.rb
index 440904188ca..14317e353ab 100644
--- a/spec/helpers/defer_script_tag_helper_spec.rb
+++ b/spec/helpers/defer_script_tag_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeferScriptTagHelper do
+RSpec.describe DeferScriptTagHelper do
describe 'script tag' do
script_url = 'test.js'
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index 63aa41bbad5..ef1f0940074 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffHelper do
+RSpec.describe DiffHelper do
include RepoHelpers
let(:project) { create(:project, :repository) }
@@ -303,6 +303,20 @@ describe DiffHelper do
end
end
+ describe '#diff_file_html_data' do
+ let(:project) { build(:project) }
+ let(:path) { 'path/to/file' }
+ let(:sha) { '1234567890' }
+
+ subject do
+ helper.diff_file_html_data(project, path, sha)
+ end
+
+ it 'returns data for project files' do
+ expect(subject).to include(blob_diff_path: helper.project_blob_diff_path(project, "#{sha}/#{path}"))
+ end
+ end
+
describe '#diff_file_path_text' do
it 'returns full path by default' do
expect(diff_file_path_text(diff_file)).to eq(diff_file.new_path)
diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb
index 0ff9080ef94..bc5fe05ab52 100644
--- a/spec/helpers/emails_helper_spec.rb
+++ b/spec/helpers/emails_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EmailsHelper do
+RSpec.describe EmailsHelper do
describe 'closure_reason_text' do
context 'when given a MergeRequest' do
let(:merge_request) { create(:merge_request) }
diff --git a/spec/helpers/emoji_helper_spec.rb b/spec/helpers/emoji_helper_spec.rb
index 1b73e956f7a..15e4ce03960 100644
--- a/spec/helpers/emoji_helper_spec.rb
+++ b/spec/helpers/emoji_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EmojiHelper do
+RSpec.describe EmojiHelper do
describe '#emoji_icon' do
let(:options) { {} }
let(:emoji_text) { 'rocket' }
diff --git a/spec/helpers/environment_helper_spec.rb b/spec/helpers/environment_helper_spec.rb
index 53953d72b06..8c542ca01f4 100644
--- a/spec/helpers/environment_helper_spec.rb
+++ b/spec/helpers/environment_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EnvironmentHelper do
+RSpec.describe EnvironmentHelper do
describe '#render_deployment_status' do
context 'when using a manual deployment' do
it 'renders a span tag' do
diff --git a/spec/helpers/environments_helper_spec.rb b/spec/helpers/environments_helper_spec.rb
index 48104dfc5a6..90d6096654e 100644
--- a/spec/helpers/environments_helper_spec.rb
+++ b/spec/helpers/environments_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EnvironmentsHelper do
+RSpec.describe EnvironmentsHelper do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :repository) }
let_it_be(:environment) { create(:environment, project: project) }
@@ -23,6 +23,7 @@ describe EnvironmentsHelper do
'metrics-dashboard-base-path' => environment_metrics_path(environment),
'current-environment-name' => environment.name,
'documentation-path' => help_page_path('administration/monitoring/prometheus/index.md'),
+ 'add-dashboard-documentation-path' => help_page_path('user/project/integrations/prometheus.md', anchor: 'adding-a-new-dashboard-to-your-project'),
'empty-getting-started-svg-path' => match_asset_path('/assets/illustrations/monitoring/getting_started.svg'),
'empty-loading-svg-path' => match_asset_path('/assets/illustrations/monitoring/loading.svg'),
'empty-no-data-svg-path' => match_asset_path('/assets/illustrations/monitoring/no_data.svg'),
@@ -41,10 +42,26 @@ describe EnvironmentsHelper do
'custom-metrics-available' => 'true',
'alerts-endpoint' => project_prometheus_alerts_path(project, environment_id: environment.id, format: :json),
'prometheus-alerts-available' => 'true',
- 'custom-dashboard-base-path' => Metrics::Dashboard::CustomDashboardService::DASHBOARD_ROOT
+ 'custom-dashboard-base-path' => Metrics::Dashboard::CustomDashboardService::DASHBOARD_ROOT,
+ 'operations-settings-path' => project_settings_operations_path(project),
+ 'can-access-operations-settings' => 'true'
)
end
+ context 'without admin_operations permission' do
+ before do
+ allow(helper).to receive(:can?)
+ .with(user, :admin_operations, project)
+ .and_return(false)
+ end
+
+ specify do
+ expect(metrics_data).to include(
+ 'can-access-operations-settings' => 'false'
+ )
+ end
+ end
+
context 'without read_prometheus_alerts permission' do
before do
allow(helper).to receive(:can?)
@@ -78,6 +95,30 @@ describe EnvironmentsHelper do
it { is_expected.to include('environment-state' => 'stopped') }
end
+
+ context 'when request is from project scoped metrics path' do
+ let(:request) { double('request', path: path) }
+
+ before do
+ allow(helper).to receive(:request).and_return(request)
+ end
+
+ context '/:namespace/:project/-/metrics' do
+ let(:path) { project_metrics_dashboard_path(project) }
+
+ it 'uses correct path for metrics-dashboard-base-path' do
+ expect(metrics_data['metrics-dashboard-base-path']).to eq(project_metrics_dashboard_path(project))
+ end
+ end
+
+ context '/:namespace/:project/-/metrics/some_custom_dashboard.yml' do
+ let(:path) { "#{project_metrics_dashboard_path(project)}/some_custom_dashboard.yml" }
+
+ it 'uses correct path for metrics-dashboard-base-path' do
+ expect(metrics_data['metrics-dashboard-base-path']).to eq(project_metrics_dashboard_path(project))
+ end
+ end
+ end
end
describe '#custom_metrics_available?' do
@@ -97,4 +138,18 @@ describe EnvironmentsHelper do
expect(subject).to eq(true)
end
end
+
+ describe '#environment_logs_data' do
+ it 'returns logs data' do
+ expected_data = {
+ "environment-name": environment.name,
+ "environments-path": project_environments_path(project, format: :json),
+ "environment-id": environment.id,
+ "cluster-applications-documentation-path" => help_page_path('user/clusters/applications.md', anchor: 'elastic-stack'),
+ "clusters-path": project_clusters_path(project, format: :json)
+ }
+
+ expect(helper.environment_logs_data(project, environment)).to eq(expected_data)
+ end
+ end
end
diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb
index 6f24308757d..4ca31405c1e 100644
--- a/spec/helpers/events_helper_spec.rb
+++ b/spec/helpers/events_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EventsHelper do
+RSpec.describe EventsHelper do
include Gitlab::Routing
describe '#event_commit_title' do
@@ -174,8 +174,8 @@ describe EventsHelper do
url = helper.event_wiki_page_target_url(event)
title = event.target_title
html = [
- "<span class=\"event-target-type append-right-4\">wiki page</span>",
- "<a title=\"#{title}\" class=\"has-tooltip event-target-link append-right-4\" href=\"#{url}\">",
+ "<span class=\"event-target-type gl-mr-2\">wiki page</span>",
+ "<a title=\"#{title}\" class=\"has-tooltip event-target-link gl-mr-2\" href=\"#{url}\">",
title,
"</a>"
].join
@@ -227,4 +227,101 @@ describe EventsHelper do
end
end
end
+
+ describe '#event_filter_visible' do
+ include DesignManagementTestHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:current_user) { create(:user) }
+
+ subject { helper.event_filter_visible(key) }
+
+ before do
+ enable_design_management
+ project.add_reporter(current_user)
+ allow(helper).to receive(:current_user).and_return(current_user)
+ end
+
+ def disable_read_design_activity(object)
+ allow(Ability).to receive(:allowed?)
+ .with(current_user, :read_design_activity, eq(object))
+ .and_return(false)
+ end
+
+ context 'for :designs' do
+ let(:key) { :designs }
+
+ context 'there is no relevant instance variable' do
+ it { is_expected.to be(true) }
+ end
+
+ context 'a project has been assigned' do
+ before do
+ assign(:project, project)
+ end
+
+ it { is_expected.to be(true) }
+
+ context 'the current user cannot read design activity' do
+ before do
+ disable_read_design_activity(project)
+ end
+
+ it { is_expected.to be(false) }
+ end
+ end
+
+ context 'projects have been assigned' do
+ before do
+ assign(:projects, Project.where(id: project.id))
+ end
+
+ it { is_expected.to be(true) }
+
+ context 'the collection is empty' do
+ before do
+ assign(:projects, Project.none)
+ end
+
+ it { is_expected.to be(false) }
+ end
+
+ context 'the current user cannot read design activity' do
+ before do
+ disable_read_design_activity(project)
+ end
+
+ it { is_expected.to be(false) }
+ end
+ end
+
+ context 'a group has been assigned' do
+ let_it_be(:group) { create(:group) }
+
+ before do
+ assign(:group, group)
+ end
+
+ context 'there are no projects in the group' do
+ it { is_expected.to be(false) }
+ end
+
+ context 'the group has at least one project' do
+ before do
+ create(:project_group_link, project: project, group: group)
+ end
+
+ it { is_expected.to be(true) }
+
+ context 'the current user cannot read design activity' do
+ before do
+ disable_read_design_activity(group)
+ end
+
+ it { is_expected.to be(false) }
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/helpers/explore_helper_spec.rb b/spec/helpers/explore_helper_spec.rb
index 1a6af3be055..d843a9d3ce5 100644
--- a/spec/helpers/explore_helper_spec.rb
+++ b/spec/helpers/explore_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExploreHelper do
+RSpec.describe ExploreHelper do
let(:user) { build(:user) }
before do
diff --git a/spec/helpers/export_helper_spec.rb b/spec/helpers/export_helper_spec.rb
index 3fbda441b5d..7ab9560ca6a 100644
--- a/spec/helpers/export_helper_spec.rb
+++ b/spec/helpers/export_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExportHelper do
+RSpec.describe ExportHelper do
describe '#project_export_descriptions' do
it 'includes design management' do
expect(project_export_descriptions).to include('Design Management files and data')
diff --git a/spec/helpers/external_link_helper_spec.rb b/spec/helpers/external_link_helper_spec.rb
index 7fc4ef18731..b1a1884d887 100644
--- a/spec/helpers/external_link_helper_spec.rb
+++ b/spec/helpers/external_link_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExternalLinkHelper do
+RSpec.describe ExternalLinkHelper do
include IconsHelper
it 'returns external link with icon' do
diff --git a/spec/helpers/form_helper_spec.rb b/spec/helpers/form_helper_spec.rb
index 6698d8970e7..79c96e65a0e 100644
--- a/spec/helpers/form_helper_spec.rb
+++ b/spec/helpers/form_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe FormHelper do
+RSpec.describe FormHelper do
describe 'form_errors' do
it 'returns nil when model has no errors' do
model = double(errors: [])
diff --git a/spec/helpers/git_helper_spec.rb b/spec/helpers/git_helper_spec.rb
index 6dfd8d2cc76..0dd9eecb7f0 100644
--- a/spec/helpers/git_helper_spec.rb
+++ b/spec/helpers/git_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitHelper do
+RSpec.describe GitHelper do
describe '#short_sha' do
let(:short_sha) { helper.short_sha('d4e043f6c20749a3ab3f4b8e23f2a8979f4b9100') }
diff --git a/spec/helpers/gitlab_routing_helper_spec.rb b/spec/helpers/gitlab_routing_helper_spec.rb
index 4def04f4284..bd48fc7cee2 100644
--- a/spec/helpers/gitlab_routing_helper_spec.rb
+++ b/spec/helpers/gitlab_routing_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabRoutingHelper do
+RSpec.describe GitlabRoutingHelper do
let(:project) { build_stubbed(:project) }
let(:group) { build_stubbed(:group) }
@@ -147,8 +147,8 @@ describe GitlabRoutingHelper do
end
context 'snippets' do
- let_it_be(:personal_snippet) { create(:personal_snippet) }
- let_it_be(:project_snippet) { create(:project_snippet) }
+ let_it_be(:personal_snippet) { create(:personal_snippet, :repository) }
+ let_it_be(:project_snippet) { create(:project_snippet, :repository) }
let_it_be(:note) { create(:note_on_personal_snippet, noteable: personal_snippet) }
describe '#gitlab_snippet_path' do
@@ -181,6 +181,23 @@ describe GitlabRoutingHelper do
end
end
+ describe '#gitlab_raw_snippet_blob_path' do
+ let(:ref) { 'test-ref' }
+
+ it_behaves_like 'snippet blob raw path' do
+ subject { gitlab_raw_snippet_blob_path(blob, ref) }
+ end
+
+ context 'without a ref' do
+ let(:blob) { personal_snippet.blobs.first }
+ let(:ref) { blob.repository.root_ref }
+
+ it 'uses the root ref' do
+ expect(gitlab_raw_snippet_blob_path(blob)).to eq("/-/snippets/#{personal_snippet.id}/raw/#{ref}/#{blob.path}")
+ end
+ end
+ end
+
describe '#gitlab_raw_snippet_url' do
it 'returns the raw personal snippet url' do
expect(gitlab_raw_snippet_url(personal_snippet)).to eq("http://test.host/snippets/#{personal_snippet.id}/raw")
@@ -191,6 +208,32 @@ describe GitlabRoutingHelper do
end
end
+ describe '#gitlab_raw_snippet_blob_url' do
+ let(:blob) { snippet.blobs.first }
+ let(:ref) { 'snippet-test-ref' }
+
+ context 'for a PersonalSnippet' do
+ let(:snippet) { personal_snippet }
+
+ it { expect(gitlab_raw_snippet_blob_url(snippet, blob.path, ref)).to eq("http://test.host/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}") }
+ end
+
+ context 'for a ProjectSnippet' do
+ let(:snippet) { project_snippet }
+
+ it { expect(gitlab_raw_snippet_blob_url(snippet, blob.path, ref)).to eq("http://test.host/#{snippet.project.full_path}/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}") }
+ end
+
+ context 'without a ref' do
+ let(:snippet) { personal_snippet }
+ let(:ref) { snippet.repository.root_ref }
+
+ it 'uses the root ref' do
+ expect(gitlab_raw_snippet_blob_url(snippet, blob.path)).to eq("http://test.host/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}")
+ end
+ end
+ end
+
describe '#gitlab_snippet_notes_path' do
it 'returns the notes path for the personal snippet' do
expect(gitlab_snippet_notes_path(personal_snippet)).to eq("/snippets/#{personal_snippet.id}/notes")
diff --git a/spec/helpers/graph_helper_spec.rb b/spec/helpers/graph_helper_spec.rb
index dc389c09e60..3c7e4f970c3 100644
--- a/spec/helpers/graph_helper_spec.rb
+++ b/spec/helpers/graph_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GraphHelper do
+RSpec.describe GraphHelper do
describe '#get_refs' do
let(:project) { create(:project, :repository) }
let(:commit) { project.commit("master") }
diff --git a/spec/helpers/groups/group_members_helper_spec.rb b/spec/helpers/groups/group_members_helper_spec.rb
index 898c330c498..90792331d9b 100644
--- a/spec/helpers/groups/group_members_helper_spec.rb
+++ b/spec/helpers/groups/group_members_helper_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Groups::GroupMembersHelper do
+RSpec.describe Groups::GroupMembersHelper do
describe '.group_member_select_options' do
let(:group) { create(:group) }
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index 5be247c5b49..a739c16f3b1 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupsHelper do
+RSpec.describe GroupsHelper do
include ApplicationHelper
describe 'group_icon_url' do
diff --git a/spec/helpers/hooks_helper_spec.rb b/spec/helpers/hooks_helper_spec.rb
index 4352089c1c0..92e082c4974 100644
--- a/spec/helpers/hooks_helper_spec.rb
+++ b/spec/helpers/hooks_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe HooksHelper do
+RSpec.describe HooksHelper do
let(:project) { create(:project) }
let(:project_hook) { create(:project_hook, project: project) }
let(:system_hook) { create(:system_hook) }
diff --git a/spec/helpers/icons_helper_spec.rb b/spec/helpers/icons_helper_spec.rb
index 5c26db028b7..c47bba42ae2 100644
--- a/spec/helpers/icons_helper_spec.rb
+++ b/spec/helpers/icons_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IconsHelper do
+RSpec.describe IconsHelper do
let(:icons_path) { ActionController::Base.helpers.image_path("icons.svg") }
describe 'icon' do
@@ -22,8 +22,13 @@ describe IconsHelper do
describe 'sprite_icon_path' do
it 'returns relative path' do
- expect(sprite_icon_path)
- .to eq icons_path
+ expect(sprite_icon_path).to eq(icons_path)
+ end
+
+ it 'only calls image_path once when called multiple times' do
+ expect(ActionController::Base.helpers).to receive(:image_path).once.and_call_original
+
+ 2.times { sprite_icon_path }
end
context 'when an asset_host is set in the config it will return an absolute local URL' do
@@ -235,4 +240,25 @@ describe IconsHelper do
.to eq("<span class=\"gl-snippet-icon gl-snippet-icon-download\"></span>")
end
end
+
+ describe 'loading_icon' do
+ it 'returns span with gl-spinner class and default configuration' do
+ expect(loading_icon.to_s)
+ .to eq '<span class="gl-spinner gl-spinner-orange gl-spinner-sm" aria-label="Loading"></span>'
+ end
+
+ context 'when css_class is provided' do
+ it 'appends css_class to gl-spinner element' do
+ expect(loading_icon(css_class: 'gl-mr-2').to_s)
+ .to eq '<span class="gl-spinner gl-spinner-orange gl-spinner-sm gl-mr-2" aria-label="Loading"></span>'
+ end
+ end
+
+ context 'when container is true' do
+ it 'creates a container that has the gl-spinner-container class selector' do
+ expect(loading_icon(container: true).to_s)
+ .to eq '<div class="gl-spinner-container"><span class="gl-spinner gl-spinner-orange gl-spinner-sm" aria-label="Loading"></span></div>'
+ end
+ end
+ end
end
diff --git a/spec/helpers/import_helper_spec.rb b/spec/helpers/import_helper_spec.rb
index a6b283e49dc..18cbbdfd804 100644
--- a/spec/helpers/import_helper_spec.rb
+++ b/spec/helpers/import_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ImportHelper do
+RSpec.describe ImportHelper do
describe '#sanitize_project_name' do
it 'removes leading tildes' do
expect(helper.sanitize_project_name('~~root')).to eq('root')
diff --git a/spec/helpers/instance_configuration_helper_spec.rb b/spec/helpers/instance_configuration_helper_spec.rb
index 31a6c7bc839..1ba06b97088 100644
--- a/spec/helpers/instance_configuration_helper_spec.rb
+++ b/spec/helpers/instance_configuration_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe InstanceConfigurationHelper do
+RSpec.describe InstanceConfigurationHelper do
describe '#instance_configuration_cell_html' do
describe 'if not block is passed' do
it 'returns the parameter if present' do
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 38ad11846d2..4c93a8387a9 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssuablesHelper do
+RSpec.describe IssuablesHelper do
let(:label) { build_stubbed(:label) }
let(:label2) { build_stubbed(:label) }
@@ -303,4 +303,28 @@ describe IssuablesHelper do
end
end
end
+
+ describe '#issuable_squash_option?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:issuable_persisted, :squash, :squash_enabled_by_default, :expectation) do
+ true | true | true | true
+ true | false | true | false
+ false | false | false | false
+ false | false | true | true
+ false | true | false | false
+ false | true | true | true
+ end
+
+ with_them do
+ it 'returns the correct value' do
+ project = double(
+ squash_enabled_by_default?: squash_enabled_by_default
+ )
+ issuable = double(persisted?: issuable_persisted, squash: squash)
+
+ expect(helper.issuable_squash_option?(issuable, project)).to eq(expectation)
+ end
+ end
+ end
end
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 3ef6745958c..f2757f0e3ed 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe IssuesHelper do
+RSpec.describe IssuesHelper do
let(:project) { create(:project) }
let(:issue) { create :issue, project: project }
let(:ext_project) { create :redmine_project }
@@ -209,4 +209,28 @@ describe IssuesHelper do
it_behaves_like 'does not display link'
end
end
+
+ describe '#show_moved_service_desk_issue_warning?' do
+ let(:project1) { create(:project, service_desk_enabled: true) }
+ let(:project2) { create(:project, service_desk_enabled: true) }
+ let!(:old_issue) { create(:issue, author: User.support_bot, project: project1) }
+ let!(:new_issue) { create(:issue, author: User.support_bot, project: project2) }
+
+ before do
+ allow(Gitlab::IncomingEmail).to receive(:enabled?) { true }
+ allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
+
+ old_issue.update(moved_to: new_issue)
+ end
+
+ it 'is true when moved issue project has service desk disabled' do
+ project2.update!(service_desk_enabled: false)
+
+ expect(helper.show_moved_service_desk_issue_warning?(new_issue)).to be(true)
+ end
+
+ it 'is false when moved issue project has service desk enabled' do
+ expect(helper.show_moved_service_desk_issue_warning?(new_issue)).to be(false)
+ end
+ end
end
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index ec70041d51f..77e1d10354c 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LabelsHelper do
+RSpec.describe LabelsHelper do
describe '#show_label_issuables_link?' do
shared_examples 'a valid response to show_label_issuables_link?' do |issuables_type, when_enabled = true, when_disabled = false|
context "when asking for a #{issuables_type} link" do
diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb
index 1fc79a9762a..302ab0cc137 100644
--- a/spec/helpers/markup_helper_spec.rb
+++ b/spec/helpers/markup_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MarkupHelper do
+RSpec.describe MarkupHelper do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) do
user = create(:user, username: 'gfm')
diff --git a/spec/helpers/members_helper_spec.rb b/spec/helpers/members_helper_spec.rb
index 946ffcddae7..99e8696e960 100644
--- a/spec/helpers/members_helper_spec.rb
+++ b/spec/helpers/members_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MembersHelper do
+RSpec.describe MembersHelper do
describe '#remove_member_message' do
let(:requester) { create(:user) }
let(:project) { create(:project, :public) }
diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb
index 8db8c37038e..fcb9efa39d5 100644
--- a/spec/helpers/merge_requests_helper_spec.rb
+++ b/spec/helpers/merge_requests_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestsHelper do
+RSpec.describe MergeRequestsHelper do
include ActionView::Helpers::UrlHelper
include ProjectForksHelper
diff --git a/spec/helpers/namespaces_helper_spec.rb b/spec/helpers/namespaces_helper_spec.rb
index ebce296d7c2..1313a5c9352 100644
--- a/spec/helpers/namespaces_helper_spec.rb
+++ b/spec/helpers/namespaces_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NamespacesHelper do
+RSpec.describe NamespacesHelper do
let!(:admin) { create(:admin) }
let!(:admin_project_creation_level) { nil }
let!(:admin_group) do
@@ -174,96 +174,4 @@ describe NamespacesHelper do
end
end
end
-
- describe '#namespace_storage_alert' do
- subject { helper.namespace_storage_alert(namespace) }
-
- let(:namespace) { build(:namespace) }
-
- let(:payload) do
- {
- alert_level: :info,
- usage_message: "Usage",
- explanation_message: "Explanation",
- root_namespace: namespace
- }
- end
-
- before do
- allow(helper).to receive(:current_user).and_return(admin)
- allow_next_instance_of(Namespaces::CheckStorageSizeService, namespace, admin) do |check_storage_size_service|
- expect(check_storage_size_service).to receive(:execute).and_return(ServiceResponse.success(payload: payload))
- end
- end
-
- context 'when payload is not empty and no cookie is set' do
- it { is_expected.to eq(payload) }
- end
-
- context 'when there is no current_user' do
- before do
- allow(helper).to receive(:current_user).and_return(nil)
- end
-
- it { is_expected.to eq({}) }
- end
-
- context 'when payload is empty' do
- let(:payload) { {} }
-
- it { is_expected.to eq({}) }
- end
-
- context 'when cookie is set' do
- before do
- helper.request.cookies["hide_storage_limit_alert_#{namespace.id}_info"] = 'true'
- end
-
- it { is_expected.to eq({}) }
- end
-
- context 'when payload is empty and cookie is set' do
- let(:payload) { {} }
-
- before do
- helper.request.cookies["hide_storage_limit_alert_#{namespace.id}_info"] = 'true'
- end
-
- it { is_expected.to eq({}) }
- end
- end
-
- describe '#namespace_storage_alert_style' do
- using RSpec::Parameterized::TableSyntax
-
- subject { helper.namespace_storage_alert_style(alert_level) }
-
- where(:alert_level, :result) do
- :info | 'info'
- :warning | 'warning'
- :error | 'danger'
- :alert | 'danger'
- end
-
- with_them do
- it { is_expected.to eq(result) }
- end
- end
-
- describe '#namespace_storage_alert_icon' do
- using RSpec::Parameterized::TableSyntax
-
- subject { helper.namespace_storage_alert_icon(alert_level) }
-
- where(:alert_level, :result) do
- :info | 'information-o'
- :warning | 'warning'
- :error | 'error'
- :alert | 'error'
- end
-
- with_them do
- it { is_expected.to eq(result) }
- end
- end
end
diff --git a/spec/helpers/nav_helper_spec.rb b/spec/helpers/nav_helper_spec.rb
index ac1c6c62433..b1c9485e5a1 100644
--- a/spec/helpers/nav_helper_spec.rb
+++ b/spec/helpers/nav_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NavHelper, :do_not_mock_admin_mode do
+RSpec.describe NavHelper, :do_not_mock_admin_mode do
describe '#header_links' do
include_context 'custom session'
diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb
index 543a9081779..f29f947ba46 100644
--- a/spec/helpers/notes_helper_spec.rb
+++ b/spec/helpers/notes_helper_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe NotesHelper do
+RSpec.describe NotesHelper do
include RepoHelpers
let(:owner) { create(:owner) }
diff --git a/spec/helpers/notifications_helper_spec.rb b/spec/helpers/notifications_helper_spec.rb
index d8dcce203fe..319c85c19f9 100644
--- a/spec/helpers/notifications_helper_spec.rb
+++ b/spec/helpers/notifications_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NotificationsHelper do
+RSpec.describe NotificationsHelper do
describe 'notification_icon' do
it { expect(notification_icon(:disabled)).to match('class="fa fa-microphone-slash fa-fw"') }
it { expect(notification_icon(:owner_disabled)).to match('class="fa fa-microphone-slash fa-fw"') }
diff --git a/spec/helpers/notify_helper_spec.rb b/spec/helpers/notify_helper_spec.rb
new file mode 100644
index 00000000000..9c9d745cb53
--- /dev/null
+++ b/spec/helpers/notify_helper_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe NotifyHelper do
+ include ActionView::Helpers::UrlHelper
+
+ describe 'merge_request_reference_link' do
+ let(:project) { create(:project) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+
+ it 'returns link to merge request with the text reference' do
+ url = "http://test.host/#{project.full_path}/-/merge_requests/#{merge_request.iid}"
+
+ expect(merge_request_reference_link(merge_request)).to eq(reference_link(merge_request, url))
+ end
+ end
+
+ describe 'issue_reference_link' do
+ let(:project) { create(:project) }
+ let(:issue) { create(:issue, project: project) }
+
+ it 'returns link to issue with the text reference' do
+ url = "http://test.host/#{project.full_path}/-/issues/#{issue.iid}"
+
+ expect(issue_reference_link(issue)).to eq(reference_link(issue, url))
+ end
+ end
+
+ def reference_link(entity, url)
+ "<a href=\"#{url}\">#{entity.to_reference}</a>"
+ end
+end
diff --git a/spec/helpers/onboarding_experiment_helper_spec.rb b/spec/helpers/onboarding_experiment_helper_spec.rb
deleted file mode 100644
index cada91bff3c..00000000000
--- a/spec/helpers/onboarding_experiment_helper_spec.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe OnboardingExperimentHelper, type: :helper do
- describe '.allow_access_to_onboarding?' do
- context "when we're not gitlab.com or dev env" do
- it 'returns false' do
- allow(::Gitlab).to receive(:dev_env_or_com?).and_return(false)
-
- expect(helper.allow_access_to_onboarding?).to be(false)
- end
- end
-
- context "when we're gitlab.com or dev env" do
- before do
- allow(::Gitlab).to receive(:dev_env_or_com?).and_return(true)
- end
-
- context 'and the :user_onboarding feature is not enabled' do
- it 'returns false' do
- stub_feature_flags(user_onboarding: false)
-
- expect(helper.allow_access_to_onboarding?).to be(false)
- end
- end
-
- context 'and the :user_onboarding feature is enabled' do
- it 'returns true' do
- stub_feature_flags(user_onboarding: true)
- allow(helper).to receive(:current_user).and_return(create(:user))
-
- expect(helper.allow_access_to_onboarding?).to be(true)
- end
- end
- end
- end
-end
diff --git a/spec/helpers/operations_helper_spec.rb b/spec/helpers/operations_helper_spec.rb
new file mode 100644
index 00000000000..73deb2249bc
--- /dev/null
+++ b/spec/helpers/operations_helper_spec.rb
@@ -0,0 +1,160 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe OperationsHelper do
+ include Gitlab::Routing
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project) }
+
+ before do
+ helper.instance_variable_set(:@project, project)
+ allow(helper).to receive(:current_user) { user }
+ end
+
+ describe '#alerts_settings_data' do
+ subject { helper.alerts_settings_data }
+
+ before do
+ allow(helper).to receive(:can?).with(user, :admin_operations, project) { true }
+ end
+
+ context 'initial service configuration' do
+ let_it_be(:alerts_service) { AlertsService.new(project: project) }
+ let_it_be(:prometheus_service) { PrometheusService.new(project: project) }
+
+ before do
+ allow(project).to receive(:find_or_initialize_service).with('alerts').and_return(alerts_service)
+ allow(project).to receive(:find_or_initialize_service).with('prometheus').and_return(prometheus_service)
+ end
+
+ it 'returns the correct values' do
+ expect(subject).to eq(
+ 'activated' => 'false',
+ 'url' => alerts_service.url,
+ 'authorization_key' => nil,
+ 'form_path' => project_service_path(project, alerts_service),
+ 'alerts_setup_url' => help_page_path('user/project/integrations/generic_alerts.md', anchor: 'setting-up-generic-alerts'),
+ 'alerts_usage_url' => project_alert_management_index_path(project),
+ 'prometheus_form_path' => project_service_path(project, prometheus_service),
+ 'prometheus_reset_key_path' => reset_alerting_token_project_settings_operations_path(project),
+ 'prometheus_authorization_key' => nil,
+ 'prometheus_api_url' => nil,
+ 'prometheus_activated' => 'false',
+ 'prometheus_url' => notify_project_prometheus_alerts_url(project, format: :json),
+ 'disabled' => 'false'
+ )
+ end
+ end
+
+ context 'with external Prometheus configured' do
+ let_it_be(:prometheus_service, reload: true) do
+ create(:prometheus_service, project: project)
+ end
+
+ context 'with external Prometheus enabled' do
+ it 'returns the correct values' do
+ expect(subject).to include(
+ 'prometheus_activated' => 'true',
+ 'prometheus_api_url' => prometheus_service.api_url
+ )
+ end
+ end
+
+ context 'with external Prometheus disabled' do
+ shared_examples 'Prometheus is disabled' do
+ it 'returns the correct values' do
+ expect(subject).to include(
+ 'prometheus_activated' => 'false',
+ 'prometheus_api_url' => prometheus_service.api_url
+ )
+ end
+ end
+
+ let(:cluster_managed) { false }
+
+ before do
+ allow(prometheus_service)
+ .to receive(:prometheus_available?)
+ .and_return(cluster_managed)
+
+ prometheus_service.update!(manual_configuration: false)
+ end
+
+ include_examples 'Prometheus is disabled'
+
+ context 'when cluster managed' do
+ let(:cluster_managed) { true }
+
+ include_examples 'Prometheus is disabled'
+ end
+ end
+
+ context 'with project alert setting' do
+ let_it_be(:project_alerting_setting) { create(:project_alerting_setting, project: project) }
+
+ it 'returns the correct values' do
+ expect(subject).to include(
+ 'prometheus_authorization_key' => project_alerting_setting.token,
+ 'prometheus_api_url' => prometheus_service.api_url
+ )
+ end
+ end
+ end
+
+ context 'with generic alerts service configured' do
+ let_it_be(:alerts_service) { create(:alerts_service, project: project) }
+
+ context 'with generic alerts enabled' do
+ it 'returns the correct values' do
+ expect(subject).to include(
+ 'activated' => 'true',
+ 'authorization_key' => alerts_service.token,
+ 'url' => alerts_service.url
+ )
+ end
+ end
+
+ context 'with generic alerts disabled' do
+ before do
+ alerts_service.update!(active: false)
+ end
+
+ it 'returns the correct values' do
+ expect(subject).to include(
+ 'activated' => 'false',
+ 'authorization_key' => alerts_service.token
+ )
+ end
+ end
+ end
+ end
+
+ describe '#operations_settings_data' do
+ let_it_be(:operations_settings) do
+ create(
+ :project_incident_management_setting,
+ project: project,
+ issue_template_key: 'template-key',
+ pagerduty_active: true
+ )
+ end
+
+ subject { helper.operations_settings_data }
+
+ it 'returns the correct set of data' do
+ is_expected.to eq(
+ operations_settings_endpoint: project_settings_operations_path(project),
+ templates: '[]',
+ create_issue: 'false',
+ issue_template_key: 'template-key',
+ send_email: 'false',
+ pagerduty_active: 'true',
+ pagerduty_token: operations_settings.pagerduty_token,
+ pagerduty_webhook_url: project_incidents_pagerduty_url(project, token: operations_settings.pagerduty_token),
+ pagerduty_reset_key_path: reset_pagerduty_token_project_settings_operations_path(project)
+ )
+ end
+ end
+end
diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb
index 55f743ac683..e8a5c4613fe 100644
--- a/spec/helpers/page_layout_helper_spec.rb
+++ b/spec/helpers/page_layout_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PageLayoutHelper do
+RSpec.describe PageLayoutHelper do
describe 'page_description' do
it 'defaults to nil' do
expect(helper.page_description).to eq nil
diff --git a/spec/helpers/pagination_helper_spec.rb b/spec/helpers/pagination_helper_spec.rb
index 9fb51249edc..4cd6b6f3922 100644
--- a/spec/helpers/pagination_helper_spec.rb
+++ b/spec/helpers/pagination_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PaginationHelper do
+RSpec.describe PaginationHelper do
describe '#paginate_collection' do
let(:collection) { User.all.page(1) }
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index 7969cfd97b5..be0ad5e1a3f 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PreferencesHelper do
+RSpec.describe PreferencesHelper do
describe '#dashboard_choices' do
let(:user) { build(:user) }
@@ -120,13 +120,6 @@ describe PreferencesHelper do
end
end
- describe '#language_choices' do
- it 'returns an array of all available languages' do
- expect(helper.language_choices).to be_an(Array)
- expect(helper.language_choices.map(&:first)).to eq(Gitlab::I18n::AVAILABLE_LANGUAGES.values.sort)
- end
- end
-
def stub_user(messages = {})
if messages.empty?
allow(helper).to receive(:current_user).and_return(nil)
diff --git a/spec/helpers/profiles_helper_spec.rb b/spec/helpers/profiles_helper_spec.rb
index fc282eee26d..4a8ba2b7113 100644
--- a/spec/helpers/profiles_helper_spec.rb
+++ b/spec/helpers/profiles_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProfilesHelper do
+RSpec.describe ProfilesHelper do
describe '#commit_email_select_options' do
it 'returns an array with private commit email along with all the verified emails' do
user = create(:user)
diff --git a/spec/helpers/projects/alert_management_helper_spec.rb b/spec/helpers/projects/alert_management_helper_spec.rb
index 49b64b316e7..859c08b194a 100644
--- a/spec/helpers/projects/alert_management_helper_spec.rb
+++ b/spec/helpers/projects/alert_management_helper_spec.rb
@@ -2,23 +2,24 @@
require 'spec_helper'
-describe Projects::AlertManagementHelper do
+RSpec.describe Projects::AlertManagementHelper do
include Gitlab::Routing.url_helpers
let_it_be(:project, reload: true) { create(:project) }
let_it_be(:current_user) { create(:user) }
- let_it_be(:project_path) { project.full_path }
+ let(:project_path) { project.full_path }
+ let(:project_id) { project.id }
describe '#alert_management_data' do
let(:user_can_enable_alert_management) { true }
- let(:setting_path) { edit_project_service_path(project, AlertsService) }
+ let(:setting_path) { project_settings_operations_path(project, anchor: 'js-alert-management-settings') }
subject(:data) { helper.alert_management_data(current_user, project) }
before do
allow(helper)
.to receive(:can?)
- .with(current_user, :admin_project, project)
+ .with(current_user, :admin_operations, project)
.and_return(user_can_enable_alert_management)
end
@@ -27,6 +28,7 @@ describe Projects::AlertManagementHelper do
expect(helper.alert_management_data(current_user, project)).to match(
'project-path' => project_path,
'enable-alert-management-path' => setting_path,
+ 'populating-alerts-help-url' => 'http://test.host/help/user/project/operations/alert_management.html#enable-alert-management',
'empty-alert-svg-path' => match_asset_path('/assets/illustrations/alert-management-empty-state.svg'),
'user-can-enable-alert-management' => 'true',
'alert-management-enabled' => 'false'
@@ -56,6 +58,28 @@ describe Projects::AlertManagementHelper do
end
end
+ context 'with prometheus service' do
+ let_it_be(:prometheus_service) { create(:prometheus_service, project: project) }
+
+ context 'when prometheus service is active' do
+ it 'enables alert management' do
+ expect(data).to include(
+ 'alert-management-enabled' => 'true'
+ )
+ end
+ end
+
+ context 'when prometheus service is inactive' do
+ it 'disables alert management' do
+ prometheus_service.update!(manual_configuration: false)
+
+ expect(data).to include(
+ 'alert-management-enabled' => 'false'
+ )
+ end
+ end
+ end
+
context 'when user does not have requisite enablement permissions' do
let(:user_can_enable_alert_management) { false }
@@ -75,6 +99,7 @@ describe Projects::AlertManagementHelper do
expect(helper.alert_management_detail_data(project, alert_id)).to eq(
'alert-id' => alert_id,
'project-path' => project_path,
+ 'project-id' => project_id,
'project-issues-path' => issues_path
)
end
diff --git a/spec/helpers/projects/error_tracking_helper_spec.rb b/spec/helpers/projects/error_tracking_helper_spec.rb
index 008d749a002..882031a9c86 100644
--- a/spec/helpers/projects/error_tracking_helper_spec.rb
+++ b/spec/helpers/projects/error_tracking_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ErrorTrackingHelper do
+RSpec.describe Projects::ErrorTrackingHelper do
include Gitlab::Routing.url_helpers
let_it_be(:project, reload: true) { create(:project) }
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 4e072f02ae0..a3d0673f1b3 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectsHelper do
+RSpec.describe ProjectsHelper do
include ProjectForksHelper
let_it_be(:project) { create(:project) }
@@ -444,8 +444,8 @@ describe ProjectsHelper do
end
describe '#get_project_nav_tabs' do
+ let_it_be(:user) { create(:user) }
let(:project) { create(:project) }
- let(:user) { create(:user) }
before do
allow(helper).to receive(:can?) { true }
@@ -501,6 +501,20 @@ describe ProjectsHelper do
is_expected.not_to include(:external_wiki)
end
end
+
+ context 'when project has confluence enabled' do
+ before do
+ allow(project).to receive(:has_confluence?).and_return(true)
+ end
+
+ it { is_expected.to include(:confluence) }
+ it { is_expected.not_to include(:wiki) }
+ end
+
+ context 'when project does not have confluence enabled' do
+ it { is_expected.not_to include(:confluence) }
+ it { is_expected.to include(:wiki) }
+ end
end
describe '#can_view_operations_tab?' do
@@ -977,19 +991,32 @@ describe ProjectsHelper do
end
end
- describe '#project_license_name(project)' do
+ describe '#project_license_name(project)', :request_store do
let_it_be(:project) { create(:project) }
let_it_be(:repository) { project.repository }
subject { project_license_name(project) }
+ def license_name
+ project_license_name(project)
+ end
+
context 'gitaly is working appropriately' do
- it 'returns the license name' do
- license = Licensee::License.new('mit')
- allow(repository).to receive(:license).and_return(license)
+ let(:license) { Licensee::License.new('mit') }
+ before do
+ expect(repository).to receive(:license).and_return(license)
+ end
+
+ it 'returns the license name' do
expect(subject).to eq(license.name)
end
+
+ it 'memoizes the value' do
+ expect do
+ 2.times { expect(license_name).to eq(license.name) }
+ end.to change { Gitlab::GitalyClient.get_request_count }.by_at_most(1)
+ end
end
context 'gitaly is unreachable' do
@@ -1003,10 +1030,16 @@ describe ProjectsHelper do
subject
end
+
+ it 'memoizes the nil value' do
+ expect do
+ 2.times { expect(license_name).to be_nil }
+ end.to change { Gitlab::GitalyClient.get_request_count }.by_at_most(1)
+ end
end
before do
- allow(repository).to receive(:license).and_raise(exception)
+ expect(repository).to receive(:license).and_raise(exception)
end
context "Gitlab::Git::CommandError" do
diff --git a/spec/helpers/recaptcha_experiment_helper_spec.rb b/spec/helpers/recaptcha_experiment_helper_spec.rb
index a5b233e28a0..e677164c950 100644
--- a/spec/helpers/recaptcha_experiment_helper_spec.rb
+++ b/spec/helpers/recaptcha_experiment_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RecaptchaExperimentHelper, type: :helper do
+RSpec.describe RecaptchaExperimentHelper, type: :helper do
let(:session) { {} }
before do
diff --git a/spec/helpers/releases_helper_spec.rb b/spec/helpers/releases_helper_spec.rb
index de4086e48db..82fc799f9b0 100644
--- a/spec/helpers/releases_helper_spec.rb
+++ b/spec/helpers/releases_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ReleasesHelper do
+RSpec.describe ReleasesHelper do
describe '#illustration' do
it 'returns the correct image path' do
expect(helper.illustration).to match(/illustrations\/releases-(\w+)\.svg/)
@@ -22,6 +22,7 @@ describe ReleasesHelper do
let(:can_user_create_release) { false }
let(:common_keys) { [:project_id, :illustration_path, :documentation_path] }
+ # rubocop: disable CodeReuse/ActiveRecord
before do
helper.instance_variable_set(:@project, project)
helper.instance_variable_set(:@release, release)
@@ -30,6 +31,7 @@ describe ReleasesHelper do
.with(user, :create_release, project)
.and_return(can_user_create_release)
end
+ # rubocop: enable CodeReuse/ActiveRecord
describe '#data_for_releases_page' do
it 'includes the required data for displaying release blocks' do
@@ -41,7 +43,20 @@ describe ReleasesHelper do
it 'includes new_release_path' do
expect(helper.data_for_releases_page.keys).to contain_exactly(*common_keys, :new_release_path)
- expect(helper.data_for_releases_page[:new_release_path]).to eq(new_project_tag_path(project))
+ end
+
+ it 'points new_release_path to the "New Release" page' do
+ expect(helper.data_for_releases_page[:new_release_path]).to eq(new_project_release_path(project))
+ end
+
+ context 'when the "new_release_page" feature flag is disabled' do
+ before do
+ stub_feature_flags(new_release_page: false)
+ end
+
+ it 'points new_release_path to the "New Tag" page' do
+ expect(helper.data_for_releases_page[:new_release_path]).to eq(new_project_tag_path(project))
+ end
end
end
end
@@ -57,7 +72,23 @@ describe ReleasesHelper do
release_assets_docs_path
manage_milestones_path
new_milestone_path)
- expect(helper.data_for_edit_release_page.keys).to eq(keys)
+
+ expect(helper.data_for_edit_release_page.keys).to match_array(keys)
+ end
+ end
+
+ describe '#data_for_new_release_page' do
+ it 'has the needed data to display the "new release" page' do
+ keys = %i(project_id
+ markdown_preview_path
+ markdown_docs_path
+ update_release_api_docs_path
+ release_assets_docs_path
+ manage_milestones_path
+ new_milestone_path
+ default_branch)
+
+ expect(helper.data_for_new_release_page.keys).to match_array(keys)
end
end
end
diff --git a/spec/helpers/rss_helper_spec.rb b/spec/helpers/rss_helper_spec.rb
index 657f5fb42bc..c7eb33dc6f7 100644
--- a/spec/helpers/rss_helper_spec.rb
+++ b/spec/helpers/rss_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RssHelper do
+RSpec.describe RssHelper do
describe '#rss_url_options' do
context 'when signed in' do
it "includes the current_user's feed_token" do
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index b209ed869bf..699232e67b1 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -2,12 +2,105 @@
require 'spec_helper'
-describe SearchHelper do
+RSpec.describe SearchHelper do
# Override simple_sanitize for our testing purposes
def simple_sanitize(str)
str
end
+ describe 'search_autocomplete_opts' do
+ context "with no current user" do
+ before do
+ allow(self).to receive(:current_user).and_return(nil)
+ end
+
+ it "returns nil" do
+ expect(search_autocomplete_opts("q")).to be_nil
+ end
+ end
+
+ context "with a standard user" do
+ let(:user) { create(:user) }
+
+ before do
+ allow(self).to receive(:current_user).and_return(user)
+ end
+
+ it "includes Help sections" do
+ expect(search_autocomplete_opts("hel").size).to eq(9)
+ end
+
+ it "includes default sections" do
+ expect(search_autocomplete_opts("dash").size).to eq(1)
+ end
+
+ it "does not include admin sections" do
+ expect(search_autocomplete_opts("admin").size).to eq(0)
+ end
+
+ it "does not allow regular expression in search term" do
+ expect(search_autocomplete_opts("(webhooks|api)").size).to eq(0)
+ end
+
+ it "includes the user's groups" do
+ create(:group).add_owner(user)
+ expect(search_autocomplete_opts("gro").size).to eq(1)
+ end
+
+ it "includes nested group" do
+ create(:group, :nested, name: 'foo').add_owner(user)
+ expect(search_autocomplete_opts('foo').size).to eq(1)
+ end
+
+ it "includes the user's projects" do
+ project = create(:project, namespace: create(:namespace, owner: user))
+ expect(search_autocomplete_opts(project.name).size).to eq(1)
+ end
+
+ it "includes the required project attrs" do
+ project = create(:project, namespace: create(:namespace, owner: user))
+ result = search_autocomplete_opts(project.name).first
+
+ expect(result.keys).to match_array(%i[category id value label url avatar_url])
+ end
+
+ it "includes the required group attrs" do
+ create(:group).add_owner(user)
+ result = search_autocomplete_opts("gro").first
+
+ expect(result.keys).to match_array(%i[category id label url avatar_url])
+ end
+
+ it "does not include the public group" do
+ group = create(:group)
+ expect(search_autocomplete_opts(group.name).size).to eq(0)
+ end
+
+ context "with a current project" do
+ before do
+ @project = create(:project, :repository)
+ end
+
+ it "includes project-specific sections" do
+ expect(search_autocomplete_opts("Files").size).to eq(1)
+ expect(search_autocomplete_opts("Commits").size).to eq(1)
+ end
+ end
+ end
+
+ context 'with an admin user' do
+ let(:admin) { create(:admin) }
+
+ before do
+ allow(self).to receive(:current_user).and_return(admin)
+ end
+
+ it "includes admin sections" do
+ expect(search_autocomplete_opts("admin").size).to eq(1)
+ end
+ end
+ end
+
describe 'search_entries_info' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/helpers/services_helper_spec.rb b/spec/helpers/services_helper_spec.rb
index edc14f86a50..10d6ec7b6a7 100644
--- a/spec/helpers/services_helper_spec.rb
+++ b/spec/helpers/services_helper_spec.rb
@@ -2,14 +2,33 @@
require 'spec_helper'
-describe ServicesHelper do
+RSpec.describe ServicesHelper do
describe 'event_action_title' do
it { expect(event_action_title('comment')).to eq 'Comment' }
it { expect(event_action_title('something')).to eq 'Something' }
end
- describe 'event_action_description' do
- it { expect(event_action_description('comment')).to eq 'Comment will be posted on each event' }
- it { expect(event_action_description('something')).to eq nil }
+ describe '#integration_form_data' do
+ subject { helper.integration_form_data(integration) }
+
+ context 'Jira service' do
+ let(:integration) { build(:jira_service) }
+
+ it 'includes Jira specific fields' do
+ is_expected.to include(
+ :id,
+ :show_active,
+ :activated,
+ :type,
+ :merge_request_events,
+ :commit_events,
+ :enable_comments,
+ :comment_detail,
+ :trigger_events,
+ :fields,
+ :inherit_from_id
+ )
+ end
+ end
end
end
diff --git a/spec/helpers/sessions_helper_spec.rb b/spec/helpers/sessions_helper_spec.rb
index 647771ace92..027943aecee 100644
--- a/spec/helpers/sessions_helper_spec.rb
+++ b/spec/helpers/sessions_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SessionsHelper do
+RSpec.describe SessionsHelper do
describe '#unconfirmed_email?' do
it 'returns true when the flash alert contains a devise failure unconfirmed message' do
flash[:alert] = t(:unconfirmed, scope: [:devise, :failure])
diff --git a/spec/helpers/sidekiq_helper_spec.rb b/spec/helpers/sidekiq_helper_spec.rb
index 86e52419f9c..6a0a92bafd8 100644
--- a/spec/helpers/sidekiq_helper_spec.rb
+++ b/spec/helpers/sidekiq_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SidekiqHelper do
+RSpec.describe SidekiqHelper do
describe 'parse_sidekiq_ps' do
it 'parses line with time' do
line = '55137 10,0 2,1 S+ 2:30pm sidekiq 4.1.4 gitlab [0 of 25 busy] '
diff --git a/spec/helpers/snippets_helper_spec.rb b/spec/helpers/snippets_helper_spec.rb
index 6fdf4f5cfb4..8fc54f17e71 100644
--- a/spec/helpers/snippets_helper_spec.rb
+++ b/spec/helpers/snippets_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SnippetsHelper do
+RSpec.describe SnippetsHelper do
include Gitlab::Routing
include IconsHelper
diff --git a/spec/helpers/sorting_helper_spec.rb b/spec/helpers/sorting_helper_spec.rb
index 5397a47b3dd..6c52016139b 100644
--- a/spec/helpers/sorting_helper_spec.rb
+++ b/spec/helpers/sorting_helper_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe SortingHelper do
+RSpec.describe SortingHelper do
include ApplicationHelper
include IconsHelper
include ExploreHelper
diff --git a/spec/helpers/sourcegraph_helper_spec.rb b/spec/helpers/sourcegraph_helper_spec.rb
index 3e8486a5632..6a95c8e4a43 100644
--- a/spec/helpers/sourcegraph_helper_spec.rb
+++ b/spec/helpers/sourcegraph_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SourcegraphHelper do
+RSpec.describe SourcegraphHelper do
describe '#sourcegraph_url_message' do
let(:sourcegraph_url) { 'http://sourcegraph.example.com' }
diff --git a/spec/helpers/storage_helper_spec.rb b/spec/helpers/storage_helper_spec.rb
index 577e6e5caf0..eca42c8ce06 100644
--- a/spec/helpers/storage_helper_spec.rb
+++ b/spec/helpers/storage_helper_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe StorageHelper do
+RSpec.describe StorageHelper do
describe "#storage_counter" do
it "formats bytes to one decimal place" do
expect(helper.storage_counter(1.23.megabytes)).to eq("1.2 MB")
@@ -30,10 +30,11 @@ describe StorageHelper do
repository_size: 10.kilobytes,
wiki_size: 10.bytes,
lfs_objects_size: 20.gigabytes,
- build_artifacts_size: 30.megabytes))
+ build_artifacts_size: 30.megabytes,
+ snippets_size: 40.megabytes))
end
- let(:message) { 'Repository: 10 KB / Wikis: 10 Bytes / Build Artifacts: 30 MB / LFS: 20 GB' }
+ let(:message) { 'Repository: 10 KB / Wikis: 10 Bytes / Build Artifacts: 30 MB / LFS: 20 GB / Snippets: 40 MB' }
it 'works on ProjectStatistics' do
expect(helper.storage_counters_details(project.statistics)).to eq(message)
diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb
index db0836c8550..426bca2ced2 100644
--- a/spec/helpers/submodule_helper_spec.rb
+++ b/spec/helpers/submodule_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SubmoduleHelper do
+RSpec.describe SubmoduleHelper do
include RepoHelpers
let(:submodule_item) { double(id: 'hash', path: 'rack') }
diff --git a/spec/helpers/subscribable_banner_helper_spec.rb b/spec/helpers/subscribable_banner_helper_spec.rb
index 75f2e32d7d8..3f9ea989fb0 100644
--- a/spec/helpers/subscribable_banner_helper_spec.rb
+++ b/spec/helpers/subscribable_banner_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SubscribableBannerHelper do
+RSpec.describe SubscribableBannerHelper do
describe '#display_subscription_banner!' do
it 'is over-written in EE' do
expect { helper.display_subscription_banner! }.not_to raise_error
diff --git a/spec/helpers/tab_helper_spec.rb b/spec/helpers/tab_helper_spec.rb
index 3a3935a2130..f89d0ac0f5a 100644
--- a/spec/helpers/tab_helper_spec.rb
+++ b/spec/helpers/tab_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TabHelper do
+RSpec.describe TabHelper do
include ApplicationHelper
describe 'nav_link' do
diff --git a/spec/helpers/time_helper_spec.rb b/spec/helpers/time_helper_spec.rb
index 858d6d341f4..6663a5c81c8 100644
--- a/spec/helpers/time_helper_spec.rb
+++ b/spec/helpers/time_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TimeHelper do
+RSpec.describe TimeHelper do
describe "#time_interval_in_words" do
it "returns minutes and seconds" do
intervals_in_words = {
diff --git a/spec/helpers/timeboxes_helper_spec.rb b/spec/helpers/timeboxes_helper_spec.rb
index 6fe738914ce..94e997f7a65 100644
--- a/spec/helpers/timeboxes_helper_spec.rb
+++ b/spec/helpers/timeboxes_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TimeboxesHelper do
+RSpec.describe TimeboxesHelper do
describe '#milestones_filter_dropdown_path' do
let(:project) { create(:project) }
let(:project2) { create(:project) }
diff --git a/spec/helpers/timeboxes_routing_helper_spec.rb b/spec/helpers/timeboxes_routing_helper_spec.rb
index 1b0eb2c0ad2..952194b6704 100644
--- a/spec/helpers/timeboxes_routing_helper_spec.rb
+++ b/spec/helpers/timeboxes_routing_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TimeboxesRoutingHelper do
+RSpec.describe TimeboxesRoutingHelper do
let(:project) { build_stubbed(:project) }
let(:group) { build_stubbed(:group) }
diff --git a/spec/helpers/todos_helper_spec.rb b/spec/helpers/todos_helper_spec.rb
index 0811c2af891..6b658a475b1 100644
--- a/spec/helpers/todos_helper_spec.rb
+++ b/spec/helpers/todos_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TodosHelper do
+RSpec.describe TodosHelper do
let_it_be(:user) { create(:user) }
let_it_be(:author) { create(:user) }
let_it_be(:issue) { create(:issue, title: 'Issue 1') }
@@ -163,4 +163,83 @@ describe TodosHelper do
expect(design_option).to include(text: 'Design')
end
end
+
+ describe '#todo_target_state_pill' do
+ subject { helper.todo_target_state_pill(todo) }
+
+ shared_examples 'a rendered state pill' do |attr|
+ it 'returns expected html' do
+ aggregate_failures do
+ expect(subject).to have_css(".status-box-#{attr[:type]}-#{attr[:state].dasherize}")
+ expect(subject).to have_content(attr[:state].capitalize)
+ end
+ end
+ end
+
+ shared_examples 'no state pill' do
+ specify { expect(subject).to eq(nil) }
+ end
+
+ context 'merge request todo' do
+ let(:todo) { create(:todo, target: create(:merge_request)) }
+
+ it_behaves_like 'no state pill'
+
+ context 'merged MR' do
+ before do
+ todo.target.update!(state: 'merged')
+ end
+
+ it_behaves_like 'a rendered state pill', type: 'mr', state: 'merged'
+ end
+ end
+
+ context 'issue todo' do
+ let(:todo) { create(:todo, target: issue) }
+
+ it_behaves_like 'no state pill'
+
+ context 'closed issue' do
+ before do
+ todo.target.update!(state: 'closed')
+ end
+
+ it_behaves_like 'a rendered state pill', type: 'issue', state: 'closed'
+ end
+ end
+
+ context 'alert todo' do
+ let(:todo) { alert_todo }
+
+ it_behaves_like 'no state pill'
+
+ context 'resolved alert' do
+ before do
+ todo.target.resolve!
+ end
+
+ it_behaves_like 'a rendered state pill', type: 'alert', state: 'resolved'
+ end
+ end
+ end
+
+ describe '#todo_author_display?' do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { helper.todo_author_display?(alert_todo) }
+
+ where(:action, :result) do
+ Todo::BUILD_FAILED | false
+ Todo::UNMERGEABLE | false
+ Todo::ASSIGNED | true
+ end
+
+ with_them do
+ before do
+ alert_todo.action = action
+ end
+
+ it { is_expected.to eq(result) }
+ end
+ end
end
diff --git a/spec/helpers/tracking_helper_spec.rb b/spec/helpers/tracking_helper_spec.rb
index b0c98be4130..47b344cfc25 100644
--- a/spec/helpers/tracking_helper_spec.rb
+++ b/spec/helpers/tracking_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TrackingHelper do
+RSpec.describe TrackingHelper do
describe '#tracking_attrs' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/helpers/tree_helper_spec.rb b/spec/helpers/tree_helper_spec.rb
index c7cdb4ae45c..307479744ef 100644
--- a/spec/helpers/tree_helper_spec.rb
+++ b/spec/helpers/tree_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TreeHelper do
+RSpec.describe TreeHelper do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:sha) { 'c1c67abbaf91f624347bb3ae96eabe3a1b742478' }
@@ -154,4 +154,58 @@ describe TreeHelper do
expect(helper.commit_in_single_accessible_branch).to include(escaped_branch_name)
end
end
+
+ describe '#vue_file_list_data' do
+ before do
+ allow(helper).to receive(:current_user).and_return(nil)
+ end
+
+ it 'returns a list of attributes related to the project' do
+ expect(helper.vue_file_list_data(project, sha)).to include(
+ can_push_code: nil,
+ fork_path: nil,
+ escaped_ref: sha,
+ ref: sha,
+ project_path: project.full_path,
+ project_short_path: project.path,
+ full_name: project.name_with_namespace
+ )
+ end
+
+ context 'user does not have write access but a personal fork exists' do
+ include ProjectForksHelper
+
+ let_it_be(:user) { create(:user) }
+ let!(:forked_project) { create(:project, :repository, namespace: user.namespace) }
+
+ before do
+ project.add_guest(user)
+ fork_project(project, nil, target_project: forked_project)
+
+ allow(helper).to receive(:current_user).and_return(user)
+ end
+
+ it 'includes fork_path too' do
+ expect(helper.vue_file_list_data(project, sha)).to include(
+ fork_path: forked_project.full_path
+ )
+ end
+ end
+
+ context 'user has write access' do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ project.add_developer(user)
+
+ allow(helper).to receive(:current_user).and_return(user)
+ end
+
+ it 'includes can_push_code: true' do
+ expect(helper.vue_file_list_data(project, sha)).to include(
+ can_push_code: "true"
+ )
+ end
+ end
+ end
end
diff --git a/spec/helpers/user_callouts_helper_spec.rb b/spec/helpers/user_callouts_helper_spec.rb
index b123b11d278..60238053aa2 100644
--- a/spec/helpers/user_callouts_helper_spec.rb
+++ b/spec/helpers/user_callouts_helper_spec.rb
@@ -2,15 +2,15 @@
require "spec_helper"
-describe UserCalloutsHelper do
- let(:user) { create(:user) }
+RSpec.describe UserCalloutsHelper do
+ let_it_be(:user) { create(:user) }
before do
allow(helper).to receive(:current_user).and_return(user)
end
describe '.show_gke_cluster_integration_callout?' do
- let(:project) { create(:project) }
+ let_it_be(:project) { create(:project) }
subject { helper.show_gke_cluster_integration_callout?(project) }
diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb
index e3e599007a4..8dfdb23c64b 100644
--- a/spec/helpers/users_helper_spec.rb
+++ b/spec/helpers/users_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UsersHelper do
+RSpec.describe UsersHelper do
include TermsHelper
let(:user) { create(:user) }
diff --git a/spec/helpers/version_check_helper_spec.rb b/spec/helpers/version_check_helper_spec.rb
index 421ff21bfdb..6d849d0720e 100644
--- a/spec/helpers/version_check_helper_spec.rb
+++ b/spec/helpers/version_check_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe VersionCheckHelper do
+RSpec.describe VersionCheckHelper do
describe '#version_status_badge' do
it 'returns nil if not dev environment and not enabled' do
stub_rails_env('development')
diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb
index 3b4d82c65ac..9cbace3cfd0 100644
--- a/spec/helpers/visibility_level_helper_spec.rb
+++ b/spec/helpers/visibility_level_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe VisibilityLevelHelper do
+RSpec.describe VisibilityLevelHelper do
include ProjectForksHelper
let(:project) { build(:project) }
diff --git a/spec/helpers/wiki_helper_spec.rb b/spec/helpers/wiki_helper_spec.rb
index 4b53823aaed..040368b5ebd 100644
--- a/spec/helpers/wiki_helper_spec.rb
+++ b/spec/helpers/wiki_helper_spec.rb
@@ -2,7 +2,39 @@
require 'spec_helper'
-describe WikiHelper do
+RSpec.describe WikiHelper do
+ describe '#wiki_page_title' do
+ let_it_be(:page) { create(:wiki_page) }
+
+ it 'sets the title for the show action' do
+ expect(helper).to receive(:breadcrumb_title).with(page.human_title)
+ expect(helper).to receive(:wiki_breadcrumb_dropdown_links).with(page.slug)
+ expect(helper).to receive(:page_title).with(page.human_title, 'Wiki')
+ expect(helper).to receive(:add_to_breadcrumbs).with('Wiki', helper.wiki_path(page.wiki))
+
+ helper.wiki_page_title(page)
+ end
+
+ it 'sets the title for a custom action' do
+ expect(helper).to receive(:breadcrumb_title).with(page.human_title)
+ expect(helper).to receive(:wiki_breadcrumb_dropdown_links).with(page.slug)
+ expect(helper).to receive(:page_title).with('Edit', page.human_title, 'Wiki')
+ expect(helper).to receive(:add_to_breadcrumbs).with('Wiki', helper.wiki_path(page.wiki))
+
+ helper.wiki_page_title(page, 'Edit')
+ end
+
+ it 'sets the title for an unsaved page' do
+ expect(page).to receive(:persisted?).and_return(false)
+ expect(helper).not_to receive(:breadcrumb_title)
+ expect(helper).not_to receive(:wiki_breadcrumb_dropdown_links)
+ expect(helper).to receive(:page_title).with('Wiki')
+ expect(helper).to receive(:add_to_breadcrumbs).with('Wiki', helper.wiki_path(page.wiki))
+
+ helper.wiki_page_title(page)
+ end
+ end
+
describe '#breadcrumb' do
context 'when the page is at the root level' do
it 'returns the capitalized page name' do
@@ -72,4 +104,24 @@ describe WikiHelper do
expect(helper.wiki_sort_title('unknown')).to eq('Title')
end
end
+
+ describe '#wiki_page_tracking_context' do
+ let_it_be(:page) { create(:wiki_page, title: 'path/to/page 💩', content: '💩', format: :markdown) }
+
+ subject { helper.wiki_page_tracking_context(page) }
+
+ it 'returns the tracking context' do
+ expect(subject).to eq(
+ 'wiki-format' => :markdown,
+ 'wiki-title-size' => 9,
+ 'wiki-content-size' => 4,
+ 'wiki-directory-nest-level' => 2
+ )
+ end
+
+ it 'returns a nest level of zero for toplevel files' do
+ expect(page).to receive(:path).and_return('page')
+ expect(subject).to include('wiki-directory-nest-level' => 0)
+ end
+ end
end
diff --git a/spec/helpers/x509_helper_spec.rb b/spec/helpers/x509_helper_spec.rb
index db3f6158195..4e3e8c8d3f6 100644
--- a/spec/helpers/x509_helper_spec.rb
+++ b/spec/helpers/x509_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe X509Helper do
+RSpec.describe X509Helper do
describe '#x509_subject' do
let(:search_uppercase) { %w[CN OU O] }
let(:search_lowercase) { %w[cn ou o] }
diff --git a/spec/initializers/100_patch_omniauth_saml_spec.rb b/spec/initializers/100_patch_omniauth_saml_spec.rb
index c4d20f79af0..3496eb4d680 100644
--- a/spec/initializers/100_patch_omniauth_saml_spec.rb
+++ b/spec/initializers/100_patch_omniauth_saml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'OmniAuth::Strategies::SAML', type: :strategy do
+RSpec.describe 'OmniAuth::Strategies::SAML', type: :strategy do
let(:idp_sso_target_url) { 'https://login.example.com/idp' }
let(:strategy) { [OmniAuth::Strategies::SAML, { idp_sso_target_url: idp_sso_target_url }] }
diff --git a/spec/initializers/6_validations_spec.rb b/spec/initializers/6_validations_spec.rb
index 248f967311b..b909fc9db0a 100644
--- a/spec/initializers/6_validations_spec.rb
+++ b/spec/initializers/6_validations_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require_relative '../../config/initializers/6_validations.rb'
-describe '6_validations' do
+RSpec.describe '6_validations' do
describe 'validate_storages_config' do
context 'with correct settings' do
before do
diff --git a/spec/initializers/action_mailer_hooks_spec.rb b/spec/initializers/action_mailer_hooks_spec.rb
index 03eee09f737..f04fe804d62 100644
--- a/spec/initializers/action_mailer_hooks_spec.rb
+++ b/spec/initializers/action_mailer_hooks_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'ActionMailer hooks' do
+RSpec.describe 'ActionMailer hooks' do
describe 'smime signature interceptor' do
before do
class_spy(ActionMailer::Base).as_stubbed_const
diff --git a/spec/initializers/actionpack_generate_old_csrf_token_spec.rb b/spec/initializers/actionpack_generate_old_csrf_token_spec.rb
deleted file mode 100644
index 036f52398bb..00000000000
--- a/spec/initializers/actionpack_generate_old_csrf_token_spec.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe ActionController::Base, 'CSRF token generation patch', type: :controller do # rubocop:disable RSpec/FilePath
- let(:fixed_seed) { SecureRandom.random_bytes(described_class::AUTHENTICITY_TOKEN_LENGTH) }
-
- context 'global_csrf_token feature flag is enabled' do
- it 'generates 6.0.3.1 style CSRF token', :aggregate_failures do
- generated_token = controller.send(:form_authenticity_token)
-
- expect(valid_authenticity_token?(generated_token)).to be_truthy
- expect(compare_with_real_token(generated_token)).to be_falsey
- expect(compare_with_global_token(generated_token)).to be_truthy
- end
- end
-
- context 'global_csrf_token feature flag is disabled' do
- before do
- stub_feature_flags(global_csrf_token: false)
- end
-
- it 'generates 6.0.3 style CSRF token', :aggregate_failures do
- generated_token = controller.send(:form_authenticity_token)
-
- expect(valid_authenticity_token?(generated_token)).to be_truthy
- expect(compare_with_real_token(generated_token)).to be_truthy
- expect(compare_with_global_token(generated_token)).to be_falsey
- end
- end
-
- def compare_with_global_token(token)
- unmasked_token = controller.send :unmask_token, Base64.strict_decode64(token)
-
- controller.send(:compare_with_global_token, unmasked_token, session)
- end
-
- def compare_with_real_token(token)
- unmasked_token = controller.send :unmask_token, Base64.strict_decode64(token)
-
- controller.send(:compare_with_real_token, unmasked_token, session)
- end
-
- def valid_authenticity_token?(token)
- controller.send(:valid_authenticity_token?, session, token)
- end
-end
diff --git a/spec/initializers/active_record_locking_spec.rb b/spec/initializers/active_record_locking_spec.rb
index 5a16aef78e6..e979fa0b793 100644
--- a/spec/initializers/active_record_locking_spec.rb
+++ b/spec/initializers/active_record_locking_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'ActiveRecord locking' do
+RSpec.describe 'ActiveRecord locking' do
let(:issue) { create(:issue) }
shared_examples 'locked model' do
diff --git a/spec/initializers/asset_proxy_setting_spec.rb b/spec/initializers/asset_proxy_setting_spec.rb
index 7eab5de155b..62bb31b2be9 100644
--- a/spec/initializers/asset_proxy_setting_spec.rb
+++ b/spec/initializers/asset_proxy_setting_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Asset proxy settings initialization' do
+RSpec.describe 'Asset proxy settings initialization' do
describe '#asset_proxy' do
it 'defaults to disabled' do
expect(Banzai::Filter::AssetProxyFilter).to receive(:initialize_settings)
diff --git a/spec/initializers/attr_encrypted_no_db_connection_spec.rb b/spec/initializers/attr_encrypted_no_db_connection_spec.rb
index 14e0e1f2167..ad3d14ed7d4 100644
--- a/spec/initializers/attr_encrypted_no_db_connection_spec.rb
+++ b/spec/initializers/attr_encrypted_no_db_connection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'GitLab monkey-patches to AttrEncrypted' do
+RSpec.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
diff --git a/spec/initializers/attr_encrypted_thread_safe_spec.rb b/spec/initializers/attr_encrypted_thread_safe_spec.rb
index 096b8b196b4..e79b7c716ec 100644
--- a/spec/initializers/attr_encrypted_thread_safe_spec.rb
+++ b/spec/initializers/attr_encrypted_thread_safe_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AttrEncrypted do
+RSpec.describe AttrEncrypted do
describe '#encrypted_attributes' do
subject do
Class.new(ActiveRecord::Base) do
diff --git a/spec/initializers/database_config_spec.rb b/spec/initializers/database_config_spec.rb
index 7c0b280fdaf..29d499efcd3 100644
--- a/spec/initializers/database_config_spec.rb
+++ b/spec/initializers/database_config_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Database config initializer' do
+RSpec.describe 'Database config initializer' do
subject do
load Rails.root.join('config/initializers/database_config.rb')
end
diff --git a/spec/initializers/direct_upload_support_spec.rb b/spec/initializers/direct_upload_support_spec.rb
index 7db40f4b5ab..aa77c0905c9 100644
--- a/spec/initializers/direct_upload_support_spec.rb
+++ b/spec/initializers/direct_upload_support_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Direct upload support' do
+RSpec.describe 'Direct upload support' do
subject do
load Rails.root.join('config/initializers/direct_upload_support.rb')
end
diff --git a/spec/initializers/doorkeeper_spec.rb b/spec/initializers/doorkeeper_spec.rb
index 47c196cb3a3..164225a00b2 100644
--- a/spec/initializers/doorkeeper_spec.rb
+++ b/spec/initializers/doorkeeper_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require_relative '../../config/initializers/doorkeeper'
-describe Doorkeeper.configuration do
+RSpec.describe Doorkeeper.configuration do
describe '#default_scopes' do
it 'matches Gitlab::Auth::DEFAULT_SCOPES' do
expect(subject.default_scopes).to eq Gitlab::Auth::DEFAULT_SCOPES
diff --git a/spec/initializers/fog_google_https_private_urls_spec.rb b/spec/initializers/fog_google_https_private_urls_spec.rb
index 8a0d7ad8f15..4825525a3d8 100644
--- a/spec/initializers/fog_google_https_private_urls_spec.rb
+++ b/spec/initializers/fog_google_https_private_urls_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Fog::Storage::GoogleXML::File', :fog_requests do
+RSpec.describe 'Fog::Storage::GoogleXML::File', :fog_requests do
let(:storage) do
Fog.mock!
Fog::Storage.new(
diff --git a/spec/initializers/hangouts_chat_http_override_spec.rb b/spec/initializers/hangouts_chat_http_override_spec.rb
index 0eee891799f..42236c8c853 100644
--- a/spec/initializers/hangouts_chat_http_override_spec.rb
+++ b/spec/initializers/hangouts_chat_http_override_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'HangoutsChat::Sender Gitlab::HTTP override' do
+RSpec.describe 'HangoutsChat::Sender Gitlab::HTTP override' do
describe 'HangoutsChat::Sender::HTTP#post' do
it 'calls Gitlab::HTTP.post with default protection settings' do
webhook_url = 'https://example.gitlab.com'
diff --git a/spec/initializers/lograge_spec.rb b/spec/initializers/lograge_spec.rb
index 9e5eab4fc6b..de722764bf4 100644
--- a/spec/initializers/lograge_spec.rb
+++ b/spec/initializers/lograge_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'lograge', type: :request do
+RSpec.describe 'lograge', type: :request do
let(:headers) { { 'X-Request-ID' => 'new-correlation-id' } }
let(:large_params) do
@@ -152,5 +152,35 @@ describe 'lograge', type: :request do
expect(log_data['etag_route']).to eq(etag_route)
end
end
+
+ context 'with transaction' do
+ let(:transaction) { Gitlab::Metrics::WebTransaction.new({}) }
+
+ before do
+ allow(Gitlab::Metrics::Transaction).to receive(:current).and_return(transaction)
+ end
+
+ context 'when RequestStore is enabled', :request_store do
+ context 'with db payload' do
+ it 'includes db counters', :request_store do
+ ActiveRecord::Base.connection.execute('SELECT pg_sleep(0.1);')
+ subscriber.process_action(event)
+
+ expect(log_data).to include("db_count" => 1, "db_write_count" => 0, "db_cached_count" => 0)
+ end
+ end
+ end
+
+ context 'when RequestStore is disabled' do
+ context 'with db payload' do
+ it 'does not include db counters' do
+ ActiveRecord::Base.connection.execute('SELECT pg_sleep(0.1);')
+ subscriber.process_action(event)
+
+ expect(log_data).not_to include("db_count" => 1, "db_write_count" => 0, "db_cached_count" => 0)
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/initializers/mail_encoding_patch_spec.rb b/spec/initializers/mail_encoding_patch_spec.rb
index 41074af3503..efacaf6b1b4 100644
--- a/spec/initializers/mail_encoding_patch_spec.rb
+++ b/spec/initializers/mail_encoding_patch_spec.rb
@@ -5,7 +5,7 @@ require 'fast_spec_helper'
require 'mail'
require_relative '../../config/initializers/mail_encoding_patch.rb'
-describe 'Mail quoted-printable transfer encoding patch and Unicode characters' do
+RSpec.describe 'Mail quoted-printable transfer encoding patch and Unicode characters' do
shared_examples 'email encoding' do |email|
it 'enclosing in a new object does not change the encoded original' do
new_email = Mail.new(email)
diff --git a/spec/initializers/rest-client-hostname_override_spec.rb b/spec/initializers/rest-client-hostname_override_spec.rb
index 7e36656ba1c..187f18a6b1d 100644
--- a/spec/initializers/rest-client-hostname_override_spec.rb
+++ b/spec/initializers/rest-client-hostname_override_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'rest-client dns rebinding protection' do
+RSpec.describe 'rest-client dns rebinding protection' do
it_behaves_like 'a request using Gitlab::UrlBlocker' do
let(:http_method) { :get }
let(:url_blocked_error_class) { ArgumentError }
diff --git a/spec/initializers/secret_token_spec.rb b/spec/initializers/secret_token_spec.rb
index b7979144c72..362371e0962 100644
--- a/spec/initializers/secret_token_spec.rb
+++ b/spec/initializers/secret_token_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require_relative '../../config/initializers/01_secret_token'
-describe 'create_tokens' do
+RSpec.describe 'create_tokens' do
include StubENV
let(:secrets) { ActiveSupport::OrderedOptions.new }
@@ -19,6 +19,30 @@ describe 'create_tokens' do
allow(self).to receive(:exit)
end
+ describe 'ensure acknowledged secrets in any installations' do
+ let(:acknowledged_secrets) do
+ %w[secret_key_base otp_key_base db_key_base openid_connect_signing_key]
+ end
+
+ it 'does not allow to add a new secret without a proper handling' do
+ create_tokens
+
+ secrets_hash = YAML.load_file(Rails.root.join('config/secrets.yml'))
+
+ secrets_hash.each do |environment, secrets|
+ new_secrets = secrets.keys - acknowledged_secrets
+
+ expect(new_secrets).to be_empty,
+ <<~EOS
+ CAUTION:
+ It looks like you have just added new secret(s) #{new_secrets.inspect} to the secrets.yml.
+ Please read the development guide for GitLab secrets at doc/development/application_secrets.md before you proceed this change.
+ If you're absolutely sure that the change is safe, please add the new secrets to the 'acknowledged_secrets' in order to silence this warning.
+ EOS
+ end
+ end
+ end
+
context 'setting secret keys' do
context 'when none of the secrets exist' do
before do
diff --git a/spec/initializers/settings_spec.rb b/spec/initializers/settings_spec.rb
index 6cb45b4c86b..71ea12a41aa 100644
--- a/spec/initializers/settings_spec.rb
+++ b/spec/initializers/settings_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require_relative '../../config/initializers/1_settings' unless defined?(Settings)
-describe Settings do
+RSpec.describe Settings do
describe '#ldap' do
it 'can be accessed with dot syntax all the way down' do
expect(Gitlab.config.ldap.servers.main.label).to eq('ldap')
diff --git a/spec/initializers/trusted_proxies_spec.rb b/spec/initializers/trusted_proxies_spec.rb
index a2bd0ff9f1c..2786f034969 100644
--- a/spec/initializers/trusted_proxies_spec.rb
+++ b/spec/initializers/trusted_proxies_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'trusted_proxies' do
+RSpec.describe 'trusted_proxies' do
context 'with default config' do
before do
set_trusted_proxies([])
diff --git a/spec/initializers/zz_metrics_spec.rb b/spec/initializers/zz_metrics_spec.rb
index f41a807f1eb..ad93c30ee22 100644
--- a/spec/initializers/zz_metrics_spec.rb
+++ b/spec/initializers/zz_metrics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'instrument_classes' do
+RSpec.describe 'instrument_classes' do
let(:config) { double(:config) }
before do
diff --git a/spec/javascripts/boards/components/board_spec.js b/spec/javascripts/boards/components/board_spec.js
deleted file mode 100644
index 86a2a10b7a0..00000000000
--- a/spec/javascripts/boards/components/board_spec.js
+++ /dev/null
@@ -1,251 +0,0 @@
-import Vue from 'vue';
-import Board from '~/boards/components/board';
-import List from '~/boards/models/list';
-
-describe('Board component', () => {
- let vm;
-
- const createComponent = ({ gon = {}, collapsed = false, listType = 'backlog' } = {}) => {
- if (Object.prototype.hasOwnProperty.call(gon, 'current_user_id')) {
- window.gon = gon;
- } else {
- window.gon = {};
- }
- const el = document.createElement('div');
- document.body.appendChild(el);
-
- vm = new Board({
- propsData: {
- boardId: '1',
- disabled: false,
- issueLinkBase: '/',
- rootPath: '/',
- list: new List({
- id: 1,
- position: 0,
- title: 'test',
- list_type: listType,
- collapsed,
- }),
- },
- }).$mount(el);
- };
-
- const setUpTests = (done, opts = {}) => {
- loadFixtures('boards/show.html');
-
- createComponent(opts);
-
- Vue.nextTick(done);
- };
-
- const cleanUpTests = spy => {
- if (spy) {
- spy.calls.reset();
- }
-
- vm.$destroy();
-
- // remove the component from the DOM
- document.querySelector('.board').remove();
-
- localStorage.removeItem(`${vm.uniqueKey}.expanded`);
- };
-
- describe('List', () => {
- it('board is expandable when list type is closed', () => {
- expect(new List({ id: 1, list_type: 'closed' }).isExpandable).toBe(true);
- });
-
- it('board is expandable when list type is label', () => {
- expect(new List({ id: 1, list_type: 'closed' }).isExpandable).toBe(true);
- });
-
- it('board is not expandable when list type is blank', () => {
- expect(new List({ id: 1, list_type: 'blank' }).isExpandable).toBe(false);
- });
- });
-
- describe('when clicking the header', () => {
- beforeEach(done => {
- setUpTests(done);
- });
-
- afterEach(() => {
- cleanUpTests();
- });
-
- it('does not collapse', done => {
- vm.list.isExpanded = true;
- vm.$el.querySelector('.board-header').click();
-
- Vue.nextTick()
- .then(() => {
- expect(vm.$el.classList.contains('is-collapsed')).toBe(false);
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('when clicking the collapse icon', () => {
- beforeEach(done => {
- setUpTests(done);
- });
-
- afterEach(() => {
- cleanUpTests();
- });
-
- it('collapses', done => {
- Vue.nextTick()
- .then(() => {
- vm.$el.querySelector('.board-title-caret').click();
- })
- .then(() => {
- expect(vm.$el.classList.contains('is-collapsed')).toBe(true);
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('when clicking the expand icon', () => {
- beforeEach(done => {
- setUpTests(done);
- });
-
- afterEach(() => {
- cleanUpTests();
- });
-
- it('expands', done => {
- vm.list.isExpanded = false;
-
- Vue.nextTick()
- .then(() => {
- vm.$el.querySelector('.board-title-caret').click();
- })
- .then(() => {
- expect(vm.$el.classList.contains('is-collapsed')).toBe(false);
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('when collapsed is false', () => {
- beforeEach(done => {
- setUpTests(done);
- });
-
- afterEach(() => {
- cleanUpTests();
- });
-
- it('is expanded when collapsed is false', () => {
- expect(vm.list.isExpanded).toBe(true);
- expect(vm.$el.classList.contains('is-collapsed')).toBe(false);
- });
- });
-
- describe('when list type is blank', () => {
- beforeEach(done => {
- setUpTests(done, { listType: 'blank' });
- });
-
- afterEach(() => {
- cleanUpTests();
- });
-
- it('does not render add issue button when list type is blank', done => {
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.issue-count-badge-add-button')).toBeNull();
-
- done();
- });
- });
- });
-
- describe('when list type is backlog', () => {
- beforeEach(done => {
- setUpTests(done);
- });
-
- afterEach(() => {
- cleanUpTests();
- });
-
- it('board is expandable', () => {
- expect(vm.$el.classList.contains('is-expandable')).toBe(true);
- });
- });
-
- describe('when logged in', () => {
- let spy;
-
- beforeEach(done => {
- spy = spyOn(List.prototype, 'update');
- setUpTests(done, { gon: { current_user_id: 1 } });
- });
-
- afterEach(() => {
- cleanUpTests(spy);
- });
-
- it('calls list update', done => {
- Vue.nextTick()
- .then(() => {
- vm.$el.querySelector('.board-title-caret').click();
- })
- .then(() => {
- expect(vm.list.update).toHaveBeenCalledTimes(1);
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('when logged out', () => {
- let spy;
- beforeEach(done => {
- spy = spyOn(List.prototype, 'update');
- setUpTests(done, { collapsed: false });
- });
-
- afterEach(() => {
- cleanUpTests(spy);
- });
-
- // can only be one or the other cant toggle window.gon.current_user_id states.
- it('clicking on the caret does not call list update', done => {
- Vue.nextTick()
- .then(() => {
- vm.$el.querySelector('.board-title-caret').click();
- })
- .then(() => {
- expect(vm.list.update).toHaveBeenCalledTimes(0);
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('sets expanded to be the opposite of its value when toggleExpanded is called', done => {
- const expanded = true;
- vm.list.isExpanded = expanded;
- vm.toggleExpanded();
-
- Vue.nextTick()
- .then(() => {
- expect(vm.list.isExpanded).toBe(!expanded);
- expect(localStorage.getItem(`${vm.uniqueKey}.expanded`)).toBe(String(!expanded));
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('does render add issue button', () => {
- expect(vm.$el.querySelector('.issue-count-badge-add-button')).not.toBeNull();
- });
- });
-});
diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js
deleted file mode 100644
index 8b39ad1abb4..00000000000
--- a/spec/javascripts/boards/mock_data.js
+++ /dev/null
@@ -1 +0,0 @@
-export * from '../../frontend/boards/mock_data';
diff --git a/spec/javascripts/fly_out_nav_spec.js b/spec/javascripts/fly_out_nav_browser_spec.js
index afcf132bea3..f84cee72042 100644
--- a/spec/javascripts/fly_out_nav_spec.js
+++ b/spec/javascripts/fly_out_nav_browser_spec.js
@@ -1,3 +1,7 @@
+// this file can't be migrated to jest because it relies on the browser to perform integration tests:
+// (specifically getClientBoundingRect and mouse movements)
+// see: https://gitlab.com/groups/gitlab-org/-/epics/895#what-if-theres-a-karma-spec-which-is-simply-unmovable-to-jest-ie-it-is-dependent-on-a-running-browser-environment
+
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
import {
calculateTop,
diff --git a/spec/javascripts/helpers/class_spec_helper.js b/spec/javascripts/helpers/class_spec_helper.js
deleted file mode 100644
index 7a60d33b471..00000000000
--- a/spec/javascripts/helpers/class_spec_helper.js
+++ /dev/null
@@ -1,9 +0,0 @@
-export default class ClassSpecHelper {
- static itShouldBeAStaticMethod(base, method) {
- return it('should be a static method', () => {
- expect(Object.prototype.hasOwnProperty.call(base, method)).toBeTruthy();
- });
- }
-}
-
-window.ClassSpecHelper = ClassSpecHelper;
diff --git a/spec/javascripts/helpers/filtered_search_spec_helper.js b/spec/javascripts/helpers/filtered_search_spec_helper.js
deleted file mode 100644
index de17518ea51..00000000000
--- a/spec/javascripts/helpers/filtered_search_spec_helper.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from '../../frontend/helpers/filtered_search_spec_helper';
diff --git a/spec/javascripts/helpers/index.js b/spec/javascripts/helpers/index.js
deleted file mode 100644
index d2c5caf0bdb..00000000000
--- a/spec/javascripts/helpers/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import mountComponent, { mountComponentWithStore } from './vue_mount_component_helper';
-
-export { mountComponent, mountComponentWithStore };
diff --git a/spec/javascripts/helpers/locale_helper.js b/spec/javascripts/helpers/locale_helper.js
deleted file mode 100644
index 80047b06003..00000000000
--- a/spec/javascripts/helpers/locale_helper.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/* eslint-disable import/prefer-default-export */
-
-export const setLanguage = languageCode => {
- const htmlElement = document.querySelector('html');
-
- if (languageCode) {
- htmlElement.setAttribute('lang', languageCode);
- } else {
- htmlElement.removeAttribute('lang');
- }
-};
diff --git a/spec/javascripts/helpers/set_timeout_promise_helper.js b/spec/javascripts/helpers/set_timeout_promise_helper.js
deleted file mode 100644
index 47087619187..00000000000
--- a/spec/javascripts/helpers/set_timeout_promise_helper.js
+++ /dev/null
@@ -1,4 +0,0 @@
-export default (time = 0) =>
- new Promise(resolve => {
- setTimeout(resolve, time);
- });
diff --git a/spec/javascripts/helpers/text_helper.js b/spec/javascripts/helpers/text_helper.js
deleted file mode 100644
index e0fe18e5560..00000000000
--- a/spec/javascripts/helpers/text_helper.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * Replaces line break with an empty space
- * @param {*} data
- */
-export const removeBreakLine = data => data.replace(/\r?\n|\r/g, ' ');
-
-/**
- * Removes line breaks, spaces and trims the given text
- * @param {String} str
- * @returns {String}
- */
-export const trimText = str =>
- str
- .replace(/\r?\n|\r/g, '')
- .replace(/\s\s+/g, ' ')
- .trim();
-
-export const removeWhitespace = str => str.replace(/\s\s+/g, ' ');
diff --git a/spec/javascripts/helpers/tracking_helper.js b/spec/javascripts/helpers/tracking_helper.js
deleted file mode 100644
index ea322de46f4..00000000000
--- a/spec/javascripts/helpers/tracking_helper.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// No new code should be added to this file. Instead, modify the
-// file this one re-exports from. For more detail about why, see:
-// https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/31349
-
-export * from '../../frontend/helpers/tracking_helper';
diff --git a/spec/javascripts/helpers/user_mock_data_helper.js b/spec/javascripts/helpers/user_mock_data_helper.js
deleted file mode 100644
index 6999fa1f8a1..00000000000
--- a/spec/javascripts/helpers/user_mock_data_helper.js
+++ /dev/null
@@ -1,14 +0,0 @@
-export default {
- createNumberRandomUsers(numberUsers) {
- const users = [];
- for (let i = 0; i < numberUsers; i += 1) {
- users.push({
- avatar: 'https://gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
- id: i + 1,
- name: `GitLab User ${i}`,
- username: `gitlab${i}`,
- });
- }
- return users;
- },
-};
diff --git a/spec/javascripts/helpers/vue_mount_component_helper.js b/spec/javascripts/helpers/vue_mount_component_helper.js
deleted file mode 100644
index c1857115b61..00000000000
--- a/spec/javascripts/helpers/vue_mount_component_helper.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default } from '../../frontend/helpers/vue_mount_component_helper';
-export * from '../../frontend/helpers/vue_mount_component_helper';
diff --git a/spec/javascripts/helpers/vue_test_utils_helper.js b/spec/javascripts/helpers/vue_test_utils_helper.js
deleted file mode 100644
index 1f5d8716dd3..00000000000
--- a/spec/javascripts/helpers/vue_test_utils_helper.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// No new code should be added to this file. Instead, modify the
-// file this one re-exports from. For more detail about why, see:
-// https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/31349
-
-export * from '../../frontend/helpers/vue_test_utils_helper';
diff --git a/spec/javascripts/helpers/vuex_action_helper.js b/spec/javascripts/helpers/vuex_action_helper.js
deleted file mode 100644
index c5de31a4138..00000000000
--- a/spec/javascripts/helpers/vuex_action_helper.js
+++ /dev/null
@@ -1,102 +0,0 @@
-const noop = () => {};
-
-/**
- * Helper for testing action with expected mutations inspired in
- * https://vuex.vuejs.org/en/testing.html
- *
- * @param {Function} action to be tested
- * @param {Object} payload will be provided to the action
- * @param {Object} state will be provided to the action
- * @param {Array} [expectedMutations=[]] mutations expected to be committed
- * @param {Array} [expectedActions=[]] actions expected to be dispatched
- * @param {Function} [done=noop] to be executed after the tests
- * @return {Promise}
- *
- * @example
- * testAction(
- * actions.actionName, // action
- * { }, // mocked payload
- * state, //state
- * // expected mutations
- * [
- * { type: types.MUTATION}
- * { type: types.MUTATION_1, payload: jasmine.any(Number)}
- * ],
- * // expected actions
- * [
- * { type: 'actionName', payload: {param: 'foobar'}},
- * { type: 'actionName1'}
- * ]
- * done,
- * );
- *
- * @example
- * testAction(
- * actions.actionName, // action
- * { }, // mocked payload
- * state, //state
- * [ { type: types.MUTATION} ], // expected mutations
- * [], // expected actions
- * ).then(done)
- * .catch(done.fail);
- */
-export default (
- action,
- payload,
- state,
- expectedMutations = [],
- expectedActions = [],
- done = noop,
-) => {
- const mutations = [];
- const actions = [];
-
- // mock commit
- const commit = (type, mutationPayload) => {
- const mutation = { type };
-
- if (typeof mutationPayload !== 'undefined') {
- mutation.payload = mutationPayload;
- }
-
- mutations.push(mutation);
- };
-
- // mock dispatch
- const dispatch = (type, actionPayload) => {
- const dispatchedAction = { type };
-
- if (typeof actionPayload !== 'undefined') {
- dispatchedAction.payload = actionPayload;
- }
-
- actions.push(dispatchedAction);
- };
-
- const validateResults = () => {
- expect({
- mutations,
- actions,
- }).toEqual({
- mutations: expectedMutations,
- actions: expectedActions,
- });
- done();
- };
-
- const result = action(
- { commit, state, dispatch, rootState: state, rootGetters: state, getters: state },
- payload,
- );
-
- return new Promise(setImmediate)
- .then(() => result)
- .catch(error => {
- validateResults();
- throw error;
- })
- .then(data => {
- validateResults();
- return data;
- });
-};
diff --git a/spec/javascripts/helpers/wait_for_promises.js b/spec/javascripts/helpers/wait_for_promises.js
deleted file mode 100644
index 1d2b53fc770..00000000000
--- a/spec/javascripts/helpers/wait_for_promises.js
+++ /dev/null
@@ -1 +0,0 @@
-export default () => new Promise(resolve => requestAnimationFrame(resolve));
diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js
deleted file mode 100644
index f0ba46c058a..00000000000
--- a/spec/javascripts/jobs/mock_data.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default } from '../../frontend/jobs/mock_data';
-export * from '../../frontend/jobs/mock_data';
diff --git a/spec/javascripts/matchers.js b/spec/javascripts/matchers.js
index 7d1921cabcf..5b7b7dc78b1 100644
--- a/spec/javascripts/matchers.js
+++ b/spec/javascripts/matchers.js
@@ -1,61 +1,6 @@
import pixelmatch from 'pixelmatch';
export default {
- toContainText: () => ({
- compare(vm, text) {
- if (!(vm.$el instanceof HTMLElement)) {
- throw new Error('vm.$el is not a DOM element!');
- }
-
- const result = {
- pass: vm.$el.innerText.includes(text),
- };
- return result;
- },
- }),
- toHaveSpriteIcon: () => ({
- compare(element, iconName) {
- if (!iconName) {
- throw new Error('toHaveSpriteIcon is missing iconName argument!');
- }
-
- if (!(element instanceof HTMLElement)) {
- throw new Error(`${element} is not a DOM element!`);
- }
-
- const iconReferences = [].slice.apply(element.querySelectorAll('svg use'));
- const matchingIcon = iconReferences.find(reference =>
- reference.getAttribute('xlink:href').endsWith(`#${iconName}`),
- );
- const result = {
- pass: Boolean(matchingIcon),
- };
-
- if (result.pass) {
- result.message = `${element.outerHTML} contains the sprite icon "${iconName}"!`;
- } else {
- result.message = `${element.outerHTML} does not contain the sprite icon "${iconName}"!`;
-
- const existingIcons = iconReferences.map(reference => {
- const iconUrl = reference.getAttribute('xlink:href');
- return `"${iconUrl.replace(/^.+#/, '')}"`;
- });
- if (existingIcons.length > 0) {
- result.message += ` (only found ${existingIcons.join(',')})`;
- }
- }
-
- return result;
- },
- }),
- toRender: () => ({
- compare(vm) {
- const result = {
- pass: vm.$el.nodeType !== Node.COMMENT_NODE,
- };
- return result;
- },
- }),
toImageDiffEqual: () => {
const getImageData = img => {
const canvas = document.createElement('canvas');
diff --git a/spec/javascripts/monitoring/components/dashboard_resize_browser_spec.js b/spec/javascripts/monitoring/components/dashboard_resize_browser_spec.js
index 4416dbd014a..bbcdc0b879f 100644
--- a/spec/javascripts/monitoring/components/dashboard_resize_browser_spec.js
+++ b/spec/javascripts/monitoring/components/dashboard_resize_browser_spec.js
@@ -11,8 +11,8 @@ import MockAdapter from 'axios-mock-adapter';
import Dashboard from '~/monitoring/components/dashboard.vue';
import { createStore } from '~/monitoring/stores';
import axios from '~/lib/utils/axios_utils';
-import { mockApiEndpoint, propsData } from '../mock_data';
-import { metricsDashboardPayload } from '../fixture_data';
+import { mockApiEndpoint } from '../mock_data';
+import { metricsDashboardPayload, dashboardProps } from '../fixture_data';
import { setupStoreWithData } from '../store_utils';
const localVue = createLocalVue();
@@ -56,7 +56,7 @@ describe('Dashboard', () => {
component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
- ...propsData,
+ ...dashboardProps,
hasMetrics: true,
showPanels: true,
},
diff --git a/spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js b/spec/javascripts/vue_shared/components/tooltip_on_truncate_browser_spec.js
index 5f432f2a1b5..da964a2d5b9 100644
--- a/spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js
+++ b/spec/javascripts/vue_shared/components/tooltip_on_truncate_browser_spec.js
@@ -1,3 +1,7 @@
+// this file can't be migrated to jest because it relies on the browser to perform integration tests:
+// (specifically testing around css properties `overflow` and `white-space`)
+// see: https://gitlab.com/groups/gitlab-org/-/epics/895#what-if-theres-a-karma-spec-which-is-simply-unmovable-to-jest-ie-it-is-dependent-on-a-running-browser-environment
+
import { mount, shallowMount } from '@vue/test-utils';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
diff --git a/spec/lib/after_commit_queue_spec.rb b/spec/lib/after_commit_queue_spec.rb
index 8e9dfd90338..ca383808bfc 100644
--- a/spec/lib/after_commit_queue_spec.rb
+++ b/spec/lib/after_commit_queue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AfterCommitQueue do
+RSpec.describe AfterCommitQueue do
it 'runs after transaction is committed' do
called = false
test_proc = proc { called = true }
diff --git a/spec/lib/api/api_spec.rb b/spec/lib/api/api_spec.rb
index c83d068ca50..cf08eaa7653 100644
--- a/spec/lib/api/api_spec.rb
+++ b/spec/lib/api/api_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::API do
+RSpec.describe API::API do
describe '.prefix' do
it 'has a prefix defined' do
expect(described_class.prefix).to eq :api
diff --git a/spec/lib/api/entities/branch_spec.rb b/spec/lib/api/entities/branch_spec.rb
index 604f56c0cb2..e07b431964c 100644
--- a/spec/lib/api/entities/branch_spec.rb
+++ b/spec/lib/api/entities/branch_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Entities::Branch do
+RSpec.describe API::Entities::Branch do
describe '#as_json' do
subject { entity.as_json }
diff --git a/spec/lib/api/entities/deploy_key_spec.rb b/spec/lib/api/entities/deploy_key_spec.rb
index 704dabae63b..6427d6eac8f 100644
--- a/spec/lib/api/entities/deploy_key_spec.rb
+++ b/spec/lib/api/entities/deploy_key_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Entities::DeployKey do
+RSpec.describe API::Entities::DeployKey do
describe '#as_json' do
subject { entity.as_json }
diff --git a/spec/lib/api/entities/deploy_keys_project_spec.rb b/spec/lib/api/entities/deploy_keys_project_spec.rb
index a357467d7ce..57ffdc55736 100644
--- a/spec/lib/api/entities/deploy_keys_project_spec.rb
+++ b/spec/lib/api/entities/deploy_keys_project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Entities::DeployKeysProject do
+RSpec.describe API::Entities::DeployKeysProject do
describe '#as_json' do
subject { entity.as_json }
diff --git a/spec/lib/api/entities/design_management/design_spec.rb b/spec/lib/api/entities/design_management/design_spec.rb
index 50ca3b43c6a..fe449e3e9bc 100644
--- a/spec/lib/api/entities/design_management/design_spec.rb
+++ b/spec/lib/api/entities/design_management/design_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Entities::DesignManagement::Design do
+RSpec.describe API::Entities::DesignManagement::Design do
let_it_be(:design) { create(:design) }
let(:entity) { described_class.new(design, request: double) }
diff --git a/spec/lib/api/entities/job_request/image_spec.rb b/spec/lib/api/entities/job_request/image_spec.rb
index 092c181ae9c..f13eab6a752 100644
--- a/spec/lib/api/entities/job_request/image_spec.rb
+++ b/spec/lib/api/entities/job_request/image_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Entities::JobRequest::Image do
+RSpec.describe API::Entities::JobRequest::Image do
let(:ports) { [{ number: 80, protocol: 'http', name: 'name' }]}
let(:image) { double(name: 'image_name', entrypoint: ['foo'], ports: ports)}
let(:entity) { described_class.new(image) }
diff --git a/spec/lib/api/entities/job_request/port_spec.rb b/spec/lib/api/entities/job_request/port_spec.rb
index 40ab4cd6231..4820c4a691b 100644
--- a/spec/lib/api/entities/job_request/port_spec.rb
+++ b/spec/lib/api/entities/job_request/port_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::API::Entities::JobRequest::Port do
+RSpec.describe ::API::Entities::JobRequest::Port do
let(:port) { double(number: 80, protocol: 'http', name: 'name')}
let(:entity) { described_class.new(port) }
diff --git a/spec/lib/api/entities/merge_request_approvals_spec.rb b/spec/lib/api/entities/merge_request_approvals_spec.rb
new file mode 100644
index 00000000000..cbbb037100a
--- /dev/null
+++ b/spec/lib/api/entities/merge_request_approvals_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Entities::MergeRequestApprovals do
+ let(:user) { create(:user) }
+ let(:merge_request) { create(:merge_request) }
+
+ subject { described_class.new(merge_request, current_user: user).as_json }
+
+ before do
+ merge_request.project.add_developer(user)
+ end
+
+ it 'serializes an approved merge request' do
+ create(:approval, merge_request: merge_request, user: user)
+
+ is_expected.to eq({
+ user_has_approved: true,
+ user_can_approve: false,
+ approved: true,
+ approved_by: [{
+ user: API::Entities::UserBasic.new(user).as_json
+ }]
+ })
+ end
+
+ it 'serializes a merge request that is not approved' do
+ is_expected.to eq({
+ user_has_approved: false,
+ user_can_approve: true,
+ approved: false,
+ approved_by: []
+ })
+ end
+end
diff --git a/spec/lib/api/entities/merge_request_basic_spec.rb b/spec/lib/api/entities/merge_request_basic_spec.rb
new file mode 100644
index 00000000000..715fcf4bcdb
--- /dev/null
+++ b/spec/lib/api/entities/merge_request_basic_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::API::Entities::MergeRequestBasic do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:merge_request) { create(:merge_request) }
+ let_it_be(:labels) { create_list(:label, 3) }
+ let_it_be(:merge_requests) { create_list(:labeled_merge_request, 10, :unique_branches, :with_diffs, labels: labels) }
+
+ # This mimics the behavior of the `Grape::Entity` serializer
+ def present(obj)
+ described_class.new(obj).presented
+ end
+
+ context "with :with_api_entity_associations scope" do
+ let(:scope) { MergeRequest.with_api_entity_associations }
+
+ it "avoids N+1 queries" do
+ query = scope.find(merge_request.id)
+
+ control = ActiveRecord::QueryRecorder.new do
+ present(query).to_json
+ end
+
+ # stub the `head_commit_sha` as it will trigger a
+ # backward compatibility query that is out-of-scope
+ # for this test whenever it is `nil`
+ allow_any_instance_of(MergeRequestDiff).to receive(:head_commit_sha).and_return(Gitlab::Git::BLANK_SHA)
+
+ query = scope.all
+ batch = ActiveRecord::QueryRecorder.new do
+ entities = query.map(&method(:present))
+
+ entities.to_json
+ end
+
+ # The current threshold is 3 query per entity maximum.
+ expect(batch.count).to be_within(3 * query.count).of(control.count)
+ end
+ end
+end
diff --git a/spec/lib/api/entities/nuget/dependency_group_spec.rb b/spec/lib/api/entities/nuget/dependency_group_spec.rb
new file mode 100644
index 00000000000..5a649be846b
--- /dev/null
+++ b/spec/lib/api/entities/nuget/dependency_group_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Entities::Nuget::DependencyGroup do
+ let(:dependency_group) do
+ {
+ id: 'http://gitlab.com/Sandbox.App/1.0.0.json#dependencygroup',
+ type: 'PackageDependencyGroup',
+ target_framework: 'fwk test',
+ dependencies: [
+ {
+ id: 'http://gitlab.com/Sandbox.App/1.0.0.json#dependency',
+ type: 'PackageDependency',
+ name: 'Dependency',
+ range: '2.0.0'
+ }
+ ]
+ }
+ end
+
+ let(:expected) do
+ {
+ '@id': 'http://gitlab.com/Sandbox.App/1.0.0.json#dependencygroup',
+ '@type': 'PackageDependencyGroup',
+ 'targetFramework': 'fwk test',
+ 'dependencies': [
+ {
+ '@id': 'http://gitlab.com/Sandbox.App/1.0.0.json#dependency',
+ '@type': 'PackageDependency',
+ 'id': 'Dependency',
+ 'range': '2.0.0'
+ }
+ ]
+ }
+ end
+ let(:entity) { described_class.new(dependency_group) }
+
+ subject { entity.as_json }
+
+ it { is_expected.to eq(expected) }
+
+ context 'dependency group without target framework' do
+ let(:dependency_group_with_no_target_framework) { dependency_group.tap { |dg| dg[:target_framework] = nil } }
+ let(:expected_no_target_framework) { expected.except(:targetFramework) }
+ let(:entity) { described_class.new(dependency_group_with_no_target_framework) }
+
+ it { is_expected.to eq(expected_no_target_framework) }
+ end
+end
diff --git a/spec/lib/api/entities/nuget/dependency_spec.rb b/spec/lib/api/entities/nuget/dependency_spec.rb
new file mode 100644
index 00000000000..13897cc91f0
--- /dev/null
+++ b/spec/lib/api/entities/nuget/dependency_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Entities::Nuget::Dependency do
+ let(:dependency) do
+ {
+ id: 'http://gitlab.com/Sandbox.App/1.0.0.json#dependency',
+ type: 'PackageDependency',
+ name: 'Dependency',
+ range: '2.0.0'
+ }
+ end
+
+ let(:expected) do
+ {
+ '@id': 'http://gitlab.com/Sandbox.App/1.0.0.json#dependency',
+ '@type': 'PackageDependency',
+ 'id': 'Dependency',
+ 'range': '2.0.0'
+ }
+ end
+ let(:entity) { described_class.new(dependency) }
+
+ subject { entity.as_json }
+
+ it { is_expected.to eq(expected) }
+end
diff --git a/spec/lib/api/entities/nuget/metadatum_spec.rb b/spec/lib/api/entities/nuget/metadatum_spec.rb
new file mode 100644
index 00000000000..fe94ea3a69a
--- /dev/null
+++ b/spec/lib/api/entities/nuget/metadatum_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Entities::Nuget::Metadatum do
+ let(:metadatum) do
+ {
+ project_url: 'http://sandbox.com/project',
+ license_url: 'http://sandbox.com/license',
+ icon_url: 'http://sandbox.com/icon'
+ }
+ end
+ let(:expected) do
+ {
+ 'projectUrl': 'http://sandbox.com/project',
+ 'licenseUrl': 'http://sandbox.com/license',
+ 'iconUrl': 'http://sandbox.com/icon'
+ }
+ end
+ let(:entity) { described_class.new(metadatum) }
+
+ subject { entity.as_json }
+
+ it { is_expected.to eq(expected) }
+
+ %i[project_url license_url icon_url].each do |optional_field|
+ context "metadatum without #{optional_field}" do
+ let(:metadatum_without_a_field) { metadatum.except(optional_field) }
+ let(:expected_without_a_field) { expected.except(optional_field.to_s.camelize(:lower).to_sym) }
+ let(:entity) { described_class.new(metadatum_without_a_field) }
+
+ it { is_expected.to eq(expected_without_a_field) }
+ end
+ end
+end
diff --git a/spec/lib/api/entities/nuget/package_metadata_catalog_entry_spec.rb b/spec/lib/api/entities/nuget/package_metadata_catalog_entry_spec.rb
new file mode 100644
index 00000000000..c422b51bf3b
--- /dev/null
+++ b/spec/lib/api/entities/nuget/package_metadata_catalog_entry_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Entities::Nuget::PackageMetadataCatalogEntry do
+ let(:entry) do
+ {
+ json_url: 'http://sandbox.com/json/package',
+ authors: 'Authors',
+ dependency_groups: [],
+ package_name: 'PackageTest',
+ package_version: '1.2.3',
+ tags: 'tag1 tag2 tag3',
+ archive_url: 'http://sandbox.com/archive/package',
+ summary: 'Summary',
+ metadatum: {
+ project_url: 'http://sandbox.com/project',
+ license_url: 'http://sandbox.com/license',
+ icon_url: 'http://sandbox.com/icon'
+ }
+ }
+ end
+
+ let(:expected) do
+ {
+ '@id': 'http://sandbox.com/json/package',
+ 'id': 'PackageTest',
+ 'version': '1.2.3',
+ 'authors': 'Authors',
+ 'dependencyGroups': [],
+ 'tags': 'tag1 tag2 tag3',
+ 'packageContent': 'http://sandbox.com/archive/package',
+ 'summary': 'Summary',
+ 'projectUrl': 'http://sandbox.com/project',
+ 'licenseUrl': 'http://sandbox.com/license',
+ 'iconUrl': 'http://sandbox.com/icon'
+ }
+ end
+
+ subject { described_class.new(entry).as_json }
+
+ it { is_expected.to eq(expected) }
+end
diff --git a/spec/lib/api/entities/nuget/search_result_spec.rb b/spec/lib/api/entities/nuget/search_result_spec.rb
new file mode 100644
index 00000000000..2a760c70224
--- /dev/null
+++ b/spec/lib/api/entities/nuget/search_result_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Entities::Nuget::SearchResult do
+ let(:search_result) do
+ {
+ type: 'Package',
+ authors: 'Author',
+ name: 'PackageTest',
+ version: '1.2.3',
+ versions: [
+ {
+ json_url: 'http://sandbox.com/json/package',
+ downloads: 100,
+ version: '1.2.3'
+ }
+ ],
+ summary: 'Summary',
+ total_downloads: 100,
+ verified: true,
+ tags: 'tag1 tag2 tag3',
+ metadatum: {
+ project_url: 'http://sandbox.com/project',
+ license_url: 'http://sandbox.com/license',
+ icon_url: 'http://sandbox.com/icon'
+ }
+ }
+ end
+ let(:expected) do
+ {
+ '@type': 'Package',
+ 'authors': 'Author',
+ 'id': 'PackageTest',
+ 'title': 'PackageTest',
+ 'summary': 'Summary',
+ 'totalDownloads': 100,
+ 'verified': true,
+ 'version': '1.2.3',
+ 'tags': 'tag1 tag2 tag3',
+ 'projectUrl': 'http://sandbox.com/project',
+ 'licenseUrl': 'http://sandbox.com/license',
+ 'iconUrl': 'http://sandbox.com/icon',
+ 'versions': [
+ {
+ '@id': 'http://sandbox.com/json/package',
+ 'downloads': 100,
+ 'version': '1.2.3'
+ }
+ ]
+ }
+ end
+
+ subject { described_class.new(search_result).as_json }
+
+ it { is_expected.to eq(expected) }
+end
diff --git a/spec/lib/api/entities/project_import_failed_relation_spec.rb b/spec/lib/api/entities/project_import_failed_relation_spec.rb
index f8330713480..51a684c4564 100644
--- a/spec/lib/api/entities/project_import_failed_relation_spec.rb
+++ b/spec/lib/api/entities/project_import_failed_relation_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Entities::ProjectImportFailedRelation do
+RSpec.describe API::Entities::ProjectImportFailedRelation do
describe '#as_json' do
subject { entity.as_json }
diff --git a/spec/lib/api/entities/project_import_status_spec.rb b/spec/lib/api/entities/project_import_status_spec.rb
index a800d703496..5eda613a6a6 100644
--- a/spec/lib/api/entities/project_import_status_spec.rb
+++ b/spec/lib/api/entities/project_import_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Entities::ProjectImportStatus do
+RSpec.describe API::Entities::ProjectImportStatus do
describe '#as_json' do
subject { entity.as_json }
diff --git a/spec/lib/api/entities/project_repository_storage_move_spec.rb b/spec/lib/api/entities/project_repository_storage_move_spec.rb
index 1c38c8231d4..b0102dc376a 100644
--- a/spec/lib/api/entities/project_repository_storage_move_spec.rb
+++ b/spec/lib/api/entities/project_repository_storage_move_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Entities::ProjectRepositoryStorageMove do
+RSpec.describe API::Entities::ProjectRepositoryStorageMove do
describe '#as_json' do
subject { entity.as_json }
diff --git a/spec/lib/api/entities/release_spec.rb b/spec/lib/api/entities/release_spec.rb
index fa9e1e74f9b..d57c283c1f4 100644
--- a/spec/lib/api/entities/release_spec.rb
+++ b/spec/lib/api/entities/release_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Entities::Release do
+RSpec.describe API::Entities::Release do
let_it_be(:project) { create(:project) }
let(:release) { create(:release, project: project) }
let(:evidence) { release.evidences.first }
diff --git a/spec/lib/api/entities/snippet_spec.rb b/spec/lib/api/entities/snippet_spec.rb
index dada0942e49..bcb8c364392 100644
--- a/spec/lib/api/entities/snippet_spec.rb
+++ b/spec/lib/api/entities/snippet_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::API::Entities::Snippet do
+RSpec.describe ::API::Entities::Snippet do
let_it_be(:user) { create(:user) }
let_it_be(:personal_snippet) { create(:personal_snippet, :repository, author: user ) }
let_it_be(:project_snippet) { create(:project_snippet, :repository, author: user) }
@@ -21,6 +21,16 @@ describe ::API::Entities::Snippet do
it { expect(subject[:visibility]).to eq snippet.visibility }
it { expect(subject).to include(:author) }
+ context 'with snippet_multiple_files feature disabled' do
+ before do
+ stub_feature_flags(snippet_multiple_files: false)
+ end
+
+ it 'does not return files' do
+ expect(subject).not_to include(:files)
+ end
+ end
+
describe 'file_name' do
it 'returns attribute from repository' do
expect(subject[:file_name]).to eq snippet.blobs.first.path
@@ -62,6 +72,49 @@ describe ::API::Entities::Snippet do
end
end
end
+
+ describe 'files' do
+ let(:blob) { snippet.blobs.first }
+ let(:ref) { blob.repository.root_ref }
+
+ context 'when repository does not exist' do
+ it 'does not include the files attribute' do
+ allow(snippet).to receive(:repository_exists?).and_return(false)
+
+ expect(subject).not_to include(:files)
+ end
+ end
+
+ shared_examples 'snippet files' do
+ let(:file) { subject[:files].first }
+
+ it 'returns all snippet files' do
+ expect(subject[:files].count).to eq snippet.blobs.count
+ end
+
+ it 'has the file path' do
+ expect(file[:path]).to eq blob.path
+ end
+
+ it 'has the raw url' do
+ expect(file[:raw_url]).to match(raw_url)
+ end
+ end
+
+ context 'with PersonalSnippet' do
+ it_behaves_like 'snippet files' do
+ let(:snippet) { personal_snippet }
+ let(:raw_url) { "/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}" }
+ end
+ end
+
+ context 'with ProjectSnippet' do
+ it_behaves_like 'snippet files' do
+ let(:snippet) { project_snippet }
+ let(:raw_url) { "#{snippet.project.full_path}/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}" }
+ end
+ end
+ end
end
context 'with PersonalSnippet' do
diff --git a/spec/lib/api/entities/ssh_key_spec.rb b/spec/lib/api/entities/ssh_key_spec.rb
index 25a0fecfb75..768ad416fbe 100644
--- a/spec/lib/api/entities/ssh_key_spec.rb
+++ b/spec/lib/api/entities/ssh_key_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Entities::SSHKey do
+RSpec.describe API::Entities::SSHKey do
describe '#as_json' do
subject { entity.as_json }
diff --git a/spec/lib/api/entities/user_spec.rb b/spec/lib/api/entities/user_spec.rb
index 20524b197e0..99ffe0eb925 100644
--- a/spec/lib/api/entities/user_spec.rb
+++ b/spec/lib/api/entities/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Entities::User do
+RSpec.describe API::Entities::User do
let(:user) { create(:user) }
let(:current_user) { create(:user) }
diff --git a/spec/lib/api/helpers/common_helpers_spec.rb b/spec/lib/api/helpers/common_helpers_spec.rb
new file mode 100644
index 00000000000..5162d2f1000
--- /dev/null
+++ b/spec/lib/api/helpers/common_helpers_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Helpers::CommonHelpers do
+ include Rack::Test::Methods
+
+ subject do
+ Class.new(Grape::API) do
+ helpers API::Helpers::CommonHelpers
+
+ before do
+ coerce_nil_params_to_array!
+ end
+
+ params do
+ requires :id, type: String
+ optional :array, type: Array, coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce
+ optional :array_of_strings, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce
+ optional :array_of_ints, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce
+ end
+ get ":id" do
+ params.to_json
+ end
+ end
+ end
+
+ def app
+ subject
+ end
+
+ describe '.coerce_nil_params_to_array!' do
+ let(:json_response) { Gitlab::Json.parse(last_response.body) }
+
+ it 'converts all nil parameters to empty arrays' do
+ get '/test?array=&array_of_strings=&array_of_ints='
+
+ expect(json_response['array']).to eq([])
+ expect(json_response['array_of_strings']).to eq([])
+ expect(json_response['array_of_ints']).to eq([])
+ end
+
+ it 'leaves non-nil parameters alone' do
+ get '/test?array=&array_of_strings=test,me&array_of_ints=1,2'
+
+ expect(json_response['array']).to eq([])
+ expect(json_response['array_of_strings']).to eq(%w(test me))
+ expect(json_response['array_of_ints']).to eq([1, 2])
+ end
+ end
+end
diff --git a/spec/lib/api/helpers/graphql_helpers_spec.rb b/spec/lib/api/helpers/graphql_helpers_spec.rb
index c775ba6d5e8..678f4f8a3e3 100644
--- a/spec/lib/api/helpers/graphql_helpers_spec.rb
+++ b/spec/lib/api/helpers/graphql_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Helpers::GraphqlHelpers do
+RSpec.describe API::Helpers::GraphqlHelpers do
describe 'run_graphql!' do
let(:query) { '{ metadata { version } }' }
diff --git a/spec/lib/api/helpers/label_helpers_spec.rb b/spec/lib/api/helpers/label_helpers_spec.rb
index 138e9a22d70..007cb3248e2 100644
--- a/spec/lib/api/helpers/label_helpers_spec.rb
+++ b/spec/lib/api/helpers/label_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Helpers::LabelHelpers do
+RSpec.describe API::Helpers::LabelHelpers do
describe 'create_service_params' do
let(:label_helper) do
Class.new do
diff --git a/spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb b/spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb
new file mode 100644
index 00000000000..ccf96bcbad6
--- /dev/null
+++ b/spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Helpers::Packages::DependencyProxyHelpers do
+ let_it_be(:helper) { Class.new.include(described_class).new }
+
+ describe 'redirect_registry_request' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:options) { {} }
+
+ subject { helper.redirect_registry_request(forward_to_registry, package_type, options) { helper.fallback } }
+
+ shared_examples 'executing fallback' do
+ it 'redirects to package registry' do
+ expect(helper).to receive(:registry_url).never
+ expect(helper).to receive(:redirect).never
+ expect(helper).to receive(:fallback).once
+
+ subject
+ end
+ end
+
+ shared_examples 'executing redirect' do
+ it 'redirects to package registry' do
+ expect(helper).to receive(:registry_url).once
+ expect(helper).to receive(:redirect).once
+ expect(helper).to receive(:fallback).never
+
+ subject
+ end
+ end
+
+ context 'with npm packages' do
+ let(:package_type) { :npm }
+
+ where(:application_setting, :forward_to_registry, :example_name) do
+ true | true | 'executing redirect'
+ true | false | 'executing fallback'
+ false | true | 'executing fallback'
+ false | false | 'executing fallback'
+ end
+
+ with_them do
+ before do
+ stub_application_setting(npm_package_requests_forwarding: application_setting)
+ end
+
+ it_behaves_like params[:example_name]
+ end
+ end
+
+ context 'with non-forwardable packages' do
+ let(:forward_to_registry) { true }
+
+ before do
+ stub_application_setting(npm_package_requests_forwarding: true)
+ end
+
+ Packages::Package.package_types.keys.without('npm').each do |pkg_type|
+ context "#{pkg_type}" do
+ let(:package_type) { pkg_type }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(ArgumentError, "Can't build registry_url for package_type #{package_type}")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/api/helpers/packages_helpers_spec.rb b/spec/lib/api/helpers/packages_helpers_spec.rb
new file mode 100644
index 00000000000..0c51e25bad9
--- /dev/null
+++ b/spec/lib/api/helpers/packages_helpers_spec.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Helpers::PackagesHelpers do
+ let_it_be(:helper) { Class.new.include(described_class).new }
+ let_it_be(:project) { create(:project) }
+
+ describe 'authorize_packages_access!' do
+ subject { helper.authorize_packages_access!(project) }
+
+ it 'authorizes packages access' do
+ expect(helper).to receive(:require_packages_enabled!)
+ expect(helper).to receive(:authorize_read_package!).with(project)
+
+ expect(subject).to eq nil
+ end
+ end
+
+ %i[read_package create_package destroy_package].each do |action|
+ describe "authorize_#{action}!" do
+ subject { helper.send("authorize_#{action}!", project) }
+
+ it 'calls authorize!' do
+ expect(helper).to receive(:authorize!).with(action, project)
+
+ expect(subject).to eq nil
+ end
+ end
+ end
+
+ describe 'require_packages_enabled!' do
+ let(:packages_enabled) { true }
+
+ subject { helper.require_packages_enabled! }
+
+ before do
+ allow(::Gitlab.config.packages).to receive(:enabled).and_return(packages_enabled)
+ end
+
+ context 'with packages enabled' do
+ it "doesn't call not_found!" do
+ expect(helper).to receive(:not_found!).never
+
+ expect(subject).to eq nil
+ end
+ end
+
+ context 'with package disabled' do
+ let(:packages_enabled) { false }
+
+ it 'calls not_found!' do
+ expect(helper).to receive(:not_found!).once
+
+ subject
+ end
+ end
+ end
+
+ describe '#authorize_workhorse!' do
+ let_it_be(:headers) { {} }
+
+ subject { helper.authorize_workhorse!(subject: project) }
+
+ before do
+ allow(helper).to receive(:headers).and_return(headers)
+ end
+
+ it 'authorizes workhorse' do
+ expect(helper).to receive(:authorize_upload!).with(project)
+ expect(helper).to receive(:status).with(200)
+ expect(helper).to receive(:content_type).with(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(Gitlab::Workhorse).to receive(:verify_api_request!).with(headers)
+ expect(::Packages::PackageFileUploader).to receive(:workhorse_authorize).with(has_length: true)
+
+ expect(subject).to eq nil
+ end
+
+ context 'without length' do
+ subject { helper.authorize_workhorse!(subject: project, has_length: false) }
+
+ it 'authorizes workhorse' do
+ expect(helper).to receive(:authorize_upload!).with(project)
+ expect(helper).to receive(:status).with(200)
+ expect(helper).to receive(:content_type).with(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(Gitlab::Workhorse).to receive(:verify_api_request!).with(headers)
+ expect(::Packages::PackageFileUploader).to receive(:workhorse_authorize).with(has_length: false, maximum_size: ::API::Helpers::PackagesHelpers::MAX_PACKAGE_FILE_SIZE)
+
+ expect(subject).to eq nil
+ end
+ end
+ end
+
+ describe '#authorize_upload!' do
+ subject { helper.authorize_upload!(project) }
+
+ it 'authorizes the upload' do
+ expect(helper).to receive(:authorize_create_package!).with(project)
+ expect(helper).to receive(:require_gitlab_workhorse!)
+
+ expect(subject).to eq nil
+ end
+ end
+end
diff --git a/spec/lib/api/helpers/packages_manager_clients_helpers_spec.rb b/spec/lib/api/helpers/packages_manager_clients_helpers_spec.rb
new file mode 100644
index 00000000000..80be5f7d10a
--- /dev/null
+++ b/spec/lib/api/helpers/packages_manager_clients_helpers_spec.rb
@@ -0,0 +1,154 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Helpers::PackagesManagerClientsHelpers do
+ let_it_be(:personal_access_token) { create(:personal_access_token) }
+ let_it_be(:username) { personal_access_token.user.username }
+ let_it_be(:helper) { Class.new.include(described_class).new }
+ let(:password) { personal_access_token.token }
+
+ describe '#find_personal_access_token_from_http_basic_auth' do
+ let(:headers) { { Authorization: basic_http_auth(username, password) } }
+
+ subject { helper.find_personal_access_token_from_http_basic_auth }
+
+ before do
+ allow(helper).to receive(:headers).and_return(headers&.with_indifferent_access)
+ end
+
+ context 'with a valid Authorization header' do
+ it { is_expected.to eq personal_access_token }
+ end
+
+ context 'with an invalid Authorization header' do
+ where(:headers) do
+ [
+ [{ Authorization: 'Invalid' }],
+ [{}],
+ [nil]
+ ]
+ end
+
+ with_them do
+ it { is_expected.to be nil }
+ end
+ end
+
+ context 'with an unknown Authorization header' do
+ let(:password) { 'Unknown' }
+
+ it { is_expected.to be nil }
+ end
+ end
+
+ describe '#find_job_from_http_basic_auth' do
+ let_it_be(:user) { personal_access_token.user }
+
+ let(:job) { create(:ci_build, user: user) }
+ let(:password) { job.token }
+ let(:headers) { { Authorization: basic_http_auth(username, password) } }
+
+ subject { helper.find_job_from_http_basic_auth }
+
+ before do
+ allow(helper).to receive(:headers).and_return(headers&.with_indifferent_access)
+ end
+
+ context 'with a valid Authorization header' do
+ it { is_expected.to eq job }
+ end
+
+ context 'with an invalid Authorization header' do
+ where(:headers) do
+ [
+ [{ Authorization: 'Invalid' }],
+ [{}],
+ [nil]
+ ]
+ end
+
+ with_them do
+ it { is_expected.to be nil }
+ end
+ end
+
+ context 'with an unknown Authorization header' do
+ let(:password) { 'Unknown' }
+
+ it { is_expected.to be nil }
+ end
+ end
+
+ describe '#find_deploy_token_from_http_basic_auth' do
+ let_it_be(:deploy_token) { create(:deploy_token) }
+ let(:token) { deploy_token.token }
+ let(:headers) { { Authorization: basic_http_auth(deploy_token.username, token) } }
+
+ subject { helper.find_deploy_token_from_http_basic_auth }
+
+ before do
+ allow(helper).to receive(:headers).and_return(headers&.with_indifferent_access)
+ end
+
+ context 'with a valid Authorization header' do
+ it { is_expected.to eq deploy_token }
+ end
+
+ context 'with an invalid Authorization header' do
+ where(:headers) do
+ [
+ [{ Authorization: 'Invalid' }],
+ [{}],
+ [nil]
+ ]
+ end
+
+ with_them do
+ it { is_expected.to be nil }
+ end
+ end
+
+ context 'with an invalid token' do
+ let(:token) { 'Unknown' }
+
+ it { is_expected.to be nil }
+ end
+ end
+
+ describe '#uploaded_package_file' do
+ let_it_be(:params) { {} }
+
+ subject { helper.uploaded_package_file }
+
+ before do
+ allow(helper).to receive(:params).and_return(params)
+ end
+
+ context 'with valid uploaded package file' do
+ let_it_be(:uploaded_file) { Object.new }
+
+ before do
+ allow(UploadedFile).to receive(:from_params).and_return(uploaded_file)
+ end
+
+ it { is_expected.to be uploaded_file }
+ end
+
+ context 'with invalid uploaded package file' do
+ before do
+ allow(UploadedFile).to receive(:from_params).and_return(nil)
+ end
+
+ it 'fails with bad_request!' do
+ expect(helper).to receive(:bad_request!)
+
+ expect(subject).to be nil
+ end
+ end
+ end
+
+ def basic_http_auth(username, password)
+ ActionController::HttpAuthentication::Basic.encode_credentials(username, password)
+ end
+end
diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb
index 796c753d6c4..a008c1adeac 100644
--- a/spec/lib/api/helpers/pagination_spec.rb
+++ b/spec/lib/api/helpers/pagination_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Helpers::Pagination do
+RSpec.describe API::Helpers::Pagination do
subject { Class.new.include(described_class).new }
let(:paginator) { double('paginator') }
diff --git a/spec/lib/api/helpers/pagination_strategies_spec.rb b/spec/lib/api/helpers/pagination_strategies_spec.rb
index eaa71159714..e8a4243b407 100644
--- a/spec/lib/api/helpers/pagination_strategies_spec.rb
+++ b/spec/lib/api/helpers/pagination_strategies_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Helpers::PaginationStrategies do
+RSpec.describe API::Helpers::PaginationStrategies do
subject { Class.new.include(described_class).new }
let(:expected_result) { double("result") }
diff --git a/spec/lib/api/helpers/related_resources_helpers_spec.rb b/spec/lib/api/helpers/related_resources_helpers_spec.rb
index eeeb22abd10..a0dc69536b4 100644
--- a/spec/lib/api/helpers/related_resources_helpers_spec.rb
+++ b/spec/lib/api/helpers/related_resources_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Helpers::RelatedResourcesHelpers do
+RSpec.describe API::Helpers::RelatedResourcesHelpers do
subject(:helpers) do
Class.new.include(described_class).new
end
diff --git a/spec/lib/api/helpers/version_spec.rb b/spec/lib/api/helpers/version_spec.rb
index a9f33962537..a87a3c5a026 100644
--- a/spec/lib/api/helpers/version_spec.rb
+++ b/spec/lib/api/helpers/version_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Helpers::Version do
+RSpec.describe API::Helpers::Version do
describe '.new' do
it 'is possible to initialize it with existing API version' do
expect(described_class.new('v4').to_s).to eq 'v4'
diff --git a/spec/lib/api/helpers_spec.rb b/spec/lib/api/helpers_spec.rb
index 3595d06a184..8cba1e0794a 100644
--- a/spec/lib/api/helpers_spec.rb
+++ b/spec/lib/api/helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Helpers do
+RSpec.describe API::Helpers do
subject { Class.new.include(described_class).new }
describe '#find_project' do
diff --git a/spec/lib/api/support/git_access_actor_spec.rb b/spec/lib/api/support/git_access_actor_spec.rb
index 69637947c79..70753856419 100644
--- a/spec/lib/api/support/git_access_actor_spec.rb
+++ b/spec/lib/api/support/git_access_actor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Support::GitAccessActor do
+RSpec.describe API::Support::GitAccessActor do
let(:user) { nil }
let(:key) { nil }
diff --git a/spec/lib/api/validations/validators/absence_spec.rb b/spec/lib/api/validations/validators/absence_spec.rb
index 31120979d4f..bfecaf4e243 100644
--- a/spec/lib/api/validations/validators/absence_spec.rb
+++ b/spec/lib/api/validations/validators/absence_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Validations::Validators::Absence do
+RSpec.describe API::Validations::Validators::Absence do
include ApiValidatorsHelpers
subject do
diff --git a/spec/lib/api/validations/validators/array_none_any_spec.rb b/spec/lib/api/validations/validators/array_none_any_spec.rb
index 03f1c63b117..833adea6554 100644
--- a/spec/lib/api/validations/validators/array_none_any_spec.rb
+++ b/spec/lib/api/validations/validators/array_none_any_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Validations::Validators::ArrayNoneAny do
+RSpec.describe API::Validations::Validators::ArrayNoneAny do
include ApiValidatorsHelpers
subject do
diff --git a/spec/lib/api/validations/validators/file_path_spec.rb b/spec/lib/api/validations/validators/file_path_spec.rb
index 8679f102d23..2c79260b8d5 100644
--- a/spec/lib/api/validations/validators/file_path_spec.rb
+++ b/spec/lib/api/validations/validators/file_path_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Validations::Validators::FilePath do
+RSpec.describe API::Validations::Validators::FilePath do
include ApiValidatorsHelpers
subject do
diff --git a/spec/lib/api/validations/validators/git_ref_spec.rb b/spec/lib/api/validations/validators/git_ref_spec.rb
index 84de6272fe1..0d2d9e8f39a 100644
--- a/spec/lib/api/validations/validators/git_ref_spec.rb
+++ b/spec/lib/api/validations/validators/git_ref_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Validations::Validators::GitRef do
+RSpec.describe API::Validations::Validators::GitRef do
include ApiValidatorsHelpers
subject do
diff --git a/spec/lib/api/validations/validators/git_sha_spec.rb b/spec/lib/api/validations/validators/git_sha_spec.rb
index 39c2fe1dcf9..ae6be52a4c7 100644
--- a/spec/lib/api/validations/validators/git_sha_spec.rb
+++ b/spec/lib/api/validations/validators/git_sha_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Validations::Validators::GitSha do
+RSpec.describe API::Validations::Validators::GitSha do
include ApiValidatorsHelpers
let(:sha) { RepoHelpers.sample_commit.id }
diff --git a/spec/lib/api/validations/validators/integer_none_any_spec.rb b/spec/lib/api/validations/validators/integer_none_any_spec.rb
index a42f69fd96e..33fa7688d18 100644
--- a/spec/lib/api/validations/validators/integer_none_any_spec.rb
+++ b/spec/lib/api/validations/validators/integer_none_any_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Validations::Validators::IntegerNoneAny do
+RSpec.describe API::Validations::Validators::IntegerNoneAny do
include ApiValidatorsHelpers
subject do
diff --git a/spec/lib/api/validations/validators/limit_spec.rb b/spec/lib/api/validations/validators/limit_spec.rb
index 600f74e1fb2..d71dde470cc 100644
--- a/spec/lib/api/validations/validators/limit_spec.rb
+++ b/spec/lib/api/validations/validators/limit_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Validations::Validators::Limit do
+RSpec.describe API::Validations::Validators::Limit do
include ApiValidatorsHelpers
subject do
diff --git a/spec/lib/api/validations/validators/untrusted_regexp_spec.rb b/spec/lib/api/validations/validators/untrusted_regexp_spec.rb
index 491bf94fd79..def67c94de0 100644
--- a/spec/lib/api/validations/validators/untrusted_regexp_spec.rb
+++ b/spec/lib/api/validations/validators/untrusted_regexp_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Validations::Validators::UntrustedRegexp do
+RSpec.describe API::Validations::Validators::UntrustedRegexp do
include ApiValidatorsHelpers
subject do
diff --git a/spec/lib/backup/files_spec.rb b/spec/lib/backup/files_spec.rb
index b75f3bafeef..a7374b82ce0 100644
--- a/spec/lib/backup/files_spec.rb
+++ b/spec/lib/backup/files_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Backup::Files do
+RSpec.describe Backup::Files do
let(:progress) { StringIO.new }
let!(:project) { create(:project) }
diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb
index b86e92d5969..38a5c30506b 100644
--- a/spec/lib/backup/manager_spec.rb
+++ b/spec/lib/backup/manager_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Backup::Manager do
+RSpec.describe Backup::Manager do
include StubENV
let(:progress) { StringIO.new }
diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb
index e0afa256581..c073a45bf68 100644
--- a/spec/lib/backup/repository_spec.rb
+++ b/spec/lib/backup/repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Backup::Repository do
+RSpec.describe Backup::Repository do
let(:progress) { StringIO.new }
let!(:project) { create(:project, :wiki_repo) }
diff --git a/spec/lib/backup/uploads_spec.rb b/spec/lib/backup/uploads_spec.rb
index 1f49baeff69..7c2d715b580 100644
--- a/spec/lib/backup/uploads_spec.rb
+++ b/spec/lib/backup/uploads_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Backup::Uploads do
+RSpec.describe Backup::Uploads do
let(:progress) { StringIO.new }
subject(:backup) { described_class.new(progress) }
diff --git a/spec/lib/banzai/color_parser_spec.rb b/spec/lib/banzai/color_parser_spec.rb
index d9202ce77db..95b3955d8fe 100644
--- a/spec/lib/banzai/color_parser_spec.rb
+++ b/spec/lib/banzai/color_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ColorParser do
+RSpec.describe Banzai::ColorParser do
describe '.parse' do
context 'HEX format' do
[
diff --git a/spec/lib/banzai/commit_renderer_spec.rb b/spec/lib/banzai/commit_renderer_spec.rb
index e5a16b167be..a10dd6eb3a2 100644
--- a/spec/lib/banzai/commit_renderer_spec.rb
+++ b/spec/lib/banzai/commit_renderer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::CommitRenderer do
+RSpec.describe Banzai::CommitRenderer do
describe '.render', :clean_gitlab_redis_cache do
it 'renders a commit description and title' do
user = build(:user)
diff --git a/spec/lib/banzai/cross_project_reference_spec.rb b/spec/lib/banzai/cross_project_reference_spec.rb
index cf41af7e7a1..95b78ceb5d5 100644
--- a/spec/lib/banzai/cross_project_reference_spec.rb
+++ b/spec/lib/banzai/cross_project_reference_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::CrossProjectReference do
+RSpec.describe Banzai::CrossProjectReference do
let(:including_class) { Class.new.include(described_class).new }
before do
diff --git a/spec/lib/banzai/filter/absolute_link_filter_spec.rb b/spec/lib/banzai/filter/absolute_link_filter_spec.rb
index b61bd496dba..2cb70850dca 100644
--- a/spec/lib/banzai/filter/absolute_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/absolute_link_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::AbsoluteLinkFilter do
+RSpec.describe Banzai::Filter::AbsoluteLinkFilter do
def filter(doc, context = {})
described_class.call(doc, context)
end
diff --git a/spec/lib/banzai/filter/abstract_reference_filter_spec.rb b/spec/lib/banzai/filter/abstract_reference_filter_spec.rb
index 6890a70518b..797f1c8d52f 100644
--- a/spec/lib/banzai/filter/abstract_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/abstract_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::AbstractReferenceFilter do
+RSpec.describe Banzai::Filter::AbstractReferenceFilter do
let_it_be(:project) { create(:project) }
let(:doc) { Nokogiri::HTML.fragment('') }
diff --git a/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb b/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb
index bd06dae26ba..334d5c59828 100644
--- a/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb
+++ b/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::AsciiDocPostProcessingFilter do
+RSpec.describe Banzai::Filter::AsciiDocPostProcessingFilter do
include FilterSpecHelper
it "adds class for elements with data-math-style" do
diff --git a/spec/lib/banzai/filter/asset_proxy_filter_spec.rb b/spec/lib/banzai/filter/asset_proxy_filter_spec.rb
index ff2346fe1ba..2a4ee28130b 100644
--- a/spec/lib/banzai/filter/asset_proxy_filter_spec.rb
+++ b/spec/lib/banzai/filter/asset_proxy_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::AssetProxyFilter do
+RSpec.describe Banzai::Filter::AssetProxyFilter do
include FilterSpecHelper
def image(path)
diff --git a/spec/lib/banzai/filter/audio_link_filter_spec.rb b/spec/lib/banzai/filter/audio_link_filter_spec.rb
index a8459137169..4198a50e980 100644
--- a/spec/lib/banzai/filter/audio_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/audio_link_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::AudioLinkFilter do
+RSpec.describe Banzai::Filter::AudioLinkFilter do
def filter(doc, contexts = {})
contexts.reverse_merge!({
project: project
diff --git a/spec/lib/banzai/filter/autolink_filter_spec.rb b/spec/lib/banzai/filter/autolink_filter_spec.rb
index be6192f9ead..ba15860f3c9 100644
--- a/spec/lib/banzai/filter/autolink_filter_spec.rb
+++ b/spec/lib/banzai/filter/autolink_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::AutolinkFilter do
+RSpec.describe Banzai::Filter::AutolinkFilter do
include FilterSpecHelper
let(:link) { 'http://about.gitlab.com/' }
diff --git a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
index 8e955ec3b6b..e736943914b 100644
--- a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
+++ b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::BlockquoteFenceFilter do
+RSpec.describe Banzai::Filter::BlockquoteFenceFilter do
include FilterSpecHelper
it 'converts blockquote fences to blockquote lines' do
diff --git a/spec/lib/banzai/filter/broadcast_message_placeholders_filter_spec.rb b/spec/lib/banzai/filter/broadcast_message_placeholders_filter_spec.rb
index 1a90abc12d9..a2d35eaa6b6 100644
--- a/spec/lib/banzai/filter/broadcast_message_placeholders_filter_spec.rb
+++ b/spec/lib/banzai/filter/broadcast_message_placeholders_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::BroadcastMessagePlaceholdersFilter do
+RSpec.describe Banzai::Filter::BroadcastMessagePlaceholdersFilter do
include FilterSpecHelper
subject { filter(text, current_user: user, broadcast_message_placeholders: true).to_html }
diff --git a/spec/lib/banzai/filter/broadcast_message_sanitization_filter_spec.rb b/spec/lib/banzai/filter/broadcast_message_sanitization_filter_spec.rb
index 317ac7ef854..1f65268bd3c 100644
--- a/spec/lib/banzai/filter/broadcast_message_sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/broadcast_message_sanitization_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::BroadcastMessageSanitizationFilter do
+RSpec.describe Banzai::Filter::BroadcastMessageSanitizationFilter do
include FilterSpecHelper
it_behaves_like 'default whitelist'
diff --git a/spec/lib/banzai/filter/color_filter_spec.rb b/spec/lib/banzai/filter/color_filter_spec.rb
index f8931d37b99..dced3671323 100644
--- a/spec/lib/banzai/filter/color_filter_spec.rb
+++ b/spec/lib/banzai/filter/color_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::ColorFilter, lib: true do
+RSpec.describe Banzai::Filter::ColorFilter, lib: true do
include FilterSpecHelper
let(:color) { '#F00' }
diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
index 5cfb0e6e6f7..f04d3212437 100644
--- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::CommitRangeReferenceFilter do
+RSpec.describe Banzai::Filter::CommitRangeReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
index 63ec597a0ba..925fd031d95 100644
--- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::CommitReferenceFilter do
+RSpec.describe Banzai::Filter::CommitReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/lib/banzai/filter/commit_trailers_filter_spec.rb b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
index 192d00805e0..03a6cc34962 100644
--- a/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'ffaker'
-describe Banzai::Filter::CommitTrailersFilter do
+RSpec.describe Banzai::Filter::CommitTrailersFilter do
include FilterSpecHelper
include CommitTrailersSpecHelper
diff --git a/spec/lib/banzai/filter/design_reference_filter_spec.rb b/spec/lib/banzai/filter/design_reference_filter_spec.rb
index 8a6c2e3b3f9..1b558754932 100644
--- a/spec/lib/banzai/filter/design_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/design_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::DesignReferenceFilter do
+RSpec.describe Banzai::Filter::DesignReferenceFilter do
include FilterSpecHelper
include DesignManagementTestHelpers
diff --git a/spec/lib/banzai/filter/emoji_filter_spec.rb b/spec/lib/banzai/filter/emoji_filter_spec.rb
index 4e163668a28..d78763b6939 100644
--- a/spec/lib/banzai/filter/emoji_filter_spec.rb
+++ b/spec/lib/banzai/filter/emoji_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::EmojiFilter do
+RSpec.describe Banzai::Filter::EmojiFilter do
include FilterSpecHelper
it 'replaces supported name emoji' do
diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
index a70c820f97a..7d8fb183dbb 100644
--- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::ExternalIssueReferenceFilter do
+RSpec.describe Banzai::Filter::ExternalIssueReferenceFilter do
include FilterSpecHelper
shared_examples_for "external issue tracker" do
diff --git a/spec/lib/banzai/filter/external_link_filter_spec.rb b/spec/lib/banzai/filter/external_link_filter_spec.rb
index 4b2500b31f7..630730dfc1a 100644
--- a/spec/lib/banzai/filter/external_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_link_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-shared_examples 'an external link with rel attribute' do
+RSpec.shared_examples 'an external link with rel attribute' do
it 'adds rel="nofollow" to external links' do
expect(doc.at_css('a')).to have_attribute('rel')
expect(doc.at_css('a')['rel']).to include 'nofollow'
@@ -19,7 +19,7 @@ shared_examples 'an external link with rel attribute' do
end
end
-describe Banzai::Filter::ExternalLinkFilter do
+RSpec.describe Banzai::Filter::ExternalLinkFilter do
include FilterSpecHelper
it 'ignores elements without an href attribute' do
diff --git a/spec/lib/banzai/filter/footnote_filter_spec.rb b/spec/lib/banzai/filter/footnote_filter_spec.rb
index c6dcb4e46fd..01b7319fab1 100644
--- a/spec/lib/banzai/filter/footnote_filter_spec.rb
+++ b/spec/lib/banzai/filter/footnote_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::FootnoteFilter do
+RSpec.describe Banzai::Filter::FootnoteFilter do
include FilterSpecHelper
# first[^1] and second[^second]
diff --git a/spec/lib/banzai/filter/front_matter_filter_spec.rb b/spec/lib/banzai/filter/front_matter_filter_spec.rb
index 1b5b1770615..3f966c94dd3 100644
--- a/spec/lib/banzai/filter/front_matter_filter_spec.rb
+++ b/spec/lib/banzai/filter/front_matter_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::FrontMatterFilter do
+RSpec.describe Banzai::Filter::FrontMatterFilter do
include FilterSpecHelper
it 'allows for `encoding:` before the front matter' do
diff --git a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
index 00d8b871224..2576dd1bf07 100644
--- a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
+++ b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::GollumTagsFilter do
+RSpec.describe Banzai::Filter::GollumTagsFilter do
include FilterSpecHelper
let(:project) { create(:project) }
diff --git a/spec/lib/banzai/filter/html_entity_filter_spec.rb b/spec/lib/banzai/filter/html_entity_filter_spec.rb
index 6017380725d..d88fa21cde7 100644
--- a/spec/lib/banzai/filter/html_entity_filter_spec.rb
+++ b/spec/lib/banzai/filter/html_entity_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::HtmlEntityFilter do
+RSpec.describe Banzai::Filter::HtmlEntityFilter do
include FilterSpecHelper
let(:unescaped) { 'foo <strike attr="foo">&&amp;&</strike>' }
diff --git a/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb b/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb
index 6475fd14ce4..9f5aa558f24 100644
--- a/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb
+++ b/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::ImageLazyLoadFilter do
+RSpec.describe Banzai::Filter::ImageLazyLoadFilter do
include FilterSpecHelper
def image(path)
diff --git a/spec/lib/banzai/filter/image_link_filter_spec.rb b/spec/lib/banzai/filter/image_link_filter_spec.rb
index 011e3a1e2da..5c04f6b2b3e 100644
--- a/spec/lib/banzai/filter/image_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/image_link_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::ImageLinkFilter do
+RSpec.describe Banzai::Filter::ImageLinkFilter do
include FilterSpecHelper
def image(path)
diff --git a/spec/lib/banzai/filter/inline_cluster_metrics_filter_spec.rb b/spec/lib/banzai/filter/inline_cluster_metrics_filter_spec.rb
new file mode 100644
index 00000000000..fe048daa601
--- /dev/null
+++ b/spec/lib/banzai/filter/inline_cluster_metrics_filter_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Banzai::Filter::InlineClusterMetricsFilter do
+ include FilterSpecHelper
+
+ let!(:cluster) { create(:cluster) }
+ let!(:project) { create(:project) }
+ let(:params) { [project.namespace.path, project.path, cluster.id] }
+ let(:query_params) { { group: 'Food metrics', title: 'Pizza Consumption', y_label: 'Slice Count' } }
+ let(:trigger_url) { urls.namespace_project_cluster_url(*params, **query_params) }
+ let(:dashboard_url) do
+ urls.metrics_dashboard_namespace_project_cluster_url(
+ *params,
+ **{
+ embedded: 'true',
+ cluster_type: 'project',
+ format: :json
+ }.merge(query_params)
+ )
+ end
+
+ it_behaves_like 'a metrics embed filter'
+end
diff --git a/spec/lib/banzai/filter/inline_diff_filter_spec.rb b/spec/lib/banzai/filter/inline_diff_filter_spec.rb
index c09065fb746..1ef00139db2 100644
--- a/spec/lib/banzai/filter/inline_diff_filter_spec.rb
+++ b/spec/lib/banzai/filter/inline_diff_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::InlineDiffFilter do
+RSpec.describe Banzai::Filter::InlineDiffFilter do
include FilterSpecHelper
it 'adds inline diff span tags for deletions when using square brackets' do
diff --git a/spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb b/spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb
index 28bf5bd3e92..8bdb24ab08c 100644
--- a/spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb
+++ b/spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::InlineGrafanaMetricsFilter do
+RSpec.describe Banzai::Filter::InlineGrafanaMetricsFilter do
include FilterSpecHelper
let_it_be(:project) { create(:project) }
diff --git a/spec/lib/banzai/filter/inline_metrics_filter_spec.rb b/spec/lib/banzai/filter/inline_metrics_filter_spec.rb
index 1546a5e88ed..9b0b95b9da2 100644
--- a/spec/lib/banzai/filter/inline_metrics_filter_spec.rb
+++ b/spec/lib/banzai/filter/inline_metrics_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::InlineMetricsFilter do
+RSpec.describe Banzai::Filter::InlineMetricsFilter do
include FilterSpecHelper
let(:params) { ['foo', 'bar', 12] }
diff --git a/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb
index 9d8cd729958..cafcaef8ae2 100644
--- a/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb
+++ b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::InlineMetricsRedactorFilter do
+RSpec.describe Banzai::Filter::InlineMetricsRedactorFilter do
include FilterSpecHelper
let_it_be(:project) { create(:project) }
@@ -29,6 +29,26 @@ describe Banzai::Filter::InlineMetricsRedactorFilter do
it_behaves_like 'retains the embed placeholder when applicable'
end
+ context 'for a cluster metric embed' do
+ let_it_be(:cluster) { create(:cluster, :provided_by_gcp, :project, projects: [project]) }
+ let(:params) { [project.namespace.path, project.path, cluster.id] }
+ let(:query_params) { { group: 'Cluster Health', title: 'CPU Usage', y_label: 'CPU (cores)' } }
+ let(:url) { urls.metrics_dashboard_namespace_project_cluster_url(*params, **query_params) }
+
+ context 'with user who can read cluster' do
+ it_behaves_like 'redacts the embed placeholder'
+ it_behaves_like 'retains the embed placeholder when applicable'
+ end
+
+ context 'without user who can read cluster' do
+ let(:doc) { filter(input, current_user: create(:user)) }
+
+ it 'redacts the embed placeholder' do
+ expect(doc.to_s).to be_empty
+ end
+ end
+ end
+
context 'the user has requisite permissions' do
let(:user) { create(:user) }
let(:doc) { filter(input, current_user: user) }
diff --git a/spec/lib/banzai/filter/issuable_state_filter_spec.rb b/spec/lib/banzai/filter/issuable_state_filter_spec.rb
index 5950b6878ef..a3851fd7cca 100644
--- a/spec/lib/banzai/filter/issuable_state_filter_spec.rb
+++ b/spec/lib/banzai/filter/issuable_state_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::IssuableStateFilter do
+RSpec.describe Banzai::Filter::IssuableStateFilter do
include ActionView::Helpers::UrlHelper
include FilterSpecHelper
diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
index 603da2b4421..98955d5cde9 100644
--- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::IssueReferenceFilter do
+RSpec.describe Banzai::Filter::IssueReferenceFilter do
include FilterSpecHelper
include DesignManagementTestHelpers
diff --git a/spec/lib/banzai/filter/jira_import/adf_to_commonmark_filter_spec.rb b/spec/lib/banzai/filter/jira_import/adf_to_commonmark_filter_spec.rb
new file mode 100644
index 00000000000..287b5774048
--- /dev/null
+++ b/spec/lib/banzai/filter/jira_import/adf_to_commonmark_filter_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Banzai::Filter::JiraImport::AdfToCommonmarkFilter do
+ include FilterSpecHelper
+
+ let_it_be(:fixtures_path) { 'lib/kramdown/atlassian_document_format' }
+
+ it 'renders a complex document' do
+ source = fixture_file(File.join(fixtures_path, 'complex_document.json'))
+ target = fixture_file(File.join(fixtures_path, 'complex_document.md'))
+
+ expect(filter(source)).to eq target
+ end
+
+ it 'renders original source when it is invalid JSON' do
+ source = fixture_file(File.join(fixtures_path, 'invalid_json.json'))
+
+ expect(filter(source)).to eq "Invalid Atlassian Document Format JSON\n\n#{source}"
+ end
+
+ it 'renders original source when missing document node' do
+ source = fixture_file(File.join(fixtures_path, 'invalid_no_doc.json'))
+
+ expect(filter(source)).to eq "Invalid Atlassian Document Format JSON\n\n#{source}"
+ end
+end
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index 0b697ab2040..dadf98d9b76 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'html/pipeline'
-describe Banzai::Filter::LabelReferenceFilter do
+RSpec.describe Banzai::Filter::LabelReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project, :public, name: 'sample-project') }
diff --git a/spec/lib/banzai/filter/markdown_filter_spec.rb b/spec/lib/banzai/filter/markdown_filter_spec.rb
index d0a43564903..8d01a651651 100644
--- a/spec/lib/banzai/filter/markdown_filter_spec.rb
+++ b/spec/lib/banzai/filter/markdown_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::MarkdownFilter do
+RSpec.describe Banzai::Filter::MarkdownFilter do
include FilterSpecHelper
describe 'markdown engine from context' do
diff --git a/spec/lib/banzai/filter/math_filter_spec.rb b/spec/lib/banzai/filter/math_filter_spec.rb
index c8fd92edcdf..9f6688f4f7d 100644
--- a/spec/lib/banzai/filter/math_filter_spec.rb
+++ b/spec/lib/banzai/filter/math_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::MathFilter do
+RSpec.describe Banzai::Filter::MathFilter do
include FilterSpecHelper
it 'leaves regular inline code unchanged' do
diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
index 12ee952b10e..f24fcf98b1f 100644
--- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::MergeRequestReferenceFilter do
+RSpec.describe Banzai::Filter::MergeRequestReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/filter/mermaid_filter_spec.rb b/spec/lib/banzai/filter/mermaid_filter_spec.rb
index ae6725cc14c..c9bfcffe98f 100644
--- a/spec/lib/banzai/filter/mermaid_filter_spec.rb
+++ b/spec/lib/banzai/filter/mermaid_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::MermaidFilter do
+RSpec.describe Banzai::Filter::MermaidFilter do
include FilterSpecHelper
it 'adds `js-render-mermaid` class to the `code` tag' do
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
index 0c8413adcba..62b1711ee57 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::MilestoneReferenceFilter do
+RSpec.describe Banzai::Filter::MilestoneReferenceFilter do
include FilterSpecHelper
let(:parent_group) { create(:group, :public) }
diff --git a/spec/lib/banzai/filter/output_safety_spec.rb b/spec/lib/banzai/filter/output_safety_spec.rb
index 5ffe591c9a4..5b7b7298411 100644
--- a/spec/lib/banzai/filter/output_safety_spec.rb
+++ b/spec/lib/banzai/filter/output_safety_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::OutputSafety do
+RSpec.describe Banzai::Filter::OutputSafety do
subject do
Class.new do
include Banzai::Filter::OutputSafety
diff --git a/spec/lib/banzai/filter/plantuml_filter_spec.rb b/spec/lib/banzai/filter/plantuml_filter_spec.rb
index abe525ac47a..5ad94c74514 100644
--- a/spec/lib/banzai/filter/plantuml_filter_spec.rb
+++ b/spec/lib/banzai/filter/plantuml_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::PlantumlFilter do
+RSpec.describe Banzai::Filter::PlantumlFilter do
include FilterSpecHelper
it 'replaces plantuml pre tag with img tag' do
diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb
index a054b79ec03..ac7a90a5893 100644
--- a/spec/lib/banzai/filter/project_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::ProjectReferenceFilter do
+RSpec.describe Banzai::Filter::ProjectReferenceFilter do
include FilterSpecHelper
def invalidate_reference(reference)
diff --git a/spec/lib/banzai/filter/reference_filter_spec.rb b/spec/lib/banzai/filter/reference_filter_spec.rb
index d889b0b832d..d5978db13c0 100644
--- a/spec/lib/banzai/filter/reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::ReferenceFilter do
+RSpec.describe Banzai::Filter::ReferenceFilter do
let(:project) { build_stubbed(:project) }
describe '#each_node' do
@@ -44,4 +44,249 @@ describe Banzai::Filter::ReferenceFilter do
expect(filter.nodes).to eq([document.children[0]])
end
end
+
+ RSpec.shared_context 'document nodes' do
+ let(:document) { Nokogiri::HTML.fragment('<p data-sourcepos="1:1-1:18"></p>') }
+ let(:nodes) { [] }
+ let(:filter) { described_class.new(document, project: project) }
+ let(:ref_pattern) { nil }
+ let(:href_link) { nil }
+
+ before do
+ nodes.each do |node|
+ document.children.first.add_child(node)
+ end
+ end
+ end
+
+ RSpec.shared_context 'new nodes' do
+ let(:nodes) { [{ value: "1" }, { value: "2" }, { value: "3" }] }
+ let(:expected_nodes) { [{ value: "1.1" }, { value: "1.2" }, { value: "1.3" }, { value: "2.1" }, { value: "2.2" }, { value: "2.3" }, { value: "3.1" }, { value: "3.2" }, { value: "3.3" }] }
+ let(:new_nodes) do
+ {
+ 0 => [{ value: "1.1" }, { value: "1.2" }, { value: "1.3" }],
+ 2 => [{ value: "3.1" }, { value: "3.2" }, { value: "3.3" }],
+ 1 => [{ value: "2.1" }, { value: "2.2" }, { value: "2.3" }]
+ }
+ end
+ end
+
+ RSpec.shared_examples 'replaces text' do |method_name, index|
+ let(:args) { [filter.nodes[index], index, ref_pattern || href_link].compact }
+
+ context 'when content didnt change' do
+ it 'does not replace link node with html' do
+ filter.send(method_name, *args) do
+ existing_content
+ end
+
+ expect(filter).not_to receive(:replace_text_with_html)
+ end
+ end
+
+ context 'when link node has changed' do
+ let(:html) { %(text <a href="reference_url" class="gfm gfm-user" title="reference">Reference</a>) }
+
+ it 'replaces reference node' do
+ filter.send(method_name, *args) do
+ html
+ end
+
+ expect(document.css('a').length).to eq 1
+ end
+
+ it 'calls replace_and_update_new_nodes' do
+ expect(filter).to receive(:replace_and_update_new_nodes).with(filter.nodes[index], index, html)
+
+ filter.send(method_name, *args) do
+ html
+ end
+ end
+
+ it 'stores filtered new nodes' do
+ filter.send(method_name, *args) do
+ html
+ end
+
+ expect(filter.instance_variable_get(:@new_nodes)).to eq({ index => [filter.each_node.to_a[index]] })
+ end
+
+ context "with update_nodes_for_banzai_reference_filter feature flag disabled" do
+ before do
+ stub_feature_flags(update_nodes_for_banzai_reference_filter: false)
+ end
+
+ it 'does not call replace_and_update_new_nodes' do
+ expect(filter).not_to receive(:replace_and_update_new_nodes).with(filter.nodes[index], index, html)
+
+ filter.send(method_name, *args) do
+ html
+ end
+ end
+ end
+ end
+ end
+
+ RSpec.shared_examples 'replaces document node' do |method_name|
+ context 'when parent has only one node' do
+ let(:nodes) { [node] }
+
+ it_behaves_like 'replaces text', method_name, 0
+ end
+
+ context 'when parent has multiple nodes' do
+ let(:node1) { Nokogiri::HTML.fragment('<span>span text</span>') }
+ let(:node2) { Nokogiri::HTML.fragment('<span>text</span>') }
+
+ context 'when pattern matches in the first node' do
+ let(:nodes) { [node, node1, node2] }
+
+ it_behaves_like 'replaces text', method_name, 0
+ end
+
+ context 'when pattern matches in the middle node' do
+ let(:nodes) { [node1, node, node2] }
+
+ it_behaves_like 'replaces text', method_name, 1
+ end
+
+ context 'when pattern matches in the last node' do
+ let(:nodes) { [node1, node2, node] }
+
+ it_behaves_like 'replaces text', method_name, 2
+ end
+ end
+ end
+
+ describe '#replace_text_when_pattern_matches' do
+ include_context 'document nodes'
+ let(:node) { Nokogiri::HTML.fragment('text @reference') }
+
+ let(:ref_pattern) { %r{(?<!\w)@(?<user>[a-zA-Z0-9_\-\.]*)}x }
+
+ context 'when node has no reference pattern' do
+ let(:node) { Nokogiri::HTML.fragment('random text') }
+ let(:nodes) { [node] }
+
+ it 'skips node' do
+ expect { |b| filter.replace_text_when_pattern_matches(filter.nodes[0], 0, ref_pattern, &b) }.not_to yield_control
+ end
+ end
+
+ it_behaves_like 'replaces document node', :replace_text_when_pattern_matches do
+ let(:existing_content) { node.to_html }
+ end
+ end
+
+ describe '#replace_link_node_with_text' do
+ include_context 'document nodes'
+ let(:node) { Nokogiri::HTML.fragment('<a>end text</a>') }
+
+ it_behaves_like 'replaces document node', :replace_link_node_with_text do
+ let(:existing_content) { node.text }
+ end
+ end
+
+ describe '#replace_link_node_with_href' do
+ include_context 'document nodes'
+ let(:node) { Nokogiri::HTML.fragment('<a href="link">end text</a>') }
+ let(:href_link) { CGI.unescape(node.attr('href').to_s) }
+
+ it_behaves_like 'replaces document node', :replace_link_node_with_href do
+ let(:existing_content) { href_link }
+ end
+ end
+
+ describe "#call_and_update_nodes" do
+ context "with update_nodes_for_banzai_reference_filter feature flag enabled" do
+ include_context 'new nodes'
+ let(:document) { Nokogiri::HTML.fragment('<a href="foo">foo</a>') }
+ let(:filter) { described_class.new(document, project: project) }
+
+ before do
+ stub_feature_flags(update_nodes_for_banzai_reference_filter: true)
+ end
+
+ it "updates all new nodes", :aggregate_failures do
+ filter.instance_variable_set('@nodes', nodes)
+
+ expect(filter).to receive(:call) { filter.instance_variable_set('@new_nodes', new_nodes) }
+ expect(filter).to receive(:with_update_nodes).and_call_original
+ expect(filter).to receive(:update_nodes!).and_call_original
+
+ filter.call_and_update_nodes
+
+ expect(filter.result[:reference_filter_nodes]).to eq(expected_nodes)
+ end
+ end
+
+ context "with update_nodes_for_banzai_reference_filter feature flag disabled" do
+ include_context 'new nodes'
+
+ before do
+ stub_feature_flags(update_nodes_for_banzai_reference_filter: false)
+ end
+
+ it "does not change nodes", :aggregate_failures do
+ document = Nokogiri::HTML.fragment('<a href="foo">foo</a>')
+ filter = described_class.new(document, project: project)
+ filter.instance_variable_set('@nodes', nodes)
+
+ expect(filter).to receive(:call) { filter.instance_variable_set('@new_nodes', new_nodes) }
+ expect(filter).not_to receive(:with_update_nodes)
+ expect(filter).not_to receive(:update_nodes!)
+
+ filter.call_and_update_nodes
+
+ expect(filter.nodes).to eq(nodes)
+ expect(filter.result[:reference_filter_nodes]).to be nil
+ end
+ end
+ end
+
+ describe ".call" do
+ include_context 'new nodes'
+
+ let(:document) { Nokogiri::HTML.fragment('<a href="foo">foo</a>') }
+
+ let(:result) { { reference_filter_nodes: nodes } }
+
+ before do
+ stub_feature_flags(update_nodes_for_banzai_reference_filter: true)
+ end
+
+ it "updates all nodes", :aggregate_failures do
+ expect_next_instance_of(described_class) do |filter|
+ expect(filter).to receive(:call_and_update_nodes).and_call_original
+ expect(filter).to receive(:with_update_nodes).and_call_original
+ expect(filter).to receive(:call) { filter.instance_variable_set('@new_nodes', new_nodes) }
+ expect(filter).to receive(:update_nodes!).and_call_original
+ end
+
+ described_class.call(document, { project: project }, result)
+
+ expect(result[:reference_filter_nodes]).to eq(expected_nodes)
+ end
+
+ context "with update_nodes_for_banzai_reference_filter feature flag disabled" do
+ let(:result) { {} }
+
+ before do
+ stub_feature_flags(update_nodes_for_banzai_reference_filter: false)
+ end
+
+ it "updates all nodes", :aggregate_failures do
+ expect_next_instance_of(described_class) do |filter|
+ expect(filter).to receive(:call_and_update_nodes).and_call_original
+ expect(filter).not_to receive(:with_update_nodes)
+ expect(filter).to receive(:call) { filter.instance_variable_set('@new_nodes', new_nodes) }
+ expect(filter).not_to receive(:update_nodes!)
+ end
+
+ described_class.call(document, { project: project }, result)
+
+ expect(result[:reference_filter_nodes]).to be nil
+ end
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/reference_redactor_filter_spec.rb b/spec/lib/banzai/filter/reference_redactor_filter_spec.rb
index 956bc85e53f..ac1cabb34cc 100644
--- a/spec/lib/banzai/filter/reference_redactor_filter_spec.rb
+++ b/spec/lib/banzai/filter/reference_redactor_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::ReferenceRedactorFilter do
+RSpec.describe Banzai::Filter::ReferenceRedactorFilter do
include ActionView::Helpers::UrlHelper
include FilterSpecHelper
diff --git a/spec/lib/banzai/filter/repository_link_filter_spec.rb b/spec/lib/banzai/filter/repository_link_filter_spec.rb
index 81f93f885f7..815053aac2f 100644
--- a/spec/lib/banzai/filter/repository_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/repository_link_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::RepositoryLinkFilter do
+RSpec.describe Banzai::Filter::RepositoryLinkFilter do
include GitHelpers
include RepoHelpers
diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
index 607dc3fda47..09dcd5518ff 100644
--- a/spec/lib/banzai/filter/sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::SanitizationFilter do
+RSpec.describe Banzai::Filter::SanitizationFilter do
include FilterSpecHelper
it_behaves_like 'default whitelist'
diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
index 62ce12406a2..f23fbc5be88 100644
--- a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::SnippetReferenceFilter do
+RSpec.describe Banzai::Filter::SnippetReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/filter/spaced_link_filter_spec.rb b/spec/lib/banzai/filter/spaced_link_filter_spec.rb
index 98c38813144..2c64657d69d 100644
--- a/spec/lib/banzai/filter/spaced_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/spaced_link_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::SpacedLinkFilter do
+RSpec.describe Banzai::Filter::SpacedLinkFilter do
include FilterSpecHelper
let(:link) { '[example](page slug)' }
diff --git a/spec/lib/banzai/filter/suggestion_filter_spec.rb b/spec/lib/banzai/filter/suggestion_filter_spec.rb
index 9c4650b73de..7d6092e21e9 100644
--- a/spec/lib/banzai/filter/suggestion_filter_spec.rb
+++ b/spec/lib/banzai/filter/suggestion_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::SuggestionFilter do
+RSpec.describe Banzai::Filter::SuggestionFilter do
include FilterSpecHelper
let(:input) { %(<pre class="code highlight js-syntax-highlight suggestion"><code>foo\n</code></pre>) }
diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
index 5a844fb61e3..a2875fad421 100644
--- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
+++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::SyntaxHighlightFilter do
+RSpec.describe Banzai::Filter::SyntaxHighlightFilter do
include FilterSpecHelper
shared_examples "XSS prevention" do |lang|
diff --git a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
index 05ef77c811a..2d17855707f 100644
--- a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
+++ b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::TableOfContentsFilter do
+RSpec.describe Banzai::Filter::TableOfContentsFilter do
include FilterSpecHelper
def header(level, text)
diff --git a/spec/lib/banzai/filter/table_of_contents_tag_filter_spec.rb b/spec/lib/banzai/filter/table_of_contents_tag_filter_spec.rb
index 20f32d7347d..56f36af5066 100644
--- a/spec/lib/banzai/filter/table_of_contents_tag_filter_spec.rb
+++ b/spec/lib/banzai/filter/table_of_contents_tag_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::TableOfContentsTagFilter do
+RSpec.describe Banzai::Filter::TableOfContentsTagFilter do
include FilterSpecHelper
context 'table of contents' do
diff --git a/spec/lib/banzai/filter/upload_link_filter_spec.rb b/spec/lib/banzai/filter/upload_link_filter_spec.rb
index 8844ad78306..0f8c773c68d 100644
--- a/spec/lib/banzai/filter/upload_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/upload_link_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::UploadLinkFilter do
+RSpec.describe Banzai::Filter::UploadLinkFilter do
def filter(doc, contexts = {})
contexts.reverse_merge!(
project: project,
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index cd6b68343b5..d8de3e5cc11 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::UserReferenceFilter do
+RSpec.describe Banzai::Filter::UserReferenceFilter do
include FilterSpecHelper
def get_reference(user)
diff --git a/spec/lib/banzai/filter/video_link_filter_spec.rb b/spec/lib/banzai/filter/video_link_filter_spec.rb
index c324c36fe4d..32fbc6b687f 100644
--- a/spec/lib/banzai/filter/video_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/video_link_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::VideoLinkFilter do
+RSpec.describe Banzai::Filter::VideoLinkFilter do
def filter(doc, contexts = {})
contexts.reverse_merge!({
project: project
diff --git a/spec/lib/banzai/filter/wiki_link_filter_spec.rb b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
index 827f38ef717..7a4464a2604 100644
--- a/spec/lib/banzai/filter/wiki_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Filter::WikiLinkFilter do
+RSpec.describe Banzai::Filter::WikiLinkFilter do
include FilterSpecHelper
let(:namespace) { build_stubbed(:namespace, name: "wiki_link_ns") }
diff --git a/spec/lib/banzai/filter_array_spec.rb b/spec/lib/banzai/filter_array_spec.rb
index bed41a80d29..47bc5633300 100644
--- a/spec/lib/banzai/filter_array_spec.rb
+++ b/spec/lib/banzai/filter_array_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::FilterArray do
+RSpec.describe Banzai::FilterArray do
describe '#insert_after' do
it 'inserts an element after a provided element' do
filters = described_class.new(%w(a b c))
diff --git a/spec/lib/banzai/issuable_extractor_spec.rb b/spec/lib/banzai/issuable_extractor_spec.rb
index 7fa6048c1c6..c4ee7160e12 100644
--- a/spec/lib/banzai/issuable_extractor_spec.rb
+++ b/spec/lib/banzai/issuable_extractor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::IssuableExtractor do
+RSpec.describe Banzai::IssuableExtractor do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:extractor) { described_class.new(Banzai::RenderContext.new(project, user)) }
diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb
index aef11775e60..f8d7acd3148 100644
--- a/spec/lib/banzai/object_renderer_spec.rb
+++ b/spec/lib/banzai/object_renderer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ObjectRenderer do
+RSpec.describe Banzai::ObjectRenderer do
let(:project) { create(:project, :repository) }
let(:user) { project.owner }
let(:renderer) do
diff --git a/spec/lib/banzai/pipeline/broadcast_message_pipeline_spec.rb b/spec/lib/banzai/pipeline/broadcast_message_pipeline_spec.rb
index 9832b132b58..41a91c56f3b 100644
--- a/spec/lib/banzai/pipeline/broadcast_message_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/broadcast_message_pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Pipeline::BroadcastMessagePipeline do
+RSpec.describe Banzai::Pipeline::BroadcastMessagePipeline do
before do
stub_commonmark_sourcepos_disabled
end
diff --git a/spec/lib/banzai/pipeline/description_pipeline_spec.rb b/spec/lib/banzai/pipeline/description_pipeline_spec.rb
index 6778a273bba..82d4f883e0d 100644
--- a/spec/lib/banzai/pipeline/description_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/description_pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Pipeline::DescriptionPipeline do
+RSpec.describe Banzai::Pipeline::DescriptionPipeline do
let_it_be(:project) { create(:project) }
def parse(html)
diff --git a/spec/lib/banzai/pipeline/email_pipeline_spec.rb b/spec/lib/banzai/pipeline/email_pipeline_spec.rb
index 77186861225..c7a0b9fefa1 100644
--- a/spec/lib/banzai/pipeline/email_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/email_pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Pipeline::EmailPipeline do
+RSpec.describe Banzai::Pipeline::EmailPipeline do
describe '.filters' do
it 'returns the expected type' do
expect(described_class.filters).to be_kind_of(Banzai::FilterArray)
diff --git a/spec/lib/banzai/pipeline/emoji_pipeline_spec.rb b/spec/lib/banzai/pipeline/emoji_pipeline_spec.rb
index 744df3e0b96..6de9d65f1b2 100644
--- a/spec/lib/banzai/pipeline/emoji_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/emoji_pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Pipeline::EmojiPipeline do
+RSpec.describe Banzai::Pipeline::EmojiPipeline do
def parse(text)
described_class.to_html(text, {})
end
diff --git a/spec/lib/banzai/pipeline/full_pipeline_spec.rb b/spec/lib/banzai/pipeline/full_pipeline_spec.rb
index b4047e369fb..0127ac11c81 100644
--- a/spec/lib/banzai/pipeline/full_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/full_pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Pipeline::FullPipeline do
+RSpec.describe Banzai::Pipeline::FullPipeline do
describe 'References' do
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project) }
diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
index 448422b15e3..beb760637b0 100644
--- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
@@ -2,12 +2,63 @@
require 'spec_helper'
-describe Banzai::Pipeline::GfmPipeline do
+RSpec.describe Banzai::Pipeline::GfmPipeline do
describe 'integration between parsing regular and external issue references' do
let(:project) { create(:redmine_project, :public) }
context 'when internal issue tracker is enabled' do
context 'when shorthand pattern #ISSUE_ID is used' do
+ it 'links an internal issues and keep updated nodes in result[:reference_filter_nodes]', :aggregate_failures do
+ issue = create(:issue, project: project)
+ markdown = "text #{issue.to_reference(project, full: true)}"
+
+ result = described_class.call(markdown, project: project)
+ link = result[:output].css('a').first
+ text = result[:output].children.first
+
+ expect(link['href']).to eq(Gitlab::Routing.url_helpers.project_issue_path(project, issue))
+ expect(result[:reference_filter_nodes]).to eq([text])
+ end
+ end
+
+ it 'executes :each_node only once for first reference filter', :aggregate_failures do
+ issue = create(:issue, project: project)
+ markdown = "text #{issue.to_reference(project, full: true)}"
+
+ expect_any_instance_of(Banzai::Filter::ReferenceFilter).to receive(:each_node).once
+
+ described_class.call(markdown, project: project)
+ end
+
+ context "with update_nodes_for_banzai_reference_filter feature flag disabled" do
+ before do
+ stub_feature_flags(update_nodes_for_banzai_reference_filter: false)
+ end
+
+ context 'when shorthand pattern #ISSUE_ID is used' do
+ it 'links an internal issues and doesnt store nodes in result[:reference_filter_nodes]', :aggregate_failures do
+ issue = create(:issue, project: project)
+ markdown = "text #{issue.to_reference(project, full: true)}"
+ result = described_class.call(markdown, project: project)
+ link = result[:output].css('a').first
+
+ expect(link['href']).to eq(Gitlab::Routing.url_helpers.project_issue_path(project, issue))
+ expect(result[:reference_filter_nodes]).to eq nil
+ end
+ end
+
+ it 'execute :each_node for each reference_filter', :aggregate_failures do
+ issue = create(:issue, project: project)
+ markdown = "text #{issue.to_reference(project, full: true)}"
+ described_class.reference_filters do |reference_filter|
+ expect_any_instance_of(reference_filter).to receive(:each_node).once
+ end
+
+ described_class.call(markdown, project: project)
+ end
+ end
+
+ context 'when shorthand pattern #ISSUE_ID is used' do
it 'links an internal issue if it exists' do
issue = create(:issue, project: project)
markdown = issue.to_reference(project, full: true)
diff --git a/spec/lib/banzai/pipeline/jira_import/adf_commonmark_pipeline_spec.rb b/spec/lib/banzai/pipeline/jira_import/adf_commonmark_pipeline_spec.rb
new file mode 100644
index 00000000000..d8841a9753e
--- /dev/null
+++ b/spec/lib/banzai/pipeline/jira_import/adf_commonmark_pipeline_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Banzai::Pipeline::JiraImport::AdfCommonmarkPipeline do
+ let_it_be(:fixtures_path) { 'lib/kramdown/atlassian_document_format' }
+
+ it 'converts text in Atlassian Document Format ' do
+ source = fixture_file(File.join(fixtures_path, 'paragraph.json'))
+ target = fixture_file(File.join(fixtures_path, 'paragraph.md'))
+ output = described_class.call(source, {})[:output]
+
+ expect(output).to eq target
+ end
+end
diff --git a/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb b/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb
index ab72354edcf..d9f45769550 100644
--- a/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Pipeline::PostProcessPipeline do
+RSpec.describe Banzai::Pipeline::PostProcessPipeline do
context 'when a document only has upload links' do
it 'does not make any Gitaly calls', :request_store do
markdown = <<-MARKDOWN.strip_heredoc
diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
index b2c24284eb9..4af782c7d73 100644
--- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Pipeline::WikiPipeline do
+RSpec.describe Banzai::Pipeline::WikiPipeline do
let_it_be(:namespace) { create(:namespace, name: "wiki_link_ns") }
let_it_be(:project) { create(:project, :public, name: "wiki_link_project", namespace: namespace) }
let_it_be(:wiki) { ProjectWiki.new(project, double(:user)) }
diff --git a/spec/lib/banzai/pipeline_spec.rb b/spec/lib/banzai/pipeline_spec.rb
index eeff7287ff5..7d4df2ca5ce 100644
--- a/spec/lib/banzai/pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Pipeline do
+RSpec.describe Banzai::Pipeline do
describe '.[]' do
subject { described_class[name] }
diff --git a/spec/lib/banzai/querying_spec.rb b/spec/lib/banzai/querying_spec.rb
index b7a235b0558..b76f6ec533c 100644
--- a/spec/lib/banzai/querying_spec.rb
+++ b/spec/lib/banzai/querying_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Querying do
+RSpec.describe Banzai::Querying do
describe '.css' do
it 'optimizes queries for elements with classes' do
document = double(:document)
diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb
index b1002c1db25..0eea51262ba 100644
--- a/spec/lib/banzai/reference_parser/base_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::BaseParser do
+RSpec.describe Banzai::ReferenceParser::BaseParser do
include ReferenceParserHelpers
let(:user) { create(:user) }
diff --git a/spec/lib/banzai/reference_parser/commit_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
index 7f7c750fe74..612ce6b93f1 100644
--- a/spec/lib/banzai/reference_parser/commit_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::CommitParser do
+RSpec.describe Banzai::ReferenceParser::CommitParser do
include ReferenceParserHelpers
let(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
index 78b337466aa..2f64aef4fb7 100644
--- a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::CommitRangeParser do
+RSpec.describe Banzai::ReferenceParser::CommitRangeParser do
include ReferenceParserHelpers
let(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/reference_parser/design_parser_spec.rb b/spec/lib/banzai/reference_parser/design_parser_spec.rb
index 76708acf887..92d3a4aaad2 100644
--- a/spec/lib/banzai/reference_parser/design_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/design_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::DesignParser do
+RSpec.describe Banzai::ReferenceParser::DesignParser do
include ReferenceParserHelpers
include DesignManagementTestHelpers
diff --git a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
index 9343d52e44b..5f92eb42e74 100644
--- a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::ExternalIssueParser do
+RSpec.describe Banzai::ReferenceParser::ExternalIssueParser do
include ReferenceParserHelpers
let(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/reference_parser/issue_parser_spec.rb b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
index ac321aca5e9..76f13e7b3aa 100644
--- a/spec/lib/banzai/reference_parser/issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::IssueParser do
+RSpec.describe Banzai::ReferenceParser::IssueParser do
include ReferenceParserHelpers
let(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/reference_parser/label_parser_spec.rb b/spec/lib/banzai/reference_parser/label_parser_spec.rb
index 8b66a891e69..8f287e15b37 100644
--- a/spec/lib/banzai/reference_parser/label_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/label_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::LabelParser do
+RSpec.describe Banzai::ReferenceParser::LabelParser do
include ReferenceParserHelpers
let(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/reference_parser/mentioned_group_parser_spec.rb b/spec/lib/banzai/reference_parser/mentioned_group_parser_spec.rb
index 8346ba93f88..4610da7cbe6 100644
--- a/spec/lib/banzai/reference_parser/mentioned_group_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/mentioned_group_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::MentionedGroupParser do
+RSpec.describe Banzai::ReferenceParser::MentionedGroupParser do
include ReferenceParserHelpers
let(:group) { create(:group, :private) }
diff --git a/spec/lib/banzai/reference_parser/mentioned_project_parser_spec.rb b/spec/lib/banzai/reference_parser/mentioned_project_parser_spec.rb
index b99c02351d0..7eb58ee40d3 100644
--- a/spec/lib/banzai/reference_parser/mentioned_project_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/mentioned_project_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::MentionedProjectParser do
+RSpec.describe Banzai::ReferenceParser::MentionedProjectParser do
include ReferenceParserHelpers
let(:group) { create(:group, :private) }
diff --git a/spec/lib/banzai/reference_parser/mentioned_user_parser_spec.rb b/spec/lib/banzai/reference_parser/mentioned_user_parser_spec.rb
index b10e5d19828..4be07866db1 100644
--- a/spec/lib/banzai/reference_parser/mentioned_user_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/mentioned_user_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::MentionedUserParser do
+RSpec.describe Banzai::ReferenceParser::MentionedUserParser do
include ReferenceParserHelpers
let(:group) { create(:group, :private) }
diff --git a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
index cb65893aea0..32a9f09c3f6 100644
--- a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::MergeRequestParser do
+RSpec.describe Banzai::ReferenceParser::MergeRequestParser do
include ReferenceParserHelpers
let(:user) { create(:user) }
diff --git a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
index 25ba41dd8a0..95f71154e38 100644
--- a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::MilestoneParser do
+RSpec.describe Banzai::ReferenceParser::MilestoneParser do
include ReferenceParserHelpers
let(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/reference_parser/project_parser_spec.rb b/spec/lib/banzai/reference_parser/project_parser_spec.rb
index e87fa3e8767..6358a04f12a 100644
--- a/spec/lib/banzai/reference_parser/project_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/project_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::ProjectParser do
+RSpec.describe Banzai::ReferenceParser::ProjectParser do
include ReferenceParserHelpers
let(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
index 6581ed0d7c3..cdc660b4f4a 100644
--- a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::SnippetParser do
+RSpec.describe Banzai::ReferenceParser::SnippetParser do
include ReferenceParserHelpers
let(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb
index 71d2e1de3b6..d4f4339cf17 100644
--- a/spec/lib/banzai/reference_parser/user_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::UserParser do
+RSpec.describe Banzai::ReferenceParser::UserParser do
include ReferenceParserHelpers
let(:group) { create(:group) }
diff --git a/spec/lib/banzai/reference_redactor_spec.rb b/spec/lib/banzai/reference_redactor_spec.rb
index 0dec6395fb3..de774267b81 100644
--- a/spec/lib/banzai/reference_redactor_spec.rb
+++ b/spec/lib/banzai/reference_redactor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::ReferenceRedactor do
+RSpec.describe Banzai::ReferenceRedactor do
let(:user) { create(:user) }
let(:project) { build(:project) }
let(:redactor) { described_class.new(Banzai::RenderContext.new(project, user)) }
diff --git a/spec/lib/banzai/render_context_spec.rb b/spec/lib/banzai/render_context_spec.rb
index ad17db11613..c4b609b936e 100644
--- a/spec/lib/banzai/render_context_spec.rb
+++ b/spec/lib/banzai/render_context_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::RenderContext do
+RSpec.describe Banzai::RenderContext do
let(:document) { Nokogiri::HTML.fragment('<p>hello</p>') }
describe '#project_for_node' do
diff --git a/spec/lib/banzai/renderer_spec.rb b/spec/lib/banzai/renderer_spec.rb
index b540a76face..52bf3087875 100644
--- a/spec/lib/banzai/renderer_spec.rb
+++ b/spec/lib/banzai/renderer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Banzai::Renderer do
+RSpec.describe Banzai::Renderer do
let(:renderer) { described_class }
def fake_object(fresh:)
diff --git a/spec/lib/bitbucket/collection_spec.rb b/spec/lib/bitbucket/collection_spec.rb
index 5946be71565..349274585c4 100644
--- a/spec/lib/bitbucket/collection_spec.rb
+++ b/spec/lib/bitbucket/collection_spec.rb
@@ -17,7 +17,7 @@ class TestPaginator
end
end
-describe Bitbucket::Collection do
+RSpec.describe Bitbucket::Collection do
it "iterates paginator" do
collection = described_class.new(TestPaginator.new)
diff --git a/spec/lib/bitbucket/connection_spec.rb b/spec/lib/bitbucket/connection_spec.rb
index 5aca93767dc..bed44b94f44 100644
--- a/spec/lib/bitbucket/connection_spec.rb
+++ b/spec/lib/bitbucket/connection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Bitbucket::Connection do
+RSpec.describe Bitbucket::Connection do
before do
allow_next_instance_of(described_class) do |instance|
allow(instance).to receive(:provider).and_return(double(app_id: '', app_secret: ''))
diff --git a/spec/lib/bitbucket/page_spec.rb b/spec/lib/bitbucket/page_spec.rb
index 6301dd56faf..1d599007d9e 100644
--- a/spec/lib/bitbucket/page_spec.rb
+++ b/spec/lib/bitbucket/page_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Bitbucket::Page do
+RSpec.describe Bitbucket::Page do
let(:response) { { 'values' => [{ 'username' => 'Ben' }], 'pagelen' => 2, 'next' => '' } }
before do
diff --git a/spec/lib/bitbucket/paginator_spec.rb b/spec/lib/bitbucket/paginator_spec.rb
index a1effa14000..e74af8a264b 100644
--- a/spec/lib/bitbucket/paginator_spec.rb
+++ b/spec/lib/bitbucket/paginator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Bitbucket::Paginator do
+RSpec.describe Bitbucket::Paginator do
let(:last_page) { double(:page, next?: false, items: ['item_2']) }
let(:first_page) { double(:page, next?: true, next: last_page, items: ['item_1']) }
diff --git a/spec/lib/bitbucket/representation/comment_spec.rb b/spec/lib/bitbucket/representation/comment_spec.rb
index 1874296df8c..f6766ab685b 100644
--- a/spec/lib/bitbucket/representation/comment_spec.rb
+++ b/spec/lib/bitbucket/representation/comment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Bitbucket::Representation::Comment do
+RSpec.describe Bitbucket::Representation::Comment do
describe '#author' do
it { expect(described_class.new('user' => { 'nickname' => 'Ben' }).author).to eq('Ben') }
it { expect(described_class.new({}).author).to be_nil }
diff --git a/spec/lib/bitbucket/representation/issue_spec.rb b/spec/lib/bitbucket/representation/issue_spec.rb
index 655b9b78b47..8c27086546f 100644
--- a/spec/lib/bitbucket/representation/issue_spec.rb
+++ b/spec/lib/bitbucket/representation/issue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Bitbucket::Representation::Issue do
+RSpec.describe Bitbucket::Representation::Issue do
describe '#iid' do
it { expect(described_class.new('id' => 1).iid).to eq(1) }
end
diff --git a/spec/lib/bitbucket/representation/pull_request_comment_spec.rb b/spec/lib/bitbucket/representation/pull_request_comment_spec.rb
index 151055f510f..cdab683492f 100644
--- a/spec/lib/bitbucket/representation/pull_request_comment_spec.rb
+++ b/spec/lib/bitbucket/representation/pull_request_comment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Bitbucket::Representation::PullRequestComment do
+RSpec.describe Bitbucket::Representation::PullRequestComment do
describe '#iid' do
it { expect(described_class.new('id' => 1).iid).to eq(1) }
end
diff --git a/spec/lib/bitbucket/representation/pull_request_spec.rb b/spec/lib/bitbucket/representation/pull_request_spec.rb
index 6a9df0e5099..6f05d03aa0a 100644
--- a/spec/lib/bitbucket/representation/pull_request_spec.rb
+++ b/spec/lib/bitbucket/representation/pull_request_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Bitbucket::Representation::PullRequest do
+RSpec.describe Bitbucket::Representation::PullRequest do
describe '#iid' do
it { expect(described_class.new('id' => 1).iid).to eq(1) }
end
diff --git a/spec/lib/bitbucket/representation/repo_spec.rb b/spec/lib/bitbucket/representation/repo_spec.rb
index a272695e681..a779a153f25 100644
--- a/spec/lib/bitbucket/representation/repo_spec.rb
+++ b/spec/lib/bitbucket/representation/repo_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Bitbucket::Representation::Repo do
+RSpec.describe Bitbucket::Representation::Repo do
describe '#has_wiki?' do
it { expect(described_class.new({ 'has_wiki' => false }).has_wiki?).to be_falsey }
it { expect(described_class.new({ 'has_wiki' => true }).has_wiki?).to be_truthy }
diff --git a/spec/lib/bitbucket/representation/user_spec.rb b/spec/lib/bitbucket/representation/user_spec.rb
index 0169887a24c..e1f6c724da8 100644
--- a/spec/lib/bitbucket/representation/user_spec.rb
+++ b/spec/lib/bitbucket/representation/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Bitbucket::Representation::User do
+RSpec.describe Bitbucket::Representation::User do
describe '#username' do
it 'returns correct value' do
user = described_class.new('username' => 'Ben')
diff --git a/spec/lib/bitbucket_server/client_spec.rb b/spec/lib/bitbucket_server/client_spec.rb
index aa0217856ee..9dcd1500aab 100644
--- a/spec/lib/bitbucket_server/client_spec.rb
+++ b/spec/lib/bitbucket_server/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BitbucketServer::Client do
+RSpec.describe BitbucketServer::Client do
let(:base_uri) { 'https://test:7990/stash/' }
let(:options) { { base_uri: base_uri, user: 'bitbucket', password: 'mypassword' } }
let(:project) { 'SOME-PROJECT' }
diff --git a/spec/lib/bitbucket_server/collection_spec.rb b/spec/lib/bitbucket_server/collection_spec.rb
index ddd02bac88a..1a47a30c74b 100644
--- a/spec/lib/bitbucket_server/collection_spec.rb
+++ b/spec/lib/bitbucket_server/collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BitbucketServer::Collection do
+RSpec.describe BitbucketServer::Collection do
let(:connection) { instance_double(BitbucketServer::Connection) }
let(:page) { 1 }
let(:paginator) { BitbucketServer::Paginator.new(connection, 'http://more-data', :pull_request, page_offset: page) }
diff --git a/spec/lib/bitbucket_server/connection_spec.rb b/spec/lib/bitbucket_server/connection_spec.rb
index 3a7fe4e7321..873eded58d7 100644
--- a/spec/lib/bitbucket_server/connection_spec.rb
+++ b/spec/lib/bitbucket_server/connection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BitbucketServer::Connection do
+RSpec.describe BitbucketServer::Connection do
let(:options) { { base_uri: 'https://test:7990', user: 'bitbucket', password: 'mypassword' } }
let(:payload) { { 'test' => 1 } }
let(:headers) { { "Content-Type" => "application/json" } }
diff --git a/spec/lib/bitbucket_server/page_spec.rb b/spec/lib/bitbucket_server/page_spec.rb
index 2da1d0995ca..2d4e946e590 100644
--- a/spec/lib/bitbucket_server/page_spec.rb
+++ b/spec/lib/bitbucket_server/page_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BitbucketServer::Page do
+RSpec.describe BitbucketServer::Page do
let(:response) { { 'values' => [{ 'description' => 'Test' }], 'isLastPage' => false, 'nextPageStart' => 2 } }
before do
diff --git a/spec/lib/bitbucket_server/paginator_spec.rb b/spec/lib/bitbucket_server/paginator_spec.rb
index e01cbeb4270..66bf5bf3f02 100644
--- a/spec/lib/bitbucket_server/paginator_spec.rb
+++ b/spec/lib/bitbucket_server/paginator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BitbucketServer::Paginator do
+RSpec.describe BitbucketServer::Paginator do
let(:last_page) { double(:page, next?: false, items: ['item_2']) }
let(:first_page) { double(:page, next?: true, next: last_page, items: ['item_1']) }
let(:connection) { instance_double(BitbucketServer::Connection) }
diff --git a/spec/lib/bitbucket_server/representation/activity_spec.rb b/spec/lib/bitbucket_server/representation/activity_spec.rb
index 6988e77ad25..0b7e4542cbe 100644
--- a/spec/lib/bitbucket_server/representation/activity_spec.rb
+++ b/spec/lib/bitbucket_server/representation/activity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BitbucketServer::Representation::Activity do
+RSpec.describe BitbucketServer::Representation::Activity do
let(:activities) { Gitlab::Json.parse(fixture_file('importers/bitbucket_server/activities.json'))['values'] }
let(:inline_comment) { activities.first }
let(:comment) { activities[3] }
diff --git a/spec/lib/bitbucket_server/representation/comment_spec.rb b/spec/lib/bitbucket_server/representation/comment_spec.rb
index ecaf6a843ae..b568789bd97 100644
--- a/spec/lib/bitbucket_server/representation/comment_spec.rb
+++ b/spec/lib/bitbucket_server/representation/comment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BitbucketServer::Representation::Comment do
+RSpec.describe BitbucketServer::Representation::Comment do
let(:activities) { Gitlab::Json.parse(fixture_file('importers/bitbucket_server/activities.json'))['values'] }
let(:comment) { activities.first }
diff --git a/spec/lib/bitbucket_server/representation/pull_request_comment_spec.rb b/spec/lib/bitbucket_server/representation/pull_request_comment_spec.rb
index aa3eddf305a..01e56263a5e 100644
--- a/spec/lib/bitbucket_server/representation/pull_request_comment_spec.rb
+++ b/spec/lib/bitbucket_server/representation/pull_request_comment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BitbucketServer::Representation::PullRequestComment do
+RSpec.describe BitbucketServer::Representation::PullRequestComment do
let(:activities) { Gitlab::Json.parse(fixture_file('importers/bitbucket_server/activities.json'))['values'] }
let(:comment) { activities.second }
diff --git a/spec/lib/bitbucket_server/representation/pull_request_spec.rb b/spec/lib/bitbucket_server/representation/pull_request_spec.rb
index 7e72da05cb1..a05d98f0d4a 100644
--- a/spec/lib/bitbucket_server/representation/pull_request_spec.rb
+++ b/spec/lib/bitbucket_server/representation/pull_request_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BitbucketServer::Representation::PullRequest do
+RSpec.describe BitbucketServer::Representation::PullRequest do
let(:sample_data) { Gitlab::Json.parse(fixture_file('importers/bitbucket_server/pull_request.json')) }
subject { described_class.new(sample_data) }
diff --git a/spec/lib/bitbucket_server/representation/repo_spec.rb b/spec/lib/bitbucket_server/representation/repo_spec.rb
index 429b6d36c59..7a773f47ca5 100644
--- a/spec/lib/bitbucket_server/representation/repo_spec.rb
+++ b/spec/lib/bitbucket_server/representation/repo_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BitbucketServer::Representation::Repo do
+RSpec.describe BitbucketServer::Representation::Repo do
let(:sample_data) do
<<~DATA
{
diff --git a/spec/lib/constraints/admin_constrainer_spec.rb b/spec/lib/constraints/admin_constrainer_spec.rb
index da178f9e71a..3efe683177c 100644
--- a/spec/lib/constraints/admin_constrainer_spec.rb
+++ b/spec/lib/constraints/admin_constrainer_spec.rb
@@ -2,7 +2,7 @@
#
require 'spec_helper'
-describe Constraints::AdminConstrainer, :do_not_mock_admin_mode do
+RSpec.describe Constraints::AdminConstrainer, :do_not_mock_admin_mode do
let(:user) { create(:user) }
let(:session) { {} }
diff --git a/spec/lib/constraints/feature_constrainer_spec.rb b/spec/lib/constraints/feature_constrainer_spec.rb
index 7665d5b3547..c98dc694186 100644
--- a/spec/lib/constraints/feature_constrainer_spec.rb
+++ b/spec/lib/constraints/feature_constrainer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Constraints::FeatureConstrainer do
+RSpec.describe Constraints::FeatureConstrainer do
describe '#matches' do
it 'calls Feature.enabled? with the correct arguments' do
gate = stub_feature_flag_gate("an object")
diff --git a/spec/lib/constraints/group_url_constrainer_spec.rb b/spec/lib/constraints/group_url_constrainer_spec.rb
index 573de331898..45a5b3afd81 100644
--- a/spec/lib/constraints/group_url_constrainer_spec.rb
+++ b/spec/lib/constraints/group_url_constrainer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Constraints::GroupUrlConstrainer do
+RSpec.describe Constraints::GroupUrlConstrainer do
let!(:group) { create(:group, path: 'gitlab') }
describe '#matches?' do
diff --git a/spec/lib/constraints/project_url_constrainer_spec.rb b/spec/lib/constraints/project_url_constrainer_spec.rb
index 963e1d5b8e0..1e8aac8479d 100644
--- a/spec/lib/constraints/project_url_constrainer_spec.rb
+++ b/spec/lib/constraints/project_url_constrainer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Constraints::ProjectUrlConstrainer do
+RSpec.describe Constraints::ProjectUrlConstrainer do
let!(:project) { create(:project) }
let!(:namespace) { project.namespace }
diff --git a/spec/lib/constraints/user_url_constrainer_spec.rb b/spec/lib/constraints/user_url_constrainer_spec.rb
index 4f665def3bf..1b68e966585 100644
--- a/spec/lib/constraints/user_url_constrainer_spec.rb
+++ b/spec/lib/constraints/user_url_constrainer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Constraints::UserUrlConstrainer do
+RSpec.describe Constraints::UserUrlConstrainer do
let!(:user) { create(:user, username: 'dz') }
describe '#matches?' do
diff --git a/spec/lib/container_registry/blob_spec.rb b/spec/lib/container_registry/blob_spec.rb
index be7be2f3719..676adc74be2 100644
--- a/spec/lib/container_registry/blob_spec.rb
+++ b/spec/lib/container_registry/blob_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerRegistry::Blob do
+RSpec.describe ContainerRegistry::Blob do
let(:group) { create(:group, name: 'group') }
let(:project) { create(:project, path: 'test', group: group) }
diff --git a/spec/lib/container_registry/client_spec.rb b/spec/lib/container_registry/client_spec.rb
index 18bcff65f41..de92ca5eeec 100644
--- a/spec/lib/container_registry/client_spec.rb
+++ b/spec/lib/container_registry/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerRegistry::Client do
+RSpec.describe ContainerRegistry::Client do
let(:token) { '12345' }
let(:options) { { token: token } }
let(:client) { described_class.new("http://container-registry", options) }
diff --git a/spec/lib/container_registry/path_spec.rb b/spec/lib/container_registry/path_spec.rb
index 8c671b4d56d..aa6876225b5 100644
--- a/spec/lib/container_registry/path_spec.rb
+++ b/spec/lib/container_registry/path_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerRegistry::Path do
+RSpec.describe ContainerRegistry::Path do
subject { described_class.new(path) }
describe '#components' do
diff --git a/spec/lib/container_registry/registry_spec.rb b/spec/lib/container_registry/registry_spec.rb
index e509566fae8..d6e2b17f53b 100644
--- a/spec/lib/container_registry/registry_spec.rb
+++ b/spec/lib/container_registry/registry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerRegistry::Registry do
+RSpec.describe ContainerRegistry::Registry do
let(:path) { nil }
let(:registry) { described_class.new('http://example.com', path: path) }
diff --git a/spec/lib/container_registry/tag_spec.rb b/spec/lib/container_registry/tag_spec.rb
index 085c73caa97..d696b61ac9d 100644
--- a/spec/lib/container_registry/tag_spec.rb
+++ b/spec/lib/container_registry/tag_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerRegistry::Tag do
+RSpec.describe ContainerRegistry::Tag do
let(:group) { create(:group, name: 'group') }
let(:project) { create(:project, path: 'test', group: group) }
diff --git a/spec/lib/csv_builder_spec.rb b/spec/lib/csv_builder_spec.rb
index 0d5e2b81b16..546be3ba6f7 100644
--- a/spec/lib/csv_builder_spec.rb
+++ b/spec/lib/csv_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CsvBuilder do
+RSpec.describe CsvBuilder do
let(:object) { double(question: :answer) }
let(:fake_relation) { FakeRelation.new([object]) }
let(:subject) { described_class.new(fake_relation, 'Q & A' => :question, 'Reversed' => -> (o) { o.question.to_s.reverse }) }
diff --git a/spec/lib/declarative_policy/overrides_spec.rb b/spec/lib/declarative_policy/overrides_spec.rb
new file mode 100644
index 00000000000..84dc8f7ac71
--- /dev/null
+++ b/spec/lib/declarative_policy/overrides_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require_dependency 'rspec-parameterized'
+
+RSpec.describe 'DeclarativePolicy overrides' do
+ let(:foo_policy) do
+ Class.new(DeclarativePolicy::Base) do
+ condition(:foo_prop_cond) { @subject.foo_prop }
+
+ rule { foo_prop_cond }.policy do
+ enable :common_ability
+ enable :foo_prop_ability
+ end
+ end
+ end
+
+ let(:bar_policy) do
+ Class.new(DeclarativePolicy::Base) do
+ delegate { @subject.foo }
+
+ overrides :common_ability
+
+ condition(:bar_prop_cond) { @subject.bar_prop }
+
+ rule { bar_prop_cond }.policy do
+ enable :common_ability
+ enable :bar_prop_ability
+ end
+
+ rule { bar_prop_cond & can?(:foo_prop_ability) }.policy do
+ enable :combined_ability
+ end
+ end
+ end
+
+ before do
+ stub_const('Foo', Struct.new(:foo_prop))
+ stub_const('FooPolicy', foo_policy)
+ stub_const('Bar', Struct.new(:foo, :bar_prop))
+ stub_const('BarPolicy', bar_policy)
+ end
+
+ where(:foo_prop, :bar_prop) do
+ [
+ [true, true],
+ [true, false],
+ [false, true],
+ [false, false]
+ ]
+ end
+
+ with_them do
+ let(:foo) { Foo.new(foo_prop) }
+ let(:bar) { Bar.new(foo, bar_prop) }
+
+ it 'determines the correct bar_prop_ability (non-delegated) permissions for bar' do
+ policy = DeclarativePolicy.policy_for(nil, bar)
+ expect(policy.allowed?(:bar_prop_ability)).to eq(bar_prop)
+ end
+
+ it 'determines the correct foo_prop (non-overridden) permissions for bar' do
+ policy = DeclarativePolicy.policy_for(nil, bar)
+ expect(policy.allowed?(:foo_prop_ability)).to eq(foo_prop)
+ end
+
+ it 'determines the correct common_ability (overridden) permissions for bar' do
+ policy = DeclarativePolicy.policy_for(nil, bar)
+ expect(policy.allowed?(:common_ability)).to eq(bar_prop)
+ end
+
+ it 'determines the correct common_ability permissions for foo' do
+ policy = DeclarativePolicy.policy_for(nil, foo)
+ expect(policy.allowed?(:common_ability)).to eq(foo_prop)
+ end
+
+ it 'allows combinations of overridden and inherited values' do
+ policy = DeclarativePolicy.policy_for(nil, bar)
+ expect(policy.allowed?(:combined_ability)).to eq(foo_prop && bar_prop)
+ end
+ end
+end
diff --git a/spec/lib/declarative_policy_spec.rb b/spec/lib/declarative_policy_spec.rb
index 5fdb3c27738..fc21bd43f48 100644
--- a/spec/lib/declarative_policy_spec.rb
+++ b/spec/lib/declarative_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeclarativePolicy do
+RSpec.describe DeclarativePolicy do
describe '.class_for' do
it 'uses declarative_policy_class if present' do
instance = Gitlab::ErrorTracking::ErrorEvent.new
diff --git a/spec/lib/event_filter_spec.rb b/spec/lib/event_filter_spec.rb
index da6e1f9458f..bab48796b8c 100644
--- a/spec/lib/event_filter_spec.rb
+++ b/spec/lib/event_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EventFilter do
+RSpec.describe EventFilter do
describe '#filter' do
it 'returns "all" if given filter is nil' do
expect(described_class.new(nil).filter).to eq(described_class::ALL)
@@ -30,6 +30,7 @@ describe EventFilter do
let_it_be(:left_event) { create(:event, :left, project: public_project, target: public_project) }
let_it_be(:wiki_page_event) { create(:wiki_page_event) }
let_it_be(:wiki_page_update_event) { create(:wiki_page_event, :updated) }
+ let_it_be(:design_event) { create(:design_event) }
let(:filtered_events) { described_class.new(filter).apply_filter(Event.all) }
@@ -79,15 +80,13 @@ describe EventFilter do
it 'returns all events' do
expect(filtered_events).to eq(Event.all)
end
+ end
- context 'the :wiki_events filter is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
+ context 'with the "design" filter' do
+ let(:filter) { described_class::DESIGNS }
- it 'does not return wiki events' do
- expect(filtered_events).to eq(Event.not_wiki_page)
- end
+ it 'returns only design events' do
+ expect(filtered_events).to contain_exactly(design_event)
end
end
@@ -97,16 +96,6 @@ describe EventFilter do
it 'returns only wiki page events' do
expect(filtered_events).to contain_exactly(wiki_page_event, wiki_page_update_event)
end
-
- context 'the :wiki_events filter is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it 'does not return wiki events' do
- expect(filtered_events).not_to include(wiki_page_event, wiki_page_update_event)
- end
- end
end
context 'with an unknown filter' do
@@ -115,16 +104,6 @@ describe EventFilter do
it 'returns all events' do
expect(filtered_events).to eq(Event.all)
end
-
- context 'the :wiki_events filter is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it 'does not return wiki events' do
- expect(filtered_events).to eq(Event.not_wiki_page)
- end
- end
end
context 'with a nil filter' do
@@ -133,16 +112,6 @@ describe EventFilter do
it 'returns all events' do
expect(filtered_events).to eq(Event.all)
end
-
- context 'the :wiki_events filter is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it 'does not return wiki events' do
- expect(filtered_events).to eq(Event.not_wiki_page)
- end
- end
end
end
diff --git a/spec/lib/expand_variables_spec.rb b/spec/lib/expand_variables_spec.rb
index 1b8ec2b1979..4a5b70ff248 100644
--- a/spec/lib/expand_variables_spec.rb
+++ b/spec/lib/expand_variables_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExpandVariables do
+RSpec.describe ExpandVariables do
describe '#expand' do
context 'table tests' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb
index a56768a1a88..dbb3aa8797e 100644
--- a/spec/lib/extracts_path_spec.rb
+++ b/spec/lib/extracts_path_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExtractsPath do
+RSpec.describe ExtractsPath do
include described_class
include RepoHelpers
include Gitlab::Routing
diff --git a/spec/lib/extracts_ref_spec.rb b/spec/lib/extracts_ref_spec.rb
index 1867f639711..ca2f1fd7dc1 100644
--- a/spec/lib/extracts_ref_spec.rb
+++ b/spec/lib/extracts_ref_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExtractsRef do
+RSpec.describe ExtractsRef do
include described_class
include RepoHelpers
diff --git a/spec/lib/feature/definition_spec.rb b/spec/lib/feature/definition_spec.rb
new file mode 100644
index 00000000000..49224cf4279
--- /dev/null
+++ b/spec/lib/feature/definition_spec.rb
@@ -0,0 +1,209 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Feature::Definition do
+ let(:attributes) do
+ { name: 'feature_flag',
+ type: 'development',
+ default_enabled: true }
+ end
+
+ let(:path) { File.join('development', 'feature_flag.yml') }
+ let(:definition) { described_class.new(path, attributes) }
+ let(:yaml_content) { attributes.deep_stringify_keys.to_yaml }
+
+ describe '#key' do
+ subject { definition.key }
+
+ it 'returns a symbol from name' do
+ is_expected.to eq(:feature_flag)
+ end
+ end
+
+ describe '#validate!' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:param, :value, :result) do
+ :name | nil | /Feature flag is missing name/
+ :path | nil | /Feature flag 'feature_flag' is missing path/
+ :type | nil | /Feature flag 'feature_flag' is missing type/
+ :type | 'invalid' | /Feature flag 'feature_flag' type 'invalid' is invalid/
+ :path | 'development/invalid.yml' | /Feature flag 'feature_flag' has an invalid path/
+ :path | 'invalid/feature_flag.yml' | /Feature flag 'feature_flag' has an invalid type/
+ :default_enabled | nil | /Feature flag 'feature_flag' is missing default_enabled/
+ end
+
+ with_them do
+ let(:params) { attributes.merge(path: path) }
+
+ before do
+ params[param] = value
+ end
+
+ it do
+ expect do
+ described_class.new(
+ params[:path], params.except(:path)
+ ).validate!
+ end.to raise_error(result)
+ end
+ end
+ end
+
+ describe '#valid_usage!' do
+ context 'validates type' do
+ it 'raises exception for invalid type' do
+ expect { definition.valid_usage!(type_in_code: :invalid, default_enabled_in_code: false) }
+ .to raise_error(/The `type:` of `feature_flag` is not equal to config/)
+ end
+ end
+
+ context 'validates default enabled' do
+ it 'raises exception for different value' do
+ expect { definition.valid_usage!(type_in_code: :development, default_enabled_in_code: false) }
+ .to raise_error(/The `default_enabled:` of `feature_flag` is not equal to config/)
+ end
+ end
+ end
+
+ describe '.paths' do
+ it 'returns at least one path' do
+ expect(described_class.paths).not_to be_empty
+ end
+ end
+
+ describe '.load_from_file' do
+ it 'properly loads a definition from file' do
+ expect(File).to receive(:read).with(path) { yaml_content }
+
+ expect(described_class.send(:load_from_file, path).attributes)
+ .to eq(definition.attributes)
+ end
+
+ context 'for missing file' do
+ let(:path) { 'missing/feature-flag/file.yml' }
+
+ it 'raises exception' do
+ expect do
+ described_class.send(:load_from_file, path)
+ end.to raise_error(/Invalid definition for/)
+ end
+ end
+
+ context 'for invalid definition' do
+ it 'raises exception' do
+ expect(File).to receive(:read).with(path) { '{}' }
+
+ expect do
+ described_class.send(:load_from_file, path)
+ end.to raise_error(/Feature flag is missing name/)
+ end
+ end
+ end
+
+ describe '.load_all!' do
+ let(:store1) { Dir.mktmpdir('path1') }
+ let(:store2) { Dir.mktmpdir('path2') }
+
+ before do
+ allow(described_class).to receive(:paths).and_return(
+ [
+ File.join(store1, '**', '*.yml'),
+ File.join(store2, '**', '*.yml')
+ ]
+ )
+ end
+
+ it "when there's no feature flags a list of definitions is empty" do
+ expect(described_class.load_all!).to be_empty
+ end
+
+ it "when there's a single feature flag it properly loads them" do
+ write_feature_flag(store1, path, yaml_content)
+
+ expect(described_class.load_all!).to be_one
+ end
+
+ it "when the same feature flag is stored multiple times raises exception" do
+ write_feature_flag(store1, path, yaml_content)
+ write_feature_flag(store2, path, yaml_content)
+
+ expect { described_class.load_all! }
+ .to raise_error(/Feature flag 'feature_flag' is already defined/)
+ end
+
+ it "when one of the YAMLs is invalid it does raise exception" do
+ write_feature_flag(store1, path, '{}')
+
+ expect { described_class.load_all! }
+ .to raise_error(/Feature flag is missing name/)
+ end
+
+ after do
+ FileUtils.rm_rf(store1)
+ FileUtils.rm_rf(store2)
+ end
+
+ def write_feature_flag(store, path, content)
+ path = File.join(store, path)
+ dir = File.dirname(path)
+ FileUtils.mkdir_p(dir)
+ File.write(path, content)
+ end
+ end
+
+ describe '.valid_usage!' do
+ before do
+ allow(described_class).to receive(:definitions) do
+ { definition.key => definition }
+ end
+ end
+
+ context 'when a known feature flag is used' do
+ it 'validates it usage' do
+ expect(definition).to receive(:valid_usage!)
+
+ described_class.valid_usage!(:feature_flag, type: :development, default_enabled: false)
+ end
+ end
+
+ context 'when an unknown feature flag is used' do
+ context 'for a type that is required to have all feature flags registered' do
+ before do
+ stub_const('Feature::Shared::TYPES', {
+ development: { optional: false }
+ })
+ end
+
+ it 'raises exception' do
+ expect do
+ described_class.valid_usage!(:unknown_feature_flag, type: :development, default_enabled: false)
+ end.to raise_error(/Missing feature definition for `unknown_feature_flag`/)
+ end
+ end
+
+ context 'for a type that is optional' do
+ before do
+ stub_const('Feature::Shared::TYPES', {
+ development: { optional: true }
+ })
+ end
+
+ it 'does not raise exception' do
+ expect do
+ described_class.valid_usage!(:unknown_feature_flag, type: :development, default_enabled: false)
+ end.not_to raise_error
+ end
+ end
+
+ context 'for an unknown type' do
+ it 'raises exception' do
+ expect do
+ described_class.valid_usage!(:unknown_feature_flag, type: :unknown_type, default_enabled: false)
+ end.to raise_error(/Unknown feature flag type used: `unknown_type`/)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/feature/gitaly_spec.rb b/spec/lib/feature/gitaly_spec.rb
index 6654b7627cd..a2181a63335 100644
--- a/spec/lib/feature/gitaly_spec.rb
+++ b/spec/lib/feature/gitaly_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Feature::Gitaly do
+RSpec.describe Feature::Gitaly do
let(:feature_flag) { "mep_mep" }
describe ".enabled?" do
diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb
index 37f8d3ad47d..acd7d97ac85 100644
--- a/spec/lib/feature_spec.rb
+++ b/spec/lib/feature_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Feature, stub_feature_flags: false do
+RSpec.describe Feature, stub_feature_flags: false do
before do
# reset Flipper AR-engine
Feature.reset
@@ -21,66 +21,29 @@ describe Feature, stub_feature_flags: false do
end
describe '.persisted_names' do
- context 'when FF_LEGACY_PERSISTED_NAMES=false' do
- before do
- stub_env('FF_LEGACY_PERSISTED_NAMES', 'false')
- end
-
- it 'returns the names of the persisted features' do
- Feature.enable('foo')
-
- expect(described_class.persisted_names).to contain_exactly('foo')
- end
-
- it 'returns an empty Array when no features are presisted' do
- expect(described_class.persisted_names).to be_empty
- end
-
- it 'caches the feature names when request store is active',
- :request_store, :use_clean_rails_memory_store_caching do
- Feature.enable('foo')
+ it 'returns the names of the persisted features' do
+ Feature.enable('foo')
- expect(Gitlab::ProcessMemoryCache.cache_backend)
- .to receive(:fetch)
- .once
- .with('flipper/v1/features', expires_in: 1.minute)
- .and_call_original
+ expect(described_class.persisted_names).to contain_exactly('foo')
+ end
- 2.times do
- expect(described_class.persisted_names).to contain_exactly('foo')
- end
- end
+ it 'returns an empty Array when no features are presisted' do
+ expect(described_class.persisted_names).to be_empty
end
- context 'when FF_LEGACY_PERSISTED_NAMES=true' do
- before do
- stub_env('FF_LEGACY_PERSISTED_NAMES', 'true')
- end
+ it 'caches the feature names when request store is active',
+ :request_store, :use_clean_rails_memory_store_caching do
+ Feature.enable('foo')
- it 'returns the names of the persisted features' do
- Feature.enable('foo')
+ expect(Gitlab::ProcessMemoryCache.cache_backend)
+ .to receive(:fetch)
+ .once
+ .with('flipper/v1/features', expires_in: 1.minute)
+ .and_call_original
+ 2.times do
expect(described_class.persisted_names).to contain_exactly('foo')
end
-
- it 'returns an empty Array when no features are presisted' do
- expect(described_class.persisted_names).to be_empty
- end
-
- it 'caches the feature names when request store is active',
- :request_store, :use_clean_rails_memory_store_caching do
- Feature.enable('foo')
-
- expect(Gitlab::ProcessMemoryCache.cache_backend)
- .to receive(:fetch)
- .once
- .with('flipper:persisted_names', expires_in: 1.minute)
- .and_call_original
-
- 2.times do
- expect(described_class.persisted_names).to contain_exactly('foo')
- end
- end
end
it 'fetches all flags once in a single query', :request_store do
@@ -279,6 +242,36 @@ describe Feature, stub_feature_flags: false do
end
end
end
+
+ context 'validates usage of feature flag with YAML definition' do
+ let(:definition) do
+ Feature::Definition.new('development/my_feature_flag.yml',
+ name: 'my_feature_flag',
+ type: 'development',
+ default_enabled: false
+ ).tap(&:validate!)
+ end
+
+ before do
+ allow(Feature::Definition).to receive(:definitions) do
+ { definition.key => definition }
+ end
+ end
+
+ it 'when usage is correct' do
+ expect { described_class.enabled?(:my_feature_flag) }.not_to raise_error
+ end
+
+ it 'when invalid type is used' do
+ expect { described_class.enabled?(:my_feature_flag, type: :licensed) }
+ .to raise_error(/The `type:` of/)
+ end
+
+ it 'when invalid default_enabled is used' do
+ expect { described_class.enabled?(:my_feature_flag, default_enabled: true) }
+ .to raise_error(/The `default_enabled:` of/)
+ end
+ end
end
describe '.disable?' do
diff --git a/spec/lib/file_size_validator_spec.rb b/spec/lib/file_size_validator_spec.rb
index 87376a98c60..c5cb7d6eb30 100644
--- a/spec/lib/file_size_validator_spec.rb
+++ b/spec/lib/file_size_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe FileSizeValidator do
+RSpec.describe FileSizeValidator do
let(:validator) { described_class.new(options) }
let(:note) { create(:note) }
let(:attachment) { AttachmentUploader.new(note) }
diff --git a/spec/lib/forever_spec.rb b/spec/lib/forever_spec.rb
index 9f17308241b..6f6b3055df5 100644
--- a/spec/lib/forever_spec.rb
+++ b/spec/lib/forever_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Forever do
+RSpec.describe Forever do
describe '.date' do
subject { described_class.date }
diff --git a/spec/lib/gitaly/server_spec.rb b/spec/lib/gitaly/server_spec.rb
index 390855b30ad..83df4b28757 100644
--- a/spec/lib/gitaly/server_spec.rb
+++ b/spec/lib/gitaly/server_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitaly::Server do
+RSpec.describe Gitaly::Server do
let(:server) { described_class.new('default') }
describe '.all' do
diff --git a/spec/lib/gitlab/access/branch_protection_spec.rb b/spec/lib/gitlab/access/branch_protection_spec.rb
index e4b763357c4..9b736a30c7e 100644
--- a/spec/lib/gitlab/access/branch_protection_spec.rb
+++ b/spec/lib/gitlab/access/branch_protection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Access::BranchProtection do
+RSpec.describe Gitlab::Access::BranchProtection do
describe '#any?' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/lib/gitlab/alert_management/alert_params_spec.rb b/spec/lib/gitlab/alert_management/alert_params_spec.rb
index 284af421f05..393838ab042 100644
--- a/spec/lib/gitlab/alert_management/alert_params_spec.rb
+++ b/spec/lib/gitlab/alert_management/alert_params_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::AlertManagement::AlertParams do
+RSpec.describe Gitlab::AlertManagement::AlertParams do
let_it_be(:project) { create(:project, :repository, :private) }
describe '.from_generic_alert' do
@@ -88,7 +88,9 @@ describe Gitlab::AlertManagement::AlertParams do
payload: payload,
started_at: parsed_alert.starts_at,
ended_at: parsed_alert.ends_at,
- fingerprint: parsed_alert.gitlab_fingerprint
+ fingerprint: parsed_alert.gitlab_fingerprint,
+ environment: parsed_alert.environment,
+ prometheus_alert: parsed_alert.gitlab_alert
)
end
end
diff --git a/spec/lib/gitlab/alert_management/alert_status_counts_spec.rb b/spec/lib/gitlab/alert_management/alert_status_counts_spec.rb
index 728cbf11cda..4e471a8eac0 100644
--- a/spec/lib/gitlab/alert_management/alert_status_counts_spec.rb
+++ b/spec/lib/gitlab/alert_management/alert_status_counts_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::AlertManagement::AlertStatusCounts do
+RSpec.describe Gitlab::AlertManagement::AlertStatusCounts do
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:alert_1) { create(:alert_management_alert, :resolved, project: project) }
diff --git a/spec/lib/gitlab/alert_management/fingerprint_spec.rb b/spec/lib/gitlab/alert_management/fingerprint_spec.rb
index 7865d667f71..8a933963f6a 100644
--- a/spec/lib/gitlab/alert_management/fingerprint_spec.rb
+++ b/spec/lib/gitlab/alert_management/fingerprint_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::AlertManagement::Fingerprint do
+RSpec.describe Gitlab::AlertManagement::Fingerprint do
using RSpec::Parameterized::TableSyntax
let_it_be(:alert) { create(:alert_management_alert) }
@@ -13,34 +13,62 @@ describe Gitlab::AlertManagement::Fingerprint do
context 'when data is an array' do
let(:data) { [1, 'fingerprint', 'given'] }
- it 'flattens the array' do
- expect_next_instance_of(described_class) do |obj|
- expect(obj).to receive(:flatten_array)
- end
-
- subject
- end
-
it 'returns the hashed fingerprint' do
expected_fingerprint = Digest::SHA1.hexdigest(data.flatten.map!(&:to_s).join)
expect(subject).to eq(expected_fingerprint)
end
- end
- context 'when data is a non-array type' do
- where(:data) do
- [
- 111,
- 'fingerprint',
- :fingerprint,
- true,
- { test: true }
- ]
+ context 'with a variety of data' do
+ where(:data) do
+ [
+ 111,
+ 'fingerprint',
+ :fingerprint,
+ true
+ ]
+ end
+
+ with_them do
+ it 'performs like a hashed fingerprint' do
+ expect(subject).to eq(Digest::SHA1.hexdigest(data.to_s))
+ end
+ end
end
+ end
- with_them do
+ context 'when data is a hash' do
+ let(:data) { { test: true } }
+
+ shared_examples 'fingerprinted Hash' do
it 'performs like a hashed fingerprint' do
- expect(subject).to eq(Digest::SHA1.hexdigest(data.to_s))
+ flattened_hash = Gitlab::Utils::SafeInlineHash.merge_keys!(data).sort.to_s
+ expect(subject).to eq(Digest::SHA1.hexdigest(flattened_hash))
+ end
+ end
+
+ it_behaves_like 'fingerprinted Hash'
+
+ context 'hashes with different order' do
+ it 'calculates the same result' do
+ data = { test: true, another_test: 1 }
+ data_hash = described_class.generate(data)
+
+ reverse_data = { another_test: 1, test: true }
+ reverse_data_hash = described_class.generate(reverse_data)
+
+ expect(data_hash).to eq(reverse_data_hash)
+ end
+ end
+
+ context 'hash is too large' do
+ before do
+ expect_next_instance_of(Gitlab::Utils::SafeInlineHash) do |obj|
+ expect(obj).to receive(:valid?).and_return(false)
+ end
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(ArgumentError)
end
end
end
diff --git a/spec/lib/gitlab/alerting/alert_spec.rb b/spec/lib/gitlab/alerting/alert_spec.rb
index d582ff6f32a..9663e6af0d2 100644
--- a/spec/lib/gitlab/alerting/alert_spec.rb
+++ b/spec/lib/gitlab/alerting/alert_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Alerting::Alert do
+RSpec.describe Gitlab::Alerting::Alert do
let_it_be(:project) { create(:project) }
let(:alert) { build(:alerting_alert, project: project, payload: payload) }
diff --git a/spec/lib/gitlab/alerting/notification_payload_parser_spec.rb b/spec/lib/gitlab/alerting/notification_payload_parser_spec.rb
index 889efae9585..0489108b159 100644
--- a/spec/lib/gitlab/alerting/notification_payload_parser_spec.rb
+++ b/spec/lib/gitlab/alerting/notification_payload_parser_spec.rb
@@ -2,7 +2,9 @@
require 'spec_helper'
-describe Gitlab::Alerting::NotificationPayloadParser do
+RSpec.describe Gitlab::Alerting::NotificationPayloadParser do
+ let_it_be(:project) { build(:project) }
+
describe '.call' do
let(:starts_at) { Time.current.change(usec: 0) }
let(:payload) do
@@ -17,7 +19,7 @@ describe Gitlab::Alerting::NotificationPayloadParser do
}
end
- subject { described_class.call(payload) }
+ subject { described_class.call(payload, project) }
it 'returns Prometheus-like payload' do
is_expected.to eq(
diff --git a/spec/lib/gitlab/allowable_spec.rb b/spec/lib/gitlab/allowable_spec.rb
index 4905cc4c3db..0535384be6e 100644
--- a/spec/lib/gitlab/allowable_spec.rb
+++ b/spec/lib/gitlab/allowable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Allowable do
+RSpec.describe Gitlab::Allowable do
subject do
Class.new.include(described_class).new
end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb
index 250e2f16aec..80d3f82b404 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder do
let_it_be(:project) { create(:project, :empty_repo) }
let_it_be(:mr1) { create(:merge_request, target_project: project, source_project: project, allow_broken: true, created_at: 3.months.ago) }
let_it_be(:mr2) { create(:merge_request, target_project: project, source_project: project, allow_broken: true, created_at: 1.month.ago) }
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb
index 92ecec350ae..c1ea000eb7b 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::Median do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::Median do
let_it_be(:project) { create(:project, :repository) }
let(:query) { Project.joins(merge_requests: :metrics) }
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb
index e3429b0ca57..b8f9dde4291 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
around do |example|
Timecop.freeze { example.run }
end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb
index 29c8d548754..fe390289ef6 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::StageEvents::CodeStageStart do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::CodeStageStart do
let(:subject) { described_class.new({}) }
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created_spec.rb
index efdef91c5a2..5cc6b05407f 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::StageEvents::IssueCreated do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::IssueCreated do
it_behaves_like 'cycle analytics event'
end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit_spec.rb
index 50883e1c1e2..715ad5a8e7d 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstMentionedInCommit do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstMentionedInCommit do
it_behaves_like 'cycle analytics event'
end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end_spec.rb
index 85062db370a..56241194f36 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::StageEvents::IssueStageEnd do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::IssueStageEnd do
it_behaves_like 'cycle analytics event'
end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created_spec.rb
index 7858b810661..f3202eab5bb 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestCreated do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestCreated do
it_behaves_like 'cycle analytics event'
end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production_spec.rb
index ba9d8be5a2c..03b0ccfae43 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestFirstDeployedToProduction do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestFirstDeployedToProduction do
it_behaves_like 'cycle analytics event'
end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished_spec.rb
index 8e83e10ef96..b0c003e6f2a 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestLastBuildFinished do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestLastBuildFinished do
it_behaves_like 'cycle analytics event'
end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started_spec.rb
index 9f6b430a320..8f9aaf6f463 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestLastBuildStarted do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestLastBuildStarted do
it_behaves_like 'cycle analytics event'
end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged_spec.rb
index ce2aa0a60db..f1d2ca9f36e 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestMerged do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestMerged do
it_behaves_like 'cycle analytics event'
end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start_spec.rb
index cb63139f0a8..3248af524bd 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::StageEvents::PlanStageStart do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::PlanStageStart do
let(:subject) { described_class.new({}) }
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb
index b05faf5d813..6fc658ecade 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Analytics::CycleAnalytics::StageEvents::StageEvent do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::StageEvent do
let(:instance) { described_class.new({}) }
it { expect(described_class).to respond_to(:name) }
diff --git a/spec/lib/gitlab/analytics/unique_visits_spec.rb b/spec/lib/gitlab/analytics/unique_visits_spec.rb
new file mode 100644
index 00000000000..ff3623a3a71
--- /dev/null
+++ b/spec/lib/gitlab/analytics/unique_visits_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Analytics::UniqueVisits, :clean_gitlab_redis_shared_state do
+ let(:unique_visits) { Gitlab::Analytics::UniqueVisits.new }
+ let(:target1_id) { 'g_analytics_contribution' }
+ let(:target2_id) { 'g_analytics_insights' }
+ let(:target3_id) { 'g_analytics_issues' }
+ let(:visitor1_id) { 'dfb9d2d2-f56c-4c77-8aeb-6cddc4a1f857' }
+ let(:visitor2_id) { '1dd9afb2-a3ee-4de1-8ae3-a405579c8584' }
+
+ around do |example|
+ # We need to freeze to a reference time
+ # because visits are grouped by the week number in the year
+ # Without freezing the time, the test may behave inconsistently
+ # depending on which day of the week test is run.
+ reference_time = Time.utc(2020, 6, 1)
+ Timecop.freeze(reference_time) { example.run }
+ end
+
+ describe '#track_visit' do
+ it 'tracks the unique weekly visits for targets' do
+ unique_visits.track_visit(visitor1_id, target1_id, 7.days.ago)
+ unique_visits.track_visit(visitor1_id, target1_id, 7.days.ago)
+ unique_visits.track_visit(visitor2_id, target1_id, 7.days.ago)
+
+ unique_visits.track_visit(visitor2_id, target2_id, 7.days.ago)
+ unique_visits.track_visit(visitor1_id, target2_id, 8.days.ago)
+ unique_visits.track_visit(visitor1_id, target2_id, 15.days.ago)
+
+ expect(unique_visits.weekly_unique_visits_for_target(target1_id)).to eq(2)
+ expect(unique_visits.weekly_unique_visits_for_target(target2_id)).to eq(1)
+
+ expect(unique_visits.weekly_unique_visits_for_target(target2_id, week_of: 15.days.ago)).to eq(1)
+
+ expect(unique_visits.weekly_unique_visits_for_target(target3_id)).to eq(0)
+
+ expect(unique_visits.weekly_unique_visits_for_any_target).to eq(2)
+ expect(unique_visits.weekly_unique_visits_for_any_target(week_of: 15.days.ago)).to eq(1)
+ expect(unique_visits.weekly_unique_visits_for_any_target(week_of: 30.days.ago)).to eq(0)
+ end
+
+ it 'sets the keys in Redis to expire automatically after 28 days' do
+ unique_visits.track_visit(visitor1_id, target1_id)
+
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.scan_each(match: "#{target1_id}-*").each do |key|
+ expect(redis.ttl(key)).to be_within(5.seconds).of(28.days)
+ end
+ end
+ end
+
+ it 'raises an error if an invalid target id is given' do
+ invalid_target_id = "x_invalid"
+
+ expect do
+ unique_visits.track_visit(visitor1_id, invalid_target_id)
+ end.to raise_error("Invalid target id #{invalid_target_id}")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/anonymous_session_spec.rb b/spec/lib/gitlab/anonymous_session_spec.rb
index 94daa0f2470..0f0795cd9fc 100644
--- a/spec/lib/gitlab/anonymous_session_spec.rb
+++ b/spec/lib/gitlab/anonymous_session_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::AnonymousSession, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::AnonymousSession, :clean_gitlab_redis_shared_state do
let(:default_session_id) { '6919a6f1bb119dd7396fadc38fd18d0d' }
let(:additional_session_id) { '7919a6f1bb119dd7396fadc38fd18d0d' }
diff --git a/spec/lib/gitlab/app_json_logger_spec.rb b/spec/lib/gitlab/app_json_logger_spec.rb
index d11456236cc..89dce969522 100644
--- a/spec/lib/gitlab/app_json_logger_spec.rb
+++ b/spec/lib/gitlab/app_json_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::AppJsonLogger do
+RSpec.describe Gitlab::AppJsonLogger do
subject { described_class.new('/dev/null') }
let(:hash_message) { { 'message' => 'Message', 'project_id' => '123' } }
diff --git a/spec/lib/gitlab/app_logger_spec.rb b/spec/lib/gitlab/app_logger_spec.rb
index 132a10b9409..166b1fda268 100644
--- a/spec/lib/gitlab/app_logger_spec.rb
+++ b/spec/lib/gitlab/app_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::AppLogger do
+RSpec.describe Gitlab::AppLogger do
subject { described_class }
it 'builds a Gitlab::Logger object twice' do
diff --git a/spec/lib/gitlab/app_text_logger_spec.rb b/spec/lib/gitlab/app_text_logger_spec.rb
index c84b986ce40..04c2e946640 100644
--- a/spec/lib/gitlab/app_text_logger_spec.rb
+++ b/spec/lib/gitlab/app_text_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::AppTextLogger do
+RSpec.describe Gitlab::AppTextLogger do
subject { described_class.new('/dev/null') }
let(:hash_message) { { message: 'Message', project_id: 123 } }
diff --git a/spec/lib/gitlab/application_context_spec.rb b/spec/lib/gitlab/application_context_spec.rb
index 3be967ac8a4..88f865adea7 100644
--- a/spec/lib/gitlab/application_context_spec.rb
+++ b/spec/lib/gitlab/application_context_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ApplicationContext do
+RSpec.describe Gitlab::ApplicationContext do
describe '.with_context' do
it 'yields the block' do
expect { |b| described_class.with_context({}, &b) }.to yield_control
diff --git a/spec/lib/gitlab/application_rate_limiter_spec.rb b/spec/lib/gitlab/application_rate_limiter_spec.rb
index f1a0163d91c..14a7e25a2e8 100644
--- a/spec/lib/gitlab/application_rate_limiter_spec.rb
+++ b/spec/lib/gitlab/application_rate_limiter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ApplicationRateLimiter, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::ApplicationRateLimiter, :clean_gitlab_redis_cache do
let(:redis) { double('redis') }
let(:user) { create(:user) }
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/asciidoc/include_processor_spec.rb b/spec/lib/gitlab/asciidoc/include_processor_spec.rb
index 2781319567c..067dcefb525 100644
--- a/spec/lib/gitlab/asciidoc/include_processor_spec.rb
+++ b/spec/lib/gitlab/asciidoc/include_processor_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'nokogiri'
-describe Gitlab::Asciidoc::IncludeProcessor do
+RSpec.describe Gitlab::Asciidoc::IncludeProcessor do
let_it_be(:project) { create(:project, :repository) }
let(:processor_context) do
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index 24528d79fa8..40a4ab3e173 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require 'nokogiri'
module Gitlab
- describe Asciidoc do
+ RSpec.describe Asciidoc do
include FakeBlobHelpers
before do
diff --git a/spec/lib/gitlab/asset_proxy_spec.rb b/spec/lib/gitlab/asset_proxy_spec.rb
index e406917a5a4..73b101c0dd8 100644
--- a/spec/lib/gitlab/asset_proxy_spec.rb
+++ b/spec/lib/gitlab/asset_proxy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::AssetProxy do
+RSpec.describe Gitlab::AssetProxy do
context 'when asset proxy is disabled' do
before do
stub_asset_proxy_setting(enabled: false)
diff --git a/spec/lib/gitlab/auth/activity_spec.rb b/spec/lib/gitlab/auth/activity_spec.rb
index e03fafe3826..cbc42c46470 100644
--- a/spec/lib/gitlab/auth/activity_spec.rb
+++ b/spec/lib/gitlab/auth/activity_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Auth::Activity do
+RSpec.describe Gitlab::Auth::Activity do
describe '.each_counter' do
it 'has all static counters defined' do
described_class.each_counter do |counter|
diff --git a/spec/lib/gitlab/auth/auth_finders_spec.rb b/spec/lib/gitlab/auth/auth_finders_spec.rb
index 2aef206c7fd..d0f5d0a9b35 100644
--- a/spec/lib/gitlab/auth/auth_finders_spec.rb
+++ b/spec/lib/gitlab/auth/auth_finders_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::AuthFinders do
+RSpec.describe Gitlab::Auth::AuthFinders do
include described_class
include HttpBasicAuthHelpers
@@ -26,6 +26,63 @@ describe Gitlab::Auth::AuthFinders do
env.merge!(basic_auth_header(username, password))
end
+ shared_examples 'find user from job token' do
+ context 'when route is allowed to be authenticated' do
+ let(:route_authentication_setting) { { job_token_allowed: true } }
+
+ it "returns an Unauthorized exception for an invalid token" do
+ set_token('invalid token')
+
+ expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
+ end
+
+ it "return user if token is valid" do
+ set_token(job.token)
+
+ expect(subject).to eq(user)
+ expect(@current_authenticated_job).to eq job
+ end
+ end
+ end
+
+ describe '#find_user_from_bearer_token' do
+ let(:job) { create(:ci_build, user: user) }
+
+ subject { find_user_from_bearer_token }
+
+ context 'when the token is passed as an oauth token' do
+ def set_token(token)
+ env['HTTP_AUTHORIZATION'] = "Bearer #{token}"
+ end
+
+ context 'with a job token' do
+ it_behaves_like 'find user from job token'
+ end
+
+ context 'with oauth token' do
+ let(:application) { Doorkeeper::Application.create!(name: 'MyApp', redirect_uri: 'https://app.com', owner: user) }
+ let(:token) { Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'api').token }
+
+ before do
+ set_token(token)
+ end
+
+ it { is_expected.to eq user }
+ end
+ end
+
+ context 'with a personal access token' do
+ let(:pat) { create(:personal_access_token, user: user) }
+ let(:token) { pat.token }
+
+ before do
+ env[described_class::PRIVATE_TOKEN_HEADER] = pat.token
+ end
+
+ it { is_expected.to eq user }
+ end
+ end
+
describe '#find_user_from_warden' do
context 'with CSRF token' do
before do
@@ -522,8 +579,24 @@ describe Gitlab::Auth::AuthFinders do
end
describe '#validate_access_token!' do
+ subject { validate_access_token! }
+
let(:personal_access_token) { create(:personal_access_token, user: user) }
+ context 'with a job token' do
+ let(:route_authentication_setting) { { job_token_allowed: true } }
+ let(:job) { create(:ci_build, user: user) }
+
+ before do
+ env['HTTP_AUTHORIZATION'] = "Bearer #{job.token}"
+ find_user_from_bearer_token
+ end
+
+ it 'does not raise an error' do
+ expect { subject }.not_to raise_error
+ end
+ end
+
it 'returns nil if no access_token present' do
expect(validate_access_token!).to be_nil
end
diff --git a/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb b/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb
index 52849f8c172..76775db3a4a 100644
--- a/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb
+++ b/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::BlockedUserTracker do
+RSpec.describe Gitlab::Auth::BlockedUserTracker do
describe '#log_blocked_user_activity!' do
context 'when user is not blocked' do
it 'does not log blocked user activity' do
diff --git a/spec/lib/gitlab/auth/current_user_mode_spec.rb b/spec/lib/gitlab/auth/current_user_mode_spec.rb
index 26e44fa7cc8..60b403780c0 100644
--- a/spec/lib/gitlab/auth/current_user_mode_spec.rb
+++ b/spec/lib/gitlab/auth/current_user_mode_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode, :request_store do
+RSpec.describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode, :request_store do
let(:user) { build_stubbed(:user) }
subject { described_class.new(user) }
diff --git a/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb b/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb
index aea1b2921b6..3d782272d7e 100644
--- a/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb
+++ b/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::IpRateLimiter, :use_clean_rails_memory_store_caching do
+RSpec.describe Gitlab::Auth::IpRateLimiter, :use_clean_rails_memory_store_caching do
let(:ip) { '10.2.2.3' }
let(:whitelist) { ['127.0.0.1'] }
let(:options) do
diff --git a/spec/lib/gitlab/auth/key_status_checker_spec.rb b/spec/lib/gitlab/auth/key_status_checker_spec.rb
index b1a540eae81..e8ac0d7c394 100644
--- a/spec/lib/gitlab/auth/key_status_checker_spec.rb
+++ b/spec/lib/gitlab/auth/key_status_checker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::KeyStatusChecker do
+RSpec.describe Gitlab::Auth::KeyStatusChecker do
let_it_be(:never_expires_key) { build(:personal_key, expires_at: nil) }
let_it_be(:expired_key) { build(:personal_key, expires_at: 3.days.ago) }
let_it_be(:expiring_soon_key) { build(:personal_key, expires_at: 3.days.from_now) }
diff --git a/spec/lib/gitlab/auth/ldap/access_spec.rb b/spec/lib/gitlab/auth/ldap/access_spec.rb
index 2f691429541..9e269f84b7e 100644
--- a/spec/lib/gitlab/auth/ldap/access_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Ldap::Access do
+RSpec.describe Gitlab::Auth::Ldap::Access do
include LdapHelpers
let(:user) { create(:omniauth_user) }
diff --git a/spec/lib/gitlab/auth/ldap/adapter_spec.rb b/spec/lib/gitlab/auth/ldap/adapter_spec.rb
index 34853acdd0f..78970378b7f 100644
--- a/spec/lib/gitlab/auth/ldap/adapter_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/adapter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Ldap::Adapter do
+RSpec.describe Gitlab::Auth::Ldap::Adapter do
include LdapHelpers
let(:ldap) { double(:ldap) }
diff --git a/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb b/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb
index 7bc92d0abea..9dff7f7b3dc 100644
--- a/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Ldap::AuthHash do
+RSpec.describe Gitlab::Auth::Ldap::AuthHash do
include LdapHelpers
let(:auth_hash) do
diff --git a/spec/lib/gitlab/auth/ldap/authentication_spec.rb b/spec/lib/gitlab/auth/ldap/authentication_spec.rb
index 1f8b1474539..42a893417d8 100644
--- a/spec/lib/gitlab/auth/ldap/authentication_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/authentication_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Ldap::Authentication do
+RSpec.describe Gitlab::Auth::Ldap::Authentication do
let(:dn) { 'uid=John Smith, ou=People, dc=example, dc=com' }
let(:user) { create(:omniauth_user, extern_uid: Gitlab::Auth::Ldap::Person.normalize_dn(dn)) }
let(:login) { 'john' }
diff --git a/spec/lib/gitlab/auth/ldap/config_spec.rb b/spec/lib/gitlab/auth/ldap/config_spec.rb
index 124f072ebe6..4287596af8f 100644
--- a/spec/lib/gitlab/auth/ldap/config_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/config_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Ldap::Config do
+RSpec.describe Gitlab::Auth::Ldap::Config do
include LdapHelpers
let(:config) { described_class.new('ldapmain') }
diff --git a/spec/lib/gitlab/auth/ldap/dn_spec.rb b/spec/lib/gitlab/auth/ldap/dn_spec.rb
index 7aaffa52ae4..e89f764b040 100644
--- a/spec/lib/gitlab/auth/ldap/dn_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/dn_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Ldap::DN do
+RSpec.describe Gitlab::Auth::Ldap::DN do
using RSpec::Parameterized::TableSyntax
describe '#normalize_value' do
diff --git a/spec/lib/gitlab/auth/ldap/person_spec.rb b/spec/lib/gitlab/auth/ldap/person_spec.rb
index 403a48d40ef..6857b561370 100644
--- a/spec/lib/gitlab/auth/ldap/person_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/person_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Ldap::Person do
+RSpec.describe Gitlab::Auth::Ldap::Person do
include LdapHelpers
let(:entry) { ldap_user_entry('john.doe') }
diff --git a/spec/lib/gitlab/auth/ldap/user_spec.rb b/spec/lib/gitlab/auth/ldap/user_spec.rb
index 867633e54df..7ca2878e583 100644
--- a/spec/lib/gitlab/auth/ldap/user_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Ldap::User do
+RSpec.describe Gitlab::Auth::Ldap::User do
include LdapHelpers
let(:ldap_user) { described_class.new(auth_hash) }
diff --git a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
index a2d9e27ea5b..7a60acca95b 100644
--- a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::OAuth::AuthHash do
+RSpec.describe Gitlab::Auth::OAuth::AuthHash do
let(:provider) { 'ldap'.freeze }
let(:auth_hash) do
described_class.new(
diff --git a/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb b/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb
index 45c1baa4089..8014fbe1687 100644
--- a/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::OAuth::IdentityLinker do
+RSpec.describe Gitlab::Auth::OAuth::IdentityLinker do
let(:user) { create(:user) }
let(:provider) { 'twitter' }
let(:uid) { user.email }
diff --git a/spec/lib/gitlab/auth/o_auth/provider_spec.rb b/spec/lib/gitlab/auth/o_auth/provider_spec.rb
index 8b0d4d786cd..658a9976cc2 100644
--- a/spec/lib/gitlab/auth/o_auth/provider_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/provider_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::OAuth::Provider do
+RSpec.describe Gitlab::Auth::OAuth::Provider do
describe '.enabled?' do
before do
allow(described_class).to receive(:providers).and_return([:ldapmain, :google_oauth2])
diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb
index 62b83ff8b88..ad04fddc675 100644
--- a/spec/lib/gitlab/auth/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::OAuth::User do
+RSpec.describe Gitlab::Auth::OAuth::User do
include LdapHelpers
let(:oauth_user) { described_class.new(auth_hash) }
diff --git a/spec/lib/gitlab/auth/request_authenticator_spec.rb b/spec/lib/gitlab/auth/request_authenticator_spec.rb
index 87c96803c3a..32d64519e2c 100644
--- a/spec/lib/gitlab/auth/request_authenticator_spec.rb
+++ b/spec/lib/gitlab/auth/request_authenticator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::RequestAuthenticator do
+RSpec.describe Gitlab::Auth::RequestAuthenticator do
let(:env) do
{
'rack.input' => '',
diff --git a/spec/lib/gitlab/auth/saml/auth_hash_spec.rb b/spec/lib/gitlab/auth/saml/auth_hash_spec.rb
index 8b88c16f317..f1fad946f35 100644
--- a/spec/lib/gitlab/auth/saml/auth_hash_spec.rb
+++ b/spec/lib/gitlab/auth/saml/auth_hash_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Saml::AuthHash do
+RSpec.describe Gitlab::Auth::Saml::AuthHash do
include LoginHelpers
let(:raw_info_attr) { { 'groups' => %w(Developers Freelancers) } }
diff --git a/spec/lib/gitlab/auth/saml/identity_linker_spec.rb b/spec/lib/gitlab/auth/saml/identity_linker_spec.rb
index 7912c8fb4b1..743163ad315 100644
--- a/spec/lib/gitlab/auth/saml/identity_linker_spec.rb
+++ b/spec/lib/gitlab/auth/saml/identity_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Saml::IdentityLinker do
+RSpec.describe Gitlab::Auth::Saml::IdentityLinker do
let(:user) { create(:user) }
let(:provider) { 'saml' }
let(:uid) { user.email }
diff --git a/spec/lib/gitlab/auth/saml/origin_validator_spec.rb b/spec/lib/gitlab/auth/saml/origin_validator_spec.rb
index ae120b328ab..f13140cdcba 100644
--- a/spec/lib/gitlab/auth/saml/origin_validator_spec.rb
+++ b/spec/lib/gitlab/auth/saml/origin_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Saml::OriginValidator do
+RSpec.describe Gitlab::Auth::Saml::OriginValidator do
let(:session) { instance_double(ActionDispatch::Request::Session) }
subject { described_class.new(session) }
diff --git a/spec/lib/gitlab/auth/saml/user_spec.rb b/spec/lib/gitlab/auth/saml/user_spec.rb
index 55d2f22b923..7f8346f0486 100644
--- a/spec/lib/gitlab/auth/saml/user_spec.rb
+++ b/spec/lib/gitlab/auth/saml/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::Saml::User do
+RSpec.describe Gitlab::Auth::Saml::User do
include LdapHelpers
include LoginHelpers
diff --git a/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb b/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb
index ebf7de9c701..a08055ab852 100644
--- a/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb
+++ b/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::UniqueIpsLimiter, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::Auth::UniqueIpsLimiter, :clean_gitlab_redis_shared_state do
include_context 'unique ips sign in limit'
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb b/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb
index 7045105a2c7..a2a0eb5428a 100644
--- a/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb
+++ b/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::UserAccessDeniedReason do
+RSpec.describe Gitlab::Auth::UserAccessDeniedReason do
include TermsHelper
let(:user) { build(:user) }
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 870f02b6933..0b391c8cba9 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
+RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
let_it_be(:project) { create(:project) }
let(:gl_auth) { described_class }
@@ -172,7 +172,7 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
end
end
- (HasStatus::AVAILABLE_STATUSES - ['running']).each do |build_status|
+ (Ci::HasStatus::AVAILABLE_STATUSES - ['running']).each do |build_status|
context "for #{build_status} build" do
let!(:build) { create(:ci_build, status: build_status) }
let(:project) { build.project }
diff --git a/spec/lib/gitlab/authorized_keys_spec.rb b/spec/lib/gitlab/authorized_keys_spec.rb
index d89eb9ef114..1053ae2e325 100644
--- a/spec/lib/gitlab/authorized_keys_spec.rb
+++ b/spec/lib/gitlab/authorized_keys_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::AuthorizedKeys do
+RSpec.describe Gitlab::AuthorizedKeys do
let(:logger) { double('logger').as_null_object }
subject(:authorized_keys) { described_class.new(logger) }
diff --git a/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb b/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
index b77c67b120f..1e72b249c19 100644
--- a/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
+++ b/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
@@ -2,13 +2,15 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::AddMergeRequestDiffCommitsCount, schema: 20180105212544 do
+RSpec.describe Gitlab::BackgroundMigration::AddMergeRequestDiffCommitsCount do
+ let(:namespaces_table) { table(:namespaces) }
let(:projects_table) { table(:projects) }
let(:merge_requests_table) { table(:merge_requests) }
let(:merge_request_diffs_table) { table(:merge_request_diffs) }
let(:merge_request_diff_commits_table) { table(:merge_request_diff_commits) }
- let(:project) { projects_table.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce') }
+ let(:namespace) { namespaces_table.create!(name: 'gitlab-org', path: 'gitlab-org') }
+ let(:project) { projects_table.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce', namespace_id: namespace.id) }
let(:merge_request) do
merge_requests_table.create!(target_project_id: project.id,
target_branch: 'master',
diff --git a/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb b/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb
index 5c8dcb38511..7991ad69007 100644
--- a/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb
+++ b/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::ArchiveLegacyTraces, schema: 20180529152628 do
+RSpec.describe Gitlab::BackgroundMigration::ArchiveLegacyTraces do
include TraceHelpers
let(:namespaces) { table(:namespaces) }
diff --git a/spec/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments_spec.rb b/spec/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments_spec.rb
index fdabc8e8f7c..2be9c03e5bd 100644
--- a/spec/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::BackfillDeploymentClustersFromDeployments, :migration, schema: 20200227140242 do
+RSpec.describe Gitlab::BackgroundMigration::BackfillDeploymentClustersFromDeployments, :migration, schema: 20200227140242 do
subject { described_class.new }
describe '#perform' do
diff --git a/spec/lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests_spec.rb b/spec/lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests_spec.rb
index 34ac70071bb..550bdc484c9 100644
--- a/spec/lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::BackfillEnvironmentIdDeploymentMergeRequests, schema: 20200312134637 do
+RSpec.describe Gitlab::BackgroundMigration::BackfillEnvironmentIdDeploymentMergeRequests, schema: 20200312134637 do
let(:environments) { table(:environments) }
let(:merge_requests) { table(:merge_requests) }
let(:deployments) { table(:deployments) }
diff --git a/spec/lib/gitlab/background_migration/backfill_hashed_project_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_hashed_project_repositories_spec.rb
index f64c3ccc058..79b344ea6fa 100644
--- a/spec/lib/gitlab/background_migration/backfill_hashed_project_repositories_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_hashed_project_repositories_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::BackfillHashedProjectRepositories, schema: 20181130102132 do
+RSpec.describe Gitlab::BackgroundMigration::BackfillHashedProjectRepositories do
it_behaves_like 'backfill migration for project repositories', :hashed
end
diff --git a/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb
index 806d044ab40..c4013d002b2 100644
--- a/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::BackfillLegacyProjectRepositories, schema: 20181212171634 do
+RSpec.describe Gitlab::BackgroundMigration::BackfillLegacyProjectRepositories do
it_behaves_like 'backfill migration for project repositories', :legacy
end
diff --git a/spec/lib/gitlab/background_migration/backfill_namespace_settings_spec.rb b/spec/lib/gitlab/background_migration/backfill_namespace_settings_spec.rb
new file mode 100644
index 00000000000..43e76a2952e
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_namespace_settings_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BackfillNamespaceSettings, schema: 20200703125016 do
+ let(:namespaces) { table(:namespaces) }
+ let(:namespace_settings) { table(:namespace_settings) }
+ let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
+
+ subject { described_class.new }
+
+ describe '#perform' do
+ it 'creates settings for all projects in range' do
+ namespaces.create!(id: 5, name: 'test1', path: 'test1')
+ namespaces.create!(id: 7, name: 'test2', path: 'test2')
+ namespaces.create!(id: 8, name: 'test3', path: 'test3')
+
+ subject.perform(5, 7)
+
+ expect(namespace_settings.all.pluck(:namespace_id)).to contain_exactly(5, 7)
+ 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
index 44f5c3380a1..1b2e1ed0c1a 100644
--- 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
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::BackfillProjectFullpathInRepoConfig, schema: 20181010133639 do
+RSpec.describe Gitlab::BackgroundMigration::BackfillProjectFullpathInRepoConfig do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:group) { namespaces.create!(name: 'foo', path: 'foo') }
diff --git a/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb
index cfaef1578a9..8a8edc1af29 100644
--- a/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
# rubocop:disable RSpec/FactoriesInMigrationSpecs
-describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
+RSpec.describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
let(:group) { create(:group, name: 'foo', path: 'foo') }
describe described_class::ShardFinder do
diff --git a/spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb
index a2b4e003d82..4e7a3a33f7e 100644
--- a/spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::BackfillProjectSettings, schema: 20200114113341 do
+RSpec.describe Gitlab::BackgroundMigration::BackfillProjectSettings, schema: 20200114113341 do
let(:projects) { table(:projects) }
let(:project_settings) { table(:project_settings) }
let(:namespace) { table(:namespaces).create(name: 'user', path: 'user') }
diff --git a/spec/lib/gitlab/background_migration/backfill_push_rules_id_in_projects_spec.rb b/spec/lib/gitlab/background_migration/backfill_push_rules_id_in_projects_spec.rb
index f150ed4bd2e..39b49d008d4 100644
--- a/spec/lib/gitlab/background_migration/backfill_push_rules_id_in_projects_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_push_rules_id_in_projects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::BackfillPushRulesIdInProjects, :migration, schema: 2020_03_25_162730 do
+RSpec.describe Gitlab::BackgroundMigration::BackfillPushRulesIdInProjects, :migration, schema: 2020_03_25_162730 do
let(:push_rules) { table(:push_rules) }
let(:projects) { table(:projects) }
let(:project_settings) { table(:project_settings) }
diff --git a/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
index 27ae60eb278..ec2fd3cc4e0 100644
--- a/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
@@ -2,14 +2,13 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migration, schema: 2020_04_20_094444 do
+RSpec.describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migration, schema: 2020_04_20_094444 do
let(:gitlab_shell) { Gitlab::Shell.new }
let(:users) { table(:users) }
let(:snippets) { table(:snippets) }
let(:snippet_repositories) { table(:snippet_repositories) }
let(:user_state) { 'active' }
- let(:ghost) { false }
let(:user_type) { nil }
let(:user_name) { 'Test' }
@@ -20,13 +19,20 @@ describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migration, s
username: 'test',
name: user_name,
state: user_state,
- ghost: ghost,
last_activity_on: 1.minute.ago,
user_type: user_type,
confirmed_at: 1.day.ago)
end
- let(:migration_bot) { User.migration_bot }
+ let!(:migration_bot) do
+ users.create(id: 100,
+ email: "noreply+gitlab-migration-bot%s@#{Settings.gitlab.host}",
+ user_type: HasUserType::USER_TYPES[:migration_bot],
+ name: 'GitLab Migration Bot',
+ projects_limit: 10,
+ username: 'bot')
+ end
+
let!(:snippet_with_repo) { snippets.create(id: 1, type: 'PersonalSnippet', author_id: user.id, file_name: file_name, content: content) }
let!(:snippet_with_empty_repo) { snippets.create(id: 2, type: 'PersonalSnippet', author_id: user.id, file_name: file_name, content: content) }
let!(:snippet_without_repo) { snippets.create(id: 3, type: 'PersonalSnippet', author_id: user.id, file_name: file_name, content: content) }
@@ -113,8 +119,7 @@ describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migration, s
end
context 'when user is a ghost' do
- let(:ghost) { true }
- let(:user_type) { 'ghost' }
+ let(:user_type) { HasUserType::USER_TYPES[:ghost] }
it_behaves_like 'migration_bot user commits files'
end
@@ -255,7 +260,6 @@ describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migration, s
username: 'test2',
name: 'Test2',
state: user_state,
- ghost: ghost,
last_activity_on: 1.minute.ago,
user_type: user_type,
confirmed_at: 1.day.ago)
diff --git a/spec/lib/gitlab/background_migration/cleanup_concurrent_schema_change_spec.rb b/spec/lib/gitlab/background_migration/cleanup_concurrent_schema_change_spec.rb
new file mode 100644
index 00000000000..2931b5e6dd3
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/cleanup_concurrent_schema_change_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::CleanupConcurrentSchemaChange do
+ describe '#perform' do
+ it 'new column does not exist' do
+ expect(subject).to receive(:column_exists?).with(:issues, :closed_at_timestamp).and_return(false)
+ expect(subject).not_to receive(:column_exists?).with(:issues, :closed_at)
+ expect(subject).not_to receive(:define_model_for)
+
+ expect(subject.perform(:issues, :closed_at, :closed_at_timestamp)).to be_nil
+ end
+
+ it 'old column does not exist' do
+ expect(subject).to receive(:column_exists?).with(:issues, :closed_at_timestamp).and_return(true)
+ expect(subject).to receive(:column_exists?).with(:issues, :closed_at).and_return(false)
+ expect(subject).not_to receive(:define_model_for)
+
+ expect(subject.perform(:issues, :closed_at, :closed_at_timestamp)).to be_nil
+ end
+
+ it 'has both old and new columns' do
+ expect(subject).to receive(:column_exists?).twice.and_return(true)
+
+ expect { subject.perform('issues', :closed_at, :created_at) }.to raise_error(NotImplementedError)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/digest_column_spec.rb b/spec/lib/gitlab/background_migration/digest_column_spec.rb
deleted file mode 100644
index 0c76ebe9c66..00000000000
--- a/spec/lib/gitlab/background_migration/digest_column_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::BackgroundMigration::DigestColumn, schema: 20180913142237 do
- let(:personal_access_tokens) { table(:personal_access_tokens) }
- let(:users) { table(:users) }
-
- subject { described_class.new }
-
- describe '#perform' do
- context 'token is not yet hashed' do
- before do
- users.create(id: 1, email: 'user@example.com', projects_limit: 10)
- personal_access_tokens.create!(id: 1, user_id: 1, name: 'pat-01', token: 'token-01')
- end
-
- it 'saves token digest' do
- expect { subject.perform(PersonalAccessToken, :token, :token_digest, 1, 2) }.to(
- change { PersonalAccessToken.find(1).token_digest }.from(nil).to(Gitlab::CryptoHelper.sha256('token-01')))
- end
-
- it 'erases token' do
- expect { subject.perform(PersonalAccessToken, :token, :token_digest, 1, 2) }.to(
- change { PersonalAccessToken.find(1).read_attribute(:token) }.from('token-01').to(nil))
- end
- end
-
- context 'token is already hashed' do
- before do
- users.create(id: 1, email: 'user@example.com', projects_limit: 10)
- personal_access_tokens.create!(id: 1, user_id: 1, name: 'pat-01', token_digest: 'token-digest-01')
- end
-
- it 'does not change existing token digest' do
- expect { subject.perform(PersonalAccessToken, :token, :token_digest, 1, 2) }.not_to(
- change { PersonalAccessToken.find(1).token_digest })
- end
-
- it 'leaves token empty' do
- expect { subject.perform(PersonalAccessToken, :token, :token_digest, 1, 2) }.not_to(
- change { PersonalAccessToken.find(1).read_attribute(:token) }.from(nil))
- 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
deleted file mode 100644
index 6d3ccde7df2..00000000000
--- a/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb
+++ /dev/null
@@ -1,96 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::BackgroundMigration::EncryptColumns, schema: 20180910115836 do
- let(:model) { Gitlab::BackgroundMigration::Models::EncryptColumns::WebHook }
- let(:web_hooks) { table(:web_hooks) }
-
- let(:plaintext_attrs) do
- {
- 'encrypted_token' => nil,
- 'encrypted_url' => nil,
- 'token' => 'secret',
- 'url' => 'http://example.com?access_token=secret'
- }
- end
-
- let(:encrypted_attrs) do
- {
- 'encrypted_token' => be_present,
- 'encrypted_url' => be_present,
- 'token' => nil,
- 'url' => nil
- }
- end
-
- describe '#perform' do
- it 'encrypts columns for the specified range' do
- hooks = web_hooks.create([plaintext_attrs] * 5).sort_by(&:id)
-
- # Encrypt all but the first and last rows
- subject.perform(model, [:token, :url], hooks[1].id, hooks[3].id)
-
- hooks = web_hooks.where(id: hooks.map(&:id)).order(:id)
-
- aggregate_failures do
- expect(hooks[0]).to have_attributes(plaintext_attrs)
- expect(hooks[1]).to have_attributes(encrypted_attrs)
- expect(hooks[2]).to have_attributes(encrypted_attrs)
- expect(hooks[3]).to have_attributes(encrypted_attrs)
- expect(hooks[4]).to have_attributes(plaintext_attrs)
- end
- end
-
- it 'acquires an exclusive lock for the update' do
- relation = double('relation', each: nil)
-
- expect(model).to receive(:where) { relation }
- expect(relation).to receive(:lock) { relation }
-
- subject.perform(model, [:token, :url], 1, 1)
- end
-
- it 'skips already-encrypted columns' do
- values = {
- 'encrypted_token' => 'known encrypted token',
- 'encrypted_url' => 'known encrypted url',
- 'token' => 'token',
- 'url' => 'url'
- }
-
- hook = web_hooks.create(values)
-
- subject.perform(model, [:token, :url], hook.id, hook.id)
-
- hook.reload
-
- 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/background_migration/encrypt_runners_tokens_spec.rb b/spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb
deleted file mode 100644
index 89262788d9b..00000000000
--- a/spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::BackgroundMigration::EncryptRunnersTokens, schema: 20181121111200 do
- let(:settings) { table(:application_settings) }
- let(:namespaces) { table(:namespaces) }
- let(:projects) { table(:projects) }
- let(:runners) { table(:ci_runners) }
-
- context 'when migrating application settings' do
- before do
- settings.create!(id: 1, runners_registration_token: 'plain-text-token1')
- end
-
- it 'migrates runners registration tokens' do
- migrate!(:settings, 1, 1)
-
- encrypted_token = settings.first.runners_registration_token_encrypted
- decrypted_token = ::Gitlab::CryptoHelper.aes256_gcm_decrypt(encrypted_token)
-
- expect(decrypted_token).to eq 'plain-text-token1'
- expect(settings.first.runners_registration_token).to eq 'plain-text-token1'
- end
- end
-
- context 'when migrating namespaces' do
- before do
- namespaces.create!(id: 11, name: 'gitlab', path: 'gitlab-org', runners_token: 'my-token1')
- namespaces.create!(id: 12, name: 'gitlab', path: 'gitlab-org', runners_token: 'my-token2')
- namespaces.create!(id: 22, name: 'gitlab', path: 'gitlab-org', runners_token: 'my-token3')
- end
-
- it 'migrates runners registration tokens' do
- migrate!(:namespace, 11, 22)
-
- expect(namespaces.all.reload).to all(
- have_attributes(runners_token: be_a(String), runners_token_encrypted: be_a(String))
- )
- end
- end
-
- context 'when migrating projects' do
- before do
- namespaces.create!(id: 11, name: 'gitlab', path: 'gitlab-org')
- projects.create!(id: 111, namespace_id: 11, name: 'gitlab', path: 'gitlab-ce', runners_token: 'my-token1')
- projects.create!(id: 114, namespace_id: 11, name: 'gitlab', path: 'gitlab-ce', runners_token: 'my-token2')
- projects.create!(id: 116, namespace_id: 11, name: 'gitlab', path: 'gitlab-ce', runners_token: 'my-token3')
- end
-
- it 'migrates runners registration tokens' do
- migrate!(:project, 111, 116)
-
- expect(projects.all.reload).to all(
- have_attributes(runners_token: be_a(String), runners_token_encrypted: be_a(String))
- )
- end
- end
-
- context 'when migrating runners' do
- before do
- runners.create!(id: 201, runner_type: 1, token: 'plain-text-token1')
- runners.create!(id: 202, runner_type: 1, token: 'plain-text-token2')
- runners.create!(id: 203, runner_type: 1, token: 'plain-text-token3')
- end
-
- it 'migrates runners communication tokens' do
- migrate!(:runner, 201, 203)
-
- expect(runners.all.reload).to all(
- have_attributes(token: be_a(String), token_encrypted: be_a(String))
- )
- end
- end
-
- def migrate!(model, from, to)
- subject.perform(model, from, to)
- end
-end
diff --git a/spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb b/spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb
index cc4ce023f04..8e3ace083fc 100644
--- a/spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::FixCrossProjectLabelLinks, schema: 20180702120647 do
+RSpec.describe Gitlab::BackgroundMigration::FixCrossProjectLabelLinks do
let(:namespaces_table) { table(:namespaces) }
let(:projects_table) { table(:projects) }
let(:issues_table) { table(:issues) }
diff --git a/spec/lib/gitlab/background_migration/fix_projects_without_project_feature_spec.rb b/spec/lib/gitlab/background_migration/fix_projects_without_project_feature_spec.rb
index 056ddd7adf9..e2175c41513 100644
--- a/spec/lib/gitlab/background_migration/fix_projects_without_project_feature_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_projects_without_project_feature_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::FixProjectsWithoutProjectFeature, schema: 2020_01_27_111840 do
+RSpec.describe Gitlab::BackgroundMigration::FixProjectsWithoutProjectFeature, schema: 2020_01_27_111840 do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:project_features) { table(:project_features) }
diff --git a/spec/lib/gitlab/background_migration/fix_projects_without_prometheus_service_spec.rb b/spec/lib/gitlab/background_migration/fix_projects_without_prometheus_service_spec.rb
index 3c3e37df200..fe2b206ea74 100644
--- a/spec/lib/gitlab/background_migration/fix_projects_without_prometheus_service_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_projects_without_prometheus_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::FixProjectsWithoutPrometheusService, :migration, schema: 2020_02_20_115023 do
+RSpec.describe Gitlab::BackgroundMigration::FixProjectsWithoutPrometheusService, :migration, schema: 2020_02_20_115023 do
def service_params_for(project_id, params = {})
{
project_id: project_id,
diff --git a/spec/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids_spec.rb b/spec/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids_spec.rb
index 141a0af6c29..452fc962c7b 100644
--- a/spec/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::FixPromotedEpicsDiscussionIds, schema: 20190715193142 do
+RSpec.describe Gitlab::BackgroundMigration::FixPromotedEpicsDiscussionIds, schema: 20190715193142 do
let(:namespaces) { table(:namespaces) }
let(:users) { table(:users) }
let(:epics) { table(:epics) }
diff --git a/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb b/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb
index 52760cdd115..7768411828c 100644
--- a/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::FixUserNamespaceNames, schema: 20190620112608 do
+RSpec.describe Gitlab::BackgroundMigration::FixUserNamespaceNames, schema: 20190620112608 do
let(:namespaces) { table(:namespaces) }
let(:users) { table(:users) }
let(:user) { users.create(name: "The user's full name", projects_limit: 10, username: 'not-null', email: '1') }
diff --git a/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb b/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb
index 0fb7eea2bd7..4c04043ebd0 100644
--- a/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::FixUserProjectRouteNames, schema: 20190620112608 do
+RSpec.describe Gitlab::BackgroundMigration::FixUserProjectRouteNames, schema: 20190620112608 do
let(:namespaces) { table(:namespaces) }
let(:users) { table(:users) }
let(:routes) { table(:routes) }
diff --git a/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb
index 850ef48d44a..bf793e7c537 100644
--- a/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb
+++ b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
# rubocop: disable RSpec/FactoriesInMigrationSpecs
-describe Gitlab::BackgroundMigration::LegacyUploadMover do
+RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover do
let(:test_dir) { FileUploader.options['storage_path'] }
let(:filename) { 'image.png' }
diff --git a/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb b/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb
index 85187d039c1..66a1787b2cb 100644
--- a/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb
+++ b/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
# rubocop: disable RSpec/FactoriesInMigrationSpecs
-describe Gitlab::BackgroundMigration::LegacyUploadsMigrator do
+RSpec.describe Gitlab::BackgroundMigration::LegacyUploadsMigrator do
let(:test_dir) { FileUploader.options['storage_path'] }
let!(:hashed_project) { create(:project) }
diff --git a/spec/lib/gitlab/background_migration/link_lfs_objects_projects_spec.rb b/spec/lib/gitlab/background_migration/link_lfs_objects_projects_spec.rb
index 5700cac2e0f..dda4f5a3a36 100644
--- a/spec/lib/gitlab/background_migration/link_lfs_objects_projects_spec.rb
+++ b/spec/lib/gitlab/background_migration/link_lfs_objects_projects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::LinkLfsObjectsProjects, :migration, schema: 2020_03_10_075115 do
+RSpec.describe Gitlab::BackgroundMigration::LinkLfsObjectsProjects, :migration, schema: 2020_03_10_075115 do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:fork_networks) { table(:fork_networks) }
diff --git a/spec/lib/gitlab/background_migration/mailers/unconfirm_mailer_spec.rb b/spec/lib/gitlab/background_migration/mailers/unconfirm_mailer_spec.rb
new file mode 100644
index 00000000000..f430009989b
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/mailers/unconfirm_mailer_spec.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::Mailers::UnconfirmMailer do
+ let(:user) { User.new(id: 1111) }
+ let(:subject) { described_class.unconfirm_notification_email(user) }
+
+ it 'contains abuse report url' do
+ expect(subject.body.encoded).to include(Rails.application.routes.url_helpers.new_abuse_report_url(user_id: user.id))
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/merge_request_assignees_migration_progress_check_spec.rb b/spec/lib/gitlab/background_migration/merge_request_assignees_migration_progress_check_spec.rb
index 9dd97b58014..a3840e3a22e 100644
--- a/spec/lib/gitlab/background_migration/merge_request_assignees_migration_progress_check_spec.rb
+++ b/spec/lib/gitlab/background_migration/merge_request_assignees_migration_progress_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MergeRequestAssigneesMigrationProgressCheck do
+RSpec.describe Gitlab::BackgroundMigration::MergeRequestAssigneesMigrationProgressCheck do
context 'rescheduling' do
context 'when there are ongoing and no dead jobs' do
it 'reschedules check' do
diff --git a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
index adf358f5320..65d45ec694f 100644
--- a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
@@ -2,7 +2,8 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigrateBuildStage, schema: 20180212101928 do
+RSpec.describe Gitlab::BackgroundMigration::MigrateBuildStage do
+ let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:pipelines) { table(:ci_pipelines) }
let(:stages) { table(:ci_stages) }
@@ -22,7 +23,8 @@ describe Gitlab::BackgroundMigration::MigrateBuildStage, schema: 20180212101928
end
before do
- projects.create!(id: 123, name: 'gitlab', path: 'gitlab-ce')
+ namespace = namespaces.create!(name: 'gitlab-org', path: 'gitlab-org')
+ projects.create!(id: 123, name: 'gitlab', path: 'gitlab-ce', namespace_id: namespace.id)
pipelines.create!(id: 1, project_id: 123, ref: 'master', sha: 'adf43c3a')
jobs.create!(id: 1, commit_id: 1, project_id: 123,
@@ -53,7 +55,7 @@ describe Gitlab::BackgroundMigration::MigrateBuildStage, schema: 20180212101928
statuses[:pending]]
end
- it 'recovers from unique constraint violation only twice', :quarantine do
+ it 'recovers from unique constraint violation only twice', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/28128' do
allow(described_class::Migratable::Stage)
.to receive(:find_by).and_return(nil)
diff --git a/spec/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys_spec.rb b/spec/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys_spec.rb
index 79a8cd926a7..c58b2d609e9 100644
--- a/spec/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigrateFingerprintSha256WithinKeys, schema: 20200106071113 do
+RSpec.describe Gitlab::BackgroundMigration::MigrateFingerprintSha256WithinKeys, schema: 20200106071113 do
subject(:fingerprint_migrator) { described_class.new }
let(:key_table) { table(:keys) }
diff --git a/spec/lib/gitlab/background_migration/migrate_issue_trackers_sensitive_data_spec.rb b/spec/lib/gitlab/background_migration/migrate_issue_trackers_sensitive_data_spec.rb
index 4411dca3fd9..d53f79c61c1 100644
--- a/spec/lib/gitlab/background_migration/migrate_issue_trackers_sensitive_data_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_issue_trackers_sensitive_data_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigrateIssueTrackersSensitiveData, schema: 20200130145430 do
+RSpec.describe Gitlab::BackgroundMigration::MigrateIssueTrackersSensitiveData, schema: 20200130145430 do
let(:services) { table(:services) }
before do
diff --git a/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb b/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb
index 5f2a27acd9b..08f2b2a043e 100644
--- a/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigrateLegacyArtifacts, schema: 20180816161409 do
+RSpec.describe Gitlab::BackgroundMigration::MigrateLegacyArtifacts do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:pipelines) { table(:ci_pipelines) }
diff --git a/spec/lib/gitlab/background_migration/migrate_null_private_profile_to_false_spec.rb b/spec/lib/gitlab/background_migration/migrate_null_private_profile_to_false_spec.rb
index ff88d2a5d00..6ff1157cb86 100644
--- a/spec/lib/gitlab/background_migration/migrate_null_private_profile_to_false_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_null_private_profile_to_false_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigrateNullPrivateProfileToFalse, schema: 20190620105427 do
+RSpec.describe Gitlab::BackgroundMigration::MigrateNullPrivateProfileToFalse, schema: 20190620105427 do
let(:users) { table(:users) }
it 'correctly migrates nil private_profile to false' do
diff --git a/spec/lib/gitlab/background_migration/migrate_pages_metadata_spec.rb b/spec/lib/gitlab/background_migration/migrate_pages_metadata_spec.rb
index 10a1d4ee1b9..906a6a747c9 100644
--- a/spec/lib/gitlab/background_migration/migrate_pages_metadata_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_pages_metadata_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigratePagesMetadata, schema: 20190919040324 do
+RSpec.describe Gitlab::BackgroundMigration::MigratePagesMetadata, schema: 20190919040324 do
let(:projects) { table(:projects) }
subject(:migrate_pages_metadata) { described_class.new }
diff --git a/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb b/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb
index 437be125cf0..81874ff7982 100644
--- a/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb
@@ -2,35 +2,33 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigrateStageIndex, schema: 20180420080616 do
+RSpec.describe Gitlab::BackgroundMigration::MigrateStageIndex do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:pipelines) { table(:ci_pipelines) }
let(:stages) { table(:ci_stages) }
let(:jobs) { table(:ci_builds) }
+ let(:namespace) { namespaces.create(name: 'gitlab-org', path: 'gitlab-org') }
+ let(:project) { projects.create!(namespace_id: namespace.id, name: 'gitlab', path: 'gitlab') }
+ let(:pipeline) { pipelines.create!(project_id: project.id, ref: 'master', sha: 'adf43c3a') }
+ let(:stage1) { stages.create(project_id: project.id, pipeline_id: pipeline.id, name: 'build') }
+ let(:stage2) { stages.create(project_id: project.id, pipeline_id: pipeline.id, name: 'test') }
before do
- namespaces.create(id: 10, name: 'gitlab-org', path: 'gitlab-org')
- projects.create!(id: 11, namespace_id: 10, name: 'gitlab', path: 'gitlab')
- pipelines.create!(id: 12, project_id: 11, ref: 'master', sha: 'adf43c3a')
-
- stages.create(id: 100, project_id: 11, pipeline_id: 12, name: 'build')
- stages.create(id: 101, project_id: 11, pipeline_id: 12, name: 'test')
-
- jobs.create!(id: 121, commit_id: 12, project_id: 11,
- stage_idx: 2, stage_id: 100)
- jobs.create!(id: 122, commit_id: 12, project_id: 11,
- stage_idx: 2, stage_id: 100)
- jobs.create!(id: 123, commit_id: 12, project_id: 11,
- stage_idx: 10, stage_id: 100)
- jobs.create!(id: 124, commit_id: 12, project_id: 11,
- stage_idx: 3, stage_id: 101)
+ jobs.create!(commit_id: pipeline.id, project_id: project.id,
+ stage_idx: 2, stage_id: stage1.id)
+ jobs.create!(commit_id: pipeline.id, project_id: project.id,
+ stage_idx: 2, stage_id: stage1.id)
+ jobs.create!(commit_id: pipeline.id, project_id: project.id,
+ stage_idx: 10, stage_id: stage1.id)
+ jobs.create!(commit_id: pipeline.id, project_id: project.id,
+ stage_idx: 3, stage_id: stage2.id)
end
it 'correctly migrates stages indices' do
expect(stages.all.pluck(:position)).to all(be_nil)
- described_class.new.perform(100, 101)
+ described_class.new.perform(stage1.id, stage2.id)
expect(stages.all.order(:id).pluck(:position)).to eq [2, 3]
end
diff --git a/spec/lib/gitlab/background_migration/migrate_users_bio_to_user_details_spec.rb b/spec/lib/gitlab/background_migration/migrate_users_bio_to_user_details_spec.rb
index 8603eb73bd5..db3cbe7ccdc 100644
--- a/spec/lib/gitlab/background_migration/migrate_users_bio_to_user_details_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_users_bio_to_user_details_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigrateUsersBioToUserDetails, :migration, schema: 20200323074147 do
+RSpec.describe Gitlab::BackgroundMigration::MigrateUsersBioToUserDetails, :migration, schema: 20200323074147 do
let(:users) { table(:users) }
let(:user_details) do
diff --git a/spec/lib/gitlab/background_migration/populate_canonical_emails_spec.rb b/spec/lib/gitlab/background_migration/populate_canonical_emails_spec.rb
index 37ddb8b569d..ee0024e8526 100644
--- a/spec/lib/gitlab/background_migration/populate_canonical_emails_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_canonical_emails_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::PopulateCanonicalEmails, :migration, schema: 20200312053852 do
+RSpec.describe Gitlab::BackgroundMigration::PopulateCanonicalEmails, :migration, schema: 20200312053852 do
let(:migration) { described_class.new }
let_it_be(:users_table) { table(:users) }
diff --git a/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
index d445858b8e8..73faca54b52 100644
--- a/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, schema: 20181022173835 do
+RSpec.describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable do
include MigrationHelpers::ClusterHelpers
let(:migration) { described_class.new }
diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
index e65b3549de0..1e5773ee16b 100644
--- a/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::PopulateMergeRequestAssigneesTable, schema: 20190315191339 do
+RSpec.describe Gitlab::BackgroundMigration::PopulateMergeRequestAssigneesTable, schema: 20190315191339 do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:users) { table(:users) }
diff --git a/spec/lib/gitlab/background_migration/populate_project_snippet_statistics_spec.rb b/spec/lib/gitlab/background_migration/populate_project_snippet_statistics_spec.rb
new file mode 100644
index 00000000000..897f5e81372
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/populate_project_snippet_statistics_spec.rb
@@ -0,0 +1,224 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::PopulateProjectSnippetStatistics do
+ let(:file_name) { 'file_name.rb' }
+ let(:content) { 'content' }
+ let(:snippets) { table(:snippets) }
+ let(:snippet_repositories) { table(:snippet_repositories) }
+ let(:users) { table(:users) }
+ let(:namespaces) { table(:namespaces) }
+ let(:snippet_statistics) { table(:snippet_statistics) }
+ let(:project_statistics) { table(:project_statistics) }
+ let(:projects) { table(:projects) }
+ let(:namespace_statistics) { table(:namespace_root_storage_statistics) }
+ let(:routes) { table(:routes) }
+ let(:repo_size) { 123456 }
+ let(:expected_repo_size) { repo_size.megabytes }
+
+ let(:user) { users.create!(id: 1, email: 'test@example.com', projects_limit: 100, username: 'test') }
+ let(:group) { namespaces.create!(id: 10, type: 'Group', name: 'group1', path: 'group1') }
+ let(:user_namespace) { namespaces.create!(id: 20, name: 'user', path: 'user', owner_id: user.id) }
+
+ let(:project1) { create_project(1, 'test', group) }
+ let(:project2) { create_project(2, 'test1', user_namespace) }
+ let(:project3) { create_project(3, 'test2', group) }
+
+ let!(:project_stats1) { create_project_statistics(project1) }
+ let!(:project_stats2) { create_project_statistics(project2) }
+ let!(:project_stats3) { create_project_statistics(project3) }
+
+ let(:ids) { snippets.pluck(:id) }
+ let(:migration) { described_class.new }
+
+ subject do
+ migration.perform(ids)
+
+ project_stats1.reload if project_stats1.persisted?
+ project_stats2.reload if project_stats2.persisted?
+ project_stats3.reload if project_stats3.persisted?
+ end
+
+ before do
+ allow_any_instance_of(Repository).to receive(:size).and_return(repo_size)
+ end
+
+ after do
+ snippets.all.each { |s| raw_repository(s).remove }
+ end
+
+ context 'with existing user and group snippets' do
+ let!(:snippet1) { create_snippet(1, project1) }
+ let!(:snippet2) { create_snippet(2, project1) }
+ let!(:snippet3) { create_snippet(3, project2) }
+ let!(:snippet4) { create_snippet(4, project2) }
+ let!(:snippet5) { create_snippet(5, project3) }
+
+ before do
+ create_snippet_statistics(2, 0)
+ create_snippet_statistics(4, 123)
+ end
+
+ it 'creates/updates all snippet_statistics' do
+ expect(snippet_statistics.count).to eq 2
+
+ subject
+
+ expect(snippet_statistics.count).to eq 5
+
+ snippet_statistics.all.each do |stat|
+ expect(stat.repository_size).to eq expected_repo_size
+ end
+ end
+
+ it 'updates associated snippet project statistics' do
+ expect(project_stats1.snippets_size).to be_nil
+ expect(project_stats2.snippets_size).to be_nil
+
+ subject
+
+ snippets_size = snippet_statistics.where(snippet_id: [snippet1.id, snippet2.id]).sum(:repository_size)
+ expect(project_stats1.snippets_size).to eq snippets_size
+
+ snippets_size = snippet_statistics.where(snippet_id: [snippet3.id, snippet4.id]).sum(:repository_size)
+ expect(project_stats2.snippets_size).to eq snippets_size
+
+ snippets_size = snippet_statistics.where(snippet_id: snippet5.id).sum(:repository_size)
+ expect(project_stats3.snippets_size).to eq snippets_size
+ end
+
+ it 'forces the project statistics refresh' do
+ expect(migration).to receive(:update_project_statistics).exactly(3).times
+
+ subject
+ end
+
+ it 'creates/updates the associated namespace statistics' do
+ expect(migration).to receive(:update_namespace_statistics).twice.and_call_original
+
+ subject
+
+ expect(namespace_statistics.find_by(namespace_id: group.id).snippets_size).to eq project_stats1.snippets_size + project_stats3.snippets_size
+ expect(namespace_statistics.find_by(namespace_id: user_namespace.id).snippets_size).to eq project_stats2.snippets_size
+ end
+
+ context 'when the project statistics does not exists' do
+ it 'does not raise any error' do
+ project_stats3.delete
+
+ subject
+
+ expect(namespace_statistics.find_by(namespace_id: group.id).snippets_size).to eq project_stats1.snippets_size
+ expect(namespace_statistics.find_by(namespace_id: user_namespace.id).snippets_size).to eq project_stats2.snippets_size
+ end
+ end
+
+ context 'when an error is raised when updating a project statistics' do
+ it 'logs the error and continue execution' do
+ expect(migration).to receive(:update_project_statistics).with(Project.find(project1.id)).and_raise('Error')
+ expect(migration).to receive(:update_project_statistics).with(Project.find(project2.id)).and_call_original
+ expect(migration).to receive(:update_project_statistics).with(Project.find(project3.id)).and_call_original
+
+ expect_next_instance_of(Gitlab::BackgroundMigration::Logger) do |instance|
+ expect(instance).to receive(:error).with(message: /Error updating statistics for project #{project1.id}/).once
+ end
+
+ subject
+
+ expect(project_stats2.snippets_size).not_to be_nil
+ expect(project_stats3.snippets_size).not_to be_nil
+ end
+ end
+
+ context 'when an error is raised when updating a namespace statistics' do
+ it 'logs the error and continue execution' do
+ expect(migration).to receive(:update_namespace_statistics).with(Group.find(group.id)).and_raise('Error')
+ expect(migration).to receive(:update_namespace_statistics).with(Namespace.find(user_namespace.id)).and_call_original
+
+ expect_next_instance_of(Gitlab::BackgroundMigration::Logger) do |instance|
+ expect(instance).to receive(:error).with(message: /Error updating statistics for namespace/).once
+ end
+
+ subject
+
+ expect(namespace_statistics.find_by(namespace_id: user_namespace.id).snippets_size).to eq project_stats2.snippets_size
+ end
+ end
+ end
+
+ context 'when project snippet is in a subgroup' do
+ let(:subgroup) { namespaces.create!(id: 30, type: 'Group', name: 'subgroup', path: 'subgroup', parent_id: group.id) }
+ let(:project1) { create_project(1, 'test', subgroup, "#{group.path}/#{subgroup.path}/test") }
+ let!(:snippet1) { create_snippet(1, project1) }
+
+ it 'updates the root namespace statistics' do
+ subject
+
+ expect(snippet_statistics.count).to eq 1
+ expect(project_stats1.snippets_size).to eq snippet_statistics.first.repository_size
+ expect(namespace_statistics.find_by(namespace_id: subgroup.id)).to be_nil
+ expect(namespace_statistics.find_by(namespace_id: group.id).snippets_size).to eq project_stats1.snippets_size
+ end
+ end
+
+ context 'when a snippet repository is empty' do
+ let!(:snippet1) { create_snippet(1, project1, with_repo: false) }
+ let!(:snippet2) { create_snippet(2, project1) }
+
+ it 'logs error and continues execution' do
+ expect_next_instance_of(Gitlab::BackgroundMigration::Logger) do |instance|
+ expect(instance).to receive(:error).with(message: /Invalid snippet repository/).once
+ end
+
+ subject
+
+ expect(snippet_statistics.find_by(snippet_id: snippet1.id)).to be_nil
+ expect(project_stats1.snippets_size).to eq snippet_statistics.find(snippet2.id).repository_size
+ end
+ end
+
+ def create_snippet(id, project, with_repo: true)
+ snippets.create!(id: id, type: 'ProjectSnippet', project_id: project.id, author_id: user.id, file_name: file_name, content: content).tap do |snippet|
+ if with_repo
+ allow(snippet).to receive(:disk_path).and_return(disk_path(snippet))
+
+ TestEnv.copy_repo(snippet,
+ bare_repo: TestEnv.factory_repo_path_bare,
+ refs: TestEnv::BRANCH_SHA)
+
+ raw_repository(snippet).create_repository
+ end
+ end
+ end
+
+ def create_project(id, name, namespace, path = nil)
+ projects.create!(id: id, name: name, path: name.downcase.gsub(/\s/, '_'), namespace_id: namespace.id).tap do |project|
+ path ||= "#{namespace.path}/#{project.path}"
+ routes.create!(id: id, source_type: 'Project', source_id: project.id, path: path)
+ end
+ end
+
+ def create_snippet_statistics(snippet_id, repository_size = 0)
+ snippet_statistics.create!(snippet_id: snippet_id, repository_size: repository_size)
+ end
+
+ def create_project_statistics(project, snippets_size = nil)
+ project_statistics.create!(id: project.id, project_id: project.id, namespace_id: project.namespace_id, snippets_size: snippets_size)
+ end
+
+ def raw_repository(snippet)
+ Gitlab::Git::Repository.new('default',
+ "#{disk_path(snippet)}.git",
+ Gitlab::GlRepository::SNIPPET.identifier_for_container(snippet),
+ "@snippets/#{snippet.id}")
+ end
+
+ def hashed_repository(snippet)
+ Storage::Hashed.new(snippet, prefix: '@snippets')
+ end
+
+ def disk_path(snippet)
+ hashed_repository(snippet).disk_path
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb b/spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb
index 0250ebd7759..6a25e8e2784 100644
--- a/spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb
@@ -2,8 +2,7 @@
require 'spec_helper'
-# Rollback DB to 10.5 (later than this was originally written for) because it still needs to work.
-describe Gitlab::BackgroundMigration::PopulateUntrackedUploadsDependencies::UntrackedFile, schema: 20180208183958 do
+RSpec.describe Gitlab::BackgroundMigration::PopulateUntrackedUploadsDependencies::UntrackedFile do
include MigrationsHelpers::TrackUntrackedUploadsHelpers
let!(:appearances) { table(:appearances) }
diff --git a/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb b/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
index 44cec112bfd..787cc54e79a 100644
--- a/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
@@ -2,8 +2,7 @@
require 'spec_helper'
-# Rollback DB to 10.5 (later than this was originally written for) because it still needs to work.
-describe Gitlab::BackgroundMigration::PopulateUntrackedUploads, schema: 20180208183958 do
+RSpec.describe Gitlab::BackgroundMigration::PopulateUntrackedUploads do
include MigrationsHelpers::TrackUntrackedUploadsHelpers
subject { described_class.new }
diff --git a/spec/lib/gitlab/background_migration/populate_user_highest_roles_table_spec.rb b/spec/lib/gitlab/background_migration/populate_user_highest_roles_table_spec.rb
index be661d5b83e..f0b0f77280e 100644
--- a/spec/lib/gitlab/background_migration/populate_user_highest_roles_table_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_user_highest_roles_table_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::PopulateUserHighestRolesTable, schema: 20200311130802 do
+RSpec.describe Gitlab::BackgroundMigration::PopulateUserHighestRolesTable, schema: 20200311130802 do
let(:members) { table(:members) }
let(:users) { table(:users) }
let(:user_highest_roles) { table(:user_highest_roles) }
diff --git a/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb b/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb
index 9daf35d0311..9b01407dc8b 100644
--- a/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb
+++ b/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
# Rollback DB to 10.5 (later than this was originally written for) because it still needs to work.
-describe Gitlab::BackgroundMigration::PrepareUntrackedUploads, schema: 20180208183958 do
+RSpec.describe Gitlab::BackgroundMigration::PrepareUntrackedUploads do
include MigrationsHelpers::TrackUntrackedUploadsHelpers
let!(:untracked_files_for_uploads) { table(:untracked_files_for_uploads) }
diff --git a/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb b/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb
index ba87312e2bf..33e1f31d1f1 100644
--- a/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb
+++ b/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::RecalculateProjectAuthorizations, schema: 20200204113223 do
+RSpec.describe Gitlab::BackgroundMigration::RecalculateProjectAuthorizations, schema: 20200204113223 do
let(:users_table) { table(:users) }
let(:namespaces_table) { table(:namespaces) }
let(:projects_table) { table(:projects) }
diff --git a/spec/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id_spec.rb b/spec/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id_spec.rb
index edb46efad7c..c1ba1607b89 100644
--- a/spec/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id_spec.rb
+++ b/spec/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::RecalculateProjectAuthorizationsWithMinMaxUserId, schema: 20200204113224 do
+RSpec.describe Gitlab::BackgroundMigration::RecalculateProjectAuthorizationsWithMinMaxUserId, schema: 20200204113224 do
let(:users_table) { table(:users) }
let(:min) { 1 }
let(:max) { 5 }
diff --git a/spec/lib/gitlab/background_migration/remove_restricted_todos_spec.rb b/spec/lib/gitlab/background_migration/remove_restricted_todos_spec.rb
index 3de24f577ab..7019d5d4212 100644
--- a/spec/lib/gitlab/background_migration/remove_restricted_todos_spec.rb
+++ b/spec/lib/gitlab/background_migration/remove_restricted_todos_spec.rb
@@ -2,7 +2,8 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::RemoveRestrictedTodos, schema: 20180704204006 do
+RSpec.describe Gitlab::BackgroundMigration::RemoveRestrictedTodos do
+ let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:users) { table(:users) }
let(:todos) { table(:todos) }
@@ -18,8 +19,9 @@ describe Gitlab::BackgroundMigration::RemoveRestrictedTodos, schema: 20180704204
users.create(id: 2, email: 'reporter@example.com', projects_limit: 10)
users.create(id: 3, email: 'guest@example.com', projects_limit: 10)
- projects.create!(id: 1, name: 'project-1', path: 'project-1', visibility_level: 0, namespace_id: 1)
- projects.create!(id: 2, name: 'project-2', path: 'project-2', visibility_level: 0, namespace_id: 1)
+ namespace = namespaces.create(name: 'gitlab-org', path: 'gitlab-org')
+ projects.create!(id: 1, name: 'project-1', path: 'project-1', visibility_level: 0, namespace_id: namespace.id)
+ projects.create!(id: 2, name: 'project-2', path: 'project-2', visibility_level: 0, namespace_id: namespace.id)
issues.create(id: 1, project_id: 1)
issues.create(id: 2, project_id: 2)
@@ -92,7 +94,7 @@ describe Gitlab::BackgroundMigration::RemoveRestrictedTodos, schema: 20180704204
context 'when issues are restricted to project members' do
before do
- project_features.create(issues_access_level: 10, project_id: 2)
+ project_features.create(issues_access_level: 10, pages_access_level: 10, project_id: 2)
end
it 'removes non members issue todos' do
@@ -102,7 +104,7 @@ describe Gitlab::BackgroundMigration::RemoveRestrictedTodos, schema: 20180704204
context 'when merge requests are restricted to project members' do
before do
- project_features.create(merge_requests_access_level: 10, project_id: 2)
+ project_features.create(merge_requests_access_level: 10, pages_access_level: 10, project_id: 2)
end
it 'removes non members issue todos' do
@@ -112,7 +114,7 @@ describe Gitlab::BackgroundMigration::RemoveRestrictedTodos, schema: 20180704204
context 'when repository and merge requests are restricted to project members' do
before do
- project_features.create(repository_access_level: 10, merge_requests_access_level: 10, project_id: 2)
+ project_features.create(repository_access_level: 10, merge_requests_access_level: 10, pages_access_level: 10, project_id: 2)
end
it 'removes non members commit and merge requests todos' do
diff --git a/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb b/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb
index e057aea6bb3..43fc0fb3691 100644
--- a/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb
+++ b/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::ResetMergeStatus do
+RSpec.describe Gitlab::BackgroundMigration::ResetMergeStatus do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
diff --git a/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb b/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb
deleted file mode 100644
index 387e3343ede..00000000000
--- a/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20190527194900_schedule_calculate_wiki_sizes.rb')
-
-describe ScheduleCalculateWikiSizes do
- let(:migration_class) { Gitlab::BackgroundMigration::CalculateWikiSizes }
- let(:migration_name) { migration_class.to_s.demodulize }
-
- let(:namespaces) { table(:namespaces) }
- let(:projects) { table(:projects) }
- let(:project_statistics) { table(:project_statistics) }
-
- context 'when missing wiki sizes exist' do
- before do
- namespaces.create!(id: 1, name: 'wiki-migration', path: 'wiki-migration')
- projects.create!(id: 1, name: 'wiki-project-1', path: 'wiki-project-1', namespace_id: 1)
- projects.create!(id: 2, name: 'wiki-project-2', path: 'wiki-project-2', namespace_id: 1)
- projects.create!(id: 3, name: 'wiki-project-3', path: 'wiki-project-3', namespace_id: 1)
- project_statistics.create!(id: 1, project_id: 1, namespace_id: 1, wiki_size: 1000)
- project_statistics.create!(id: 2, project_id: 2, namespace_id: 1, wiki_size: nil)
- project_statistics.create!(id: 3, project_id: 3, namespace_id: 1, wiki_size: nil)
- end
-
- it 'schedules a background migration' do
- Sidekiq::Testing.fake! do
- Timecop.freeze do
- migrate!
-
- expect(migration_name).to be_scheduled_delayed_migration(5.minutes, 2, 3)
- expect(BackgroundMigrationWorker.jobs.size).to eq 1
- end
- end
- end
-
- it 'calculates missing wiki sizes', :sidekiq_might_not_need_inline do
- expect(project_statistics.find_by(id: 2).wiki_size).to be_nil
- expect(project_statistics.find_by(id: 3).wiki_size).to be_nil
-
- migrate!
-
- expect(project_statistics.find_by(id: 2).wiki_size).not_to be_nil
- expect(project_statistics.find_by(id: 3).wiki_size).not_to be_nil
- end
- end
-
- context 'when missing wiki sizes do not exist' do
- before do
- namespaces.create!(id: 1, name: 'wiki-migration', path: 'wiki-migration')
- projects.create!(id: 1, name: 'wiki-project-1', path: 'wiki-project-1', namespace_id: 1)
- project_statistics.create!(id: 1, project_id: 1, namespace_id: 1, wiki_size: 1000)
- end
-
- it 'does not schedule a background migration' do
- Sidekiq::Testing.fake! do
- Timecop.freeze do
- migrate!
-
- expect(BackgroundMigrationWorker.jobs.size).to eq 0
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb
index 5ce4a322e51..364edf3ed2a 100644
--- a/spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb
+++ b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnServices, schema: 20180122154930 do
+RSpec.describe Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnServices do
let(:services) { table(:services) }
describe '#perform' do
diff --git a/spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb
index 08f1f543f5d..28b06ac3ba3 100644
--- a/spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb
+++ b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnWebhooks, schema: 20180104131052 do
+RSpec.describe Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnWebhooks do
let(:web_hooks) { table(:web_hooks) }
describe '#perform' do
diff --git a/spec/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb b/spec/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb
index 70397ae1e30..6c0a1d3a5b0 100644
--- a/spec/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb
+++ b/spec/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::UpdateExistingSubgroupToMatchVisibilityLevelOfParent, schema: 2020_01_10_121314 do
+RSpec.describe Gitlab::BackgroundMigration::UpdateExistingSubgroupToMatchVisibilityLevelOfParent, schema: 2020_01_10_121314 do
include MigrationHelpers::NamespacesHelpers
context 'private visibility level' do
diff --git a/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb b/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb
index d4f52a11ce7..08a4bbe38ac 100644
--- a/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb
+++ b/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require './db/post_migrate/20200128134110_migrate_commit_notes_mentions_to_db'
require './db/post_migrate/20200211155539_migrate_merge_request_mentions_to_db'
-describe Gitlab::BackgroundMigration::UserMentions::CreateResourceUserMention, schema: 20200211155539 do
+RSpec.describe Gitlab::BackgroundMigration::UserMentions::CreateResourceUserMention, schema: 20200211155539 do
include MigrationsHelpers
context 'when migrating data' do
diff --git a/spec/lib/gitlab/background_migration/wrongfully_confirmed_email_unconfirmer_spec.rb b/spec/lib/gitlab/background_migration/wrongfully_confirmed_email_unconfirmer_spec.rb
new file mode 100644
index 00000000000..c9928edb8e8
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/wrongfully_confirmed_email_unconfirmer_spec.rb
@@ -0,0 +1,123 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::WrongfullyConfirmedEmailUnconfirmer, schema: 20200615111857 do
+ let(:users) { table(:users) }
+ let(:emails) { table(:emails) }
+ let(:confirmed_at_2_days_ago) { 2.days.ago }
+ let(:confirmed_at_3_days_ago) { 3.days.ago }
+ let(:one_year_ago) { 1.year.ago }
+
+ let!(:user_needs_migration_1) { users.create!(name: 'user1', email: 'test1@test.com', state: 'active', projects_limit: 1, confirmed_at: confirmed_at_2_days_ago, confirmation_sent_at: one_year_ago) }
+ let!(:user_needs_migration_2) { users.create!(name: 'user2', email: 'test2@test.com', unconfirmed_email: 'unconfirmed@test.com', state: 'active', projects_limit: 1, confirmed_at: confirmed_at_3_days_ago, confirmation_sent_at: one_year_ago) }
+ let!(:user_does_not_need_migration) { users.create!(name: 'user3', email: 'test3@test.com', state: 'active', projects_limit: 1) }
+ let!(:inactive_user) { users.create!(name: 'user4', email: 'test4@test.com', state: 'blocked', projects_limit: 1, confirmed_at: confirmed_at_3_days_ago, confirmation_sent_at: one_year_ago) }
+ let!(:alert_bot_user) { users.create!(name: 'user5', email: 'test5@test.com', state: 'active', user_type: 2, projects_limit: 1, confirmed_at: confirmed_at_3_days_ago, confirmation_sent_at: one_year_ago) }
+
+ let!(:bad_email_1) { emails.create!(user_id: user_needs_migration_1.id, email: 'other1@test.com', confirmed_at: confirmed_at_2_days_ago, confirmation_sent_at: one_year_ago) }
+ let!(:bad_email_2) { emails.create!(user_id: user_needs_migration_2.id, email: 'other2@test.com', confirmed_at: confirmed_at_3_days_ago, confirmation_sent_at: one_year_ago) }
+ let!(:bad_email_3_inactive_user) { emails.create!(user_id: inactive_user.id, email: 'other-inactive@test.com', confirmed_at: confirmed_at_3_days_ago, confirmation_sent_at: one_year_ago) }
+ let!(:bad_email_4_bot_user) { emails.create!(user_id: alert_bot_user.id, email: 'other-bot@test.com', confirmed_at: confirmed_at_3_days_ago, confirmation_sent_at: one_year_ago) }
+
+ let!(:good_email_1) { emails.create!(user_id: user_needs_migration_2.id, email: 'other3@test.com', confirmed_at: confirmed_at_2_days_ago, confirmation_sent_at: one_year_ago) }
+ let!(:good_email_2) { emails.create!(user_id: user_needs_migration_2.id, email: 'other4@test.com', confirmed_at: nil) }
+ let!(:good_email_3) { emails.create!(user_id: user_does_not_need_migration.id, email: 'other5@test.com', confirmed_at: confirmed_at_2_days_ago, confirmation_sent_at: one_year_ago) }
+
+ subject do
+ email_ids = [bad_email_1, bad_email_2, good_email_1, good_email_2, good_email_3].map(&:id)
+
+ described_class.new.perform(email_ids.min, email_ids.max)
+ end
+
+ it 'does not change irrelevant email records' do
+ subject
+
+ expect(good_email_1.reload.confirmed_at).to be_within(1.second).of(confirmed_at_2_days_ago)
+ expect(good_email_2.reload.confirmed_at).to be_nil
+ expect(good_email_3.reload.confirmed_at).to be_within(1.second).of(confirmed_at_2_days_ago)
+
+ expect(bad_email_3_inactive_user.reload.confirmed_at).to be_within(1.second).of(confirmed_at_3_days_ago)
+ expect(bad_email_4_bot_user.reload.confirmed_at).to be_within(1.second).of(confirmed_at_3_days_ago)
+
+ expect(good_email_1.reload.confirmation_sent_at).to be_within(1.second).of(one_year_ago)
+ expect(good_email_2.reload.confirmation_sent_at).to be_nil
+ expect(good_email_3.reload.confirmation_sent_at).to be_within(1.second).of(one_year_ago)
+
+ expect(bad_email_3_inactive_user.reload.confirmation_sent_at).to be_within(1.second).of(one_year_ago)
+ expect(bad_email_4_bot_user.reload.confirmation_sent_at).to be_within(1.second).of(one_year_ago)
+ end
+
+ it 'clears the `unconfirmed_email` field' do
+ subject
+
+ user_needs_migration_2.reload
+ expect(user_needs_migration_2.unconfirmed_email).to be_nil
+ end
+
+ it 'does not change irrelevant user records' do
+ subject
+
+ expect(user_does_not_need_migration.reload.confirmed_at).to be_nil
+ expect(inactive_user.reload.confirmed_at).to be_within(1.second).of(confirmed_at_3_days_ago)
+ expect(alert_bot_user.reload.confirmed_at).to be_within(1.second).of(confirmed_at_3_days_ago)
+
+ expect(user_does_not_need_migration.reload.confirmation_sent_at).to be_nil
+ expect(inactive_user.reload.confirmation_sent_at).to be_within(1.second).of(one_year_ago)
+ expect(alert_bot_user.reload.confirmation_sent_at).to be_within(1.second).of(one_year_ago)
+ end
+
+ it 'updates confirmation_sent_at column' do
+ subject
+
+ expect(user_needs_migration_1.reload.confirmation_sent_at).to be_within(1.minute).of(Time.now)
+ expect(user_needs_migration_2.reload.confirmation_sent_at).to be_within(1.minute).of(Time.now)
+
+ expect(bad_email_1.reload.confirmation_sent_at).to be_within(1.minute).of(Time.now)
+ expect(bad_email_2.reload.confirmation_sent_at).to be_within(1.minute).of(Time.now)
+ end
+
+ it 'unconfirms bad email records' do
+ subject
+
+ expect(bad_email_1.reload.confirmed_at).to be_nil
+ expect(bad_email_2.reload.confirmed_at).to be_nil
+
+ expect(bad_email_1.reload.confirmation_token).not_to be_nil
+ expect(bad_email_2.reload.confirmation_token).not_to be_nil
+ end
+
+ it 'unconfirms user records' do
+ subject
+
+ expect(user_needs_migration_1.reload.confirmed_at).to be_nil
+ expect(user_needs_migration_2.reload.confirmed_at).to be_nil
+
+ expect(user_needs_migration_1.reload.confirmation_token).not_to be_nil
+ expect(user_needs_migration_2.reload.confirmation_token).not_to be_nil
+ end
+
+ context 'enqueued jobs' do
+ let(:user_1) { User.find(user_needs_migration_1.id) }
+ let(:user_2) { User.find(user_needs_migration_2.id) }
+
+ let(:email_1) { Email.find(bad_email_1.id) }
+ let(:email_2) { Email.find(bad_email_2.id) }
+
+ it 'enqueues the email confirmation and the unconfirm notification mailer jobs' do
+ allow(DeviseMailer).to receive(:confirmation_instructions).and_call_original
+ allow(Gitlab::BackgroundMigration::Mailers::UnconfirmMailer).to receive(:unconfirm_notification_email).and_call_original
+
+ subject
+
+ expect(DeviseMailer).to have_received(:confirmation_instructions).with(email_1, email_1.confirmation_token)
+ expect(DeviseMailer).to have_received(:confirmation_instructions).with(email_2, email_2.confirmation_token)
+
+ expect(Gitlab::BackgroundMigration::Mailers::UnconfirmMailer).to have_received(:unconfirm_notification_email).with(user_1)
+ expect(DeviseMailer).to have_received(:confirmation_instructions).with(user_1, user_1.confirmation_token)
+
+ expect(Gitlab::BackgroundMigration::Mailers::UnconfirmMailer).to have_received(:unconfirm_notification_email).with(user_2)
+ expect(DeviseMailer).to have_received(:confirmation_instructions).with(user_2, user_2.confirmation_token)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration_spec.rb b/spec/lib/gitlab/background_migration_spec.rb
index 71959f54b38..b110fa484ff 100644
--- a/spec/lib/gitlab/background_migration_spec.rb
+++ b/spec/lib/gitlab/background_migration_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration do
+RSpec.describe Gitlab::BackgroundMigration do
describe '.queue' do
it 'returns background migration worker queue' do
expect(described_class.queue)
@@ -47,6 +47,25 @@ describe Gitlab::BackgroundMigration do
described_class.steal('Bar')
end
+
+ context 'when a custom predicate is given' do
+ it 'steals jobs that match the predicate' do
+ expect(queue[0]).to receive(:delete).and_return(true)
+
+ expect(described_class).to receive(:perform)
+ .with('Foo', [10, 20])
+
+ described_class.steal('Foo') { |(arg1, arg2)| arg1 == 10 && arg2 == 20 }
+ end
+
+ it 'does not steal jobs that do not match the predicate' do
+ expect(described_class).not_to receive(:perform)
+
+ expect(queue[0]).not_to receive(:delete)
+
+ described_class.steal('Foo') { |(arg1, _)| arg1 == 5 }
+ end
+ end
end
context 'when one of the jobs raises an error' do
diff --git a/spec/lib/gitlab/backtrace_cleaner_spec.rb b/spec/lib/gitlab/backtrace_cleaner_spec.rb
index f3aded9faad..51d99bf5f74 100644
--- a/spec/lib/gitlab/backtrace_cleaner_spec.rb
+++ b/spec/lib/gitlab/backtrace_cleaner_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BacktraceCleaner do
+RSpec.describe Gitlab::BacktraceCleaner do
describe '.clean_backtrace' do
it 'uses the Rails backtrace cleaner' do
backtrace = []
diff --git a/spec/lib/gitlab/badge/coverage/metadata_spec.rb b/spec/lib/gitlab/badge/coverage/metadata_spec.rb
index 2b87508bdef..725ae03ad74 100644
--- a/spec/lib/gitlab/badge/coverage/metadata_spec.rb
+++ b/spec/lib/gitlab/badge/coverage/metadata_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'lib/gitlab/badge/shared/metadata'
-describe Gitlab::Badge::Coverage::Metadata do
+RSpec.describe Gitlab::Badge::Coverage::Metadata do
let(:badge) do
double(project: create(:project), ref: 'feature', job: 'test')
end
diff --git a/spec/lib/gitlab/badge/coverage/report_spec.rb b/spec/lib/gitlab/badge/coverage/report_spec.rb
index 284ca53a996..9c4dfcbfd54 100644
--- a/spec/lib/gitlab/badge/coverage/report_spec.rb
+++ b/spec/lib/gitlab/badge/coverage/report_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Badge::Coverage::Report do
+RSpec.describe Gitlab::Badge::Coverage::Report do
let(:project) { create(:project, :repository) }
let(:job_name) { nil }
diff --git a/spec/lib/gitlab/badge/coverage/template_spec.rb b/spec/lib/gitlab/badge/coverage/template_spec.rb
index 3940b37830e..5a0adfd8e59 100644
--- a/spec/lib/gitlab/badge/coverage/template_spec.rb
+++ b/spec/lib/gitlab/badge/coverage/template_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Badge::Coverage::Template do
+RSpec.describe Gitlab::Badge::Coverage::Template do
let(:badge) { double(entity: 'coverage', status: 90.00, customization: {}) }
let(:template) { described_class.new(badge) }
diff --git a/spec/lib/gitlab/badge/pipeline/metadata_spec.rb b/spec/lib/gitlab/badge/pipeline/metadata_spec.rb
index b096803f921..c8ed0c8ea29 100644
--- a/spec/lib/gitlab/badge/pipeline/metadata_spec.rb
+++ b/spec/lib/gitlab/badge/pipeline/metadata_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'lib/gitlab/badge/shared/metadata'
-describe Gitlab::Badge::Pipeline::Metadata do
+RSpec.describe Gitlab::Badge::Pipeline::Metadata do
let(:badge) { double(project: create(:project), ref: 'feature') }
let(:metadata) { described_class.new(badge) }
diff --git a/spec/lib/gitlab/badge/pipeline/status_spec.rb b/spec/lib/gitlab/badge/pipeline/status_spec.rb
index ab8d1f0ec5b..fcc0d4030fd 100644
--- a/spec/lib/gitlab/badge/pipeline/status_spec.rb
+++ b/spec/lib/gitlab/badge/pipeline/status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Badge::Pipeline::Status do
+RSpec.describe Gitlab::Badge::Pipeline::Status do
let(:project) { create(:project, :repository) }
let(:sha) { project.commit.sha }
let(:branch) { 'master' }
diff --git a/spec/lib/gitlab/badge/pipeline/template_spec.rb b/spec/lib/gitlab/badge/pipeline/template_spec.rb
index 751a5d6645e..2f0d0782369 100644
--- a/spec/lib/gitlab/badge/pipeline/template_spec.rb
+++ b/spec/lib/gitlab/badge/pipeline/template_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Badge::Pipeline::Template do
+RSpec.describe Gitlab::Badge::Pipeline::Template do
let(:badge) { double(entity: 'pipeline', status: 'success', customization: {}) }
let(:template) { described_class.new(badge) }
diff --git a/spec/lib/gitlab/badge/shared/metadata.rb b/spec/lib/gitlab/badge/shared/metadata.rb
index 809fa54db02..c99a65bb2f4 100644
--- a/spec/lib/gitlab/badge/shared/metadata.rb
+++ b/spec/lib/gitlab/badge/shared/metadata.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'badge metadata' do
+RSpec.shared_examples 'badge metadata' do
describe '#to_html' do
let(:html) { Nokogiri::HTML.parse(metadata.to_html) }
let(:a_href) { html.at('a') }
diff --git a/spec/lib/gitlab/bare_repository_import/importer_spec.rb b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
index 75a23d4f49e..e09430a858c 100644
--- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BareRepositoryImport::Importer, :seed_helper do
+RSpec.describe Gitlab::BareRepositoryImport::Importer, :seed_helper do
let!(:admin) { create(:admin) }
let!(:base_dir) { Dir.mktmpdir + '/' }
let(:bare_repository) { Gitlab::BareRepositoryImport::Repository.new(base_dir, File.join(base_dir, "#{project_path}.git")) }
diff --git a/spec/lib/gitlab/bare_repository_import/repository_spec.rb b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
index d2ecb1869fc..bf115046744 100644
--- a/spec/lib/gitlab/bare_repository_import/repository_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::BareRepositoryImport::Repository do
+RSpec.describe ::Gitlab::BareRepositoryImport::Repository do
context 'legacy storage' do
subject { described_class.new('/full/path/', '/full/path/to/repo.git') }
diff --git a/spec/lib/gitlab/batch_pop_queueing_spec.rb b/spec/lib/gitlab/batch_pop_queueing_spec.rb
index 28984d52024..41efc5417e4 100644
--- a/spec/lib/gitlab/batch_pop_queueing_spec.rb
+++ b/spec/lib/gitlab/batch_pop_queueing_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BatchPopQueueing do
+RSpec.describe Gitlab::BatchPopQueueing do
include ExclusiveLeaseHelpers
using RSpec::Parameterized::TableSyntax
diff --git a/spec/lib/gitlab/batch_worker_context_spec.rb b/spec/lib/gitlab/batch_worker_context_spec.rb
index 0ba30287ae5..31641f7449e 100644
--- a/spec/lib/gitlab/batch_worker_context_spec.rb
+++ b/spec/lib/gitlab/batch_worker_context_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BatchWorkerContext do
+RSpec.describe Gitlab::BatchWorkerContext do
subject(:batch_context) do
described_class.new(
%w(hello world),
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index 137d0fd4f9e..08b7bafddf0 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BitbucketImport::Importer do
+RSpec.describe Gitlab::BitbucketImport::Importer do
include ImportSpecHelper
before do
@@ -226,8 +226,8 @@ describe Gitlab::BitbucketImport::Importer do
it 'counts imported pull requests' do
expect(Gitlab::Metrics).to receive(:counter).with(
- :bitbucket_importer_imported_pull_requests,
- 'The number of imported Bitbucket pull requests'
+ :bitbucket_importer_imported_merge_requests_total,
+ 'The number of imported merge (pull) requests'
)
expect(counter).to receive(:increment)
@@ -369,8 +369,8 @@ describe Gitlab::BitbucketImport::Importer do
it 'counts imported issues' do
expect(Gitlab::Metrics).to receive(:counter).with(
- :bitbucket_importer_imported_issues,
- 'The number of imported Bitbucket issues'
+ :bitbucket_importer_imported_issues_total,
+ 'The number of imported issues'
)
expect(counter).to receive(:increment)
@@ -389,23 +389,27 @@ describe Gitlab::BitbucketImport::Importer do
allow(subject).to receive(:import_issues)
allow(subject).to receive(:import_pull_requests)
- allow(Gitlab::Metrics).to receive(:counter) { counter }
- allow(Gitlab::Metrics).to receive(:histogram) { histogram }
+ allow(Gitlab::Metrics).to receive(:counter).and_return(counter)
+ allow(Gitlab::Metrics).to receive(:histogram).and_return(histogram)
+ allow(histogram).to receive(:observe)
+ allow(counter).to receive(:increment)
end
it 'counts and measures duration of imported projects' do
expect(Gitlab::Metrics).to receive(:counter).with(
- :bitbucket_importer_imported_projects,
- 'The number of imported Bitbucket projects'
+ :bitbucket_importer_imported_projects_total,
+ 'The number of imported projects'
)
expect(Gitlab::Metrics).to receive(:histogram).with(
:bitbucket_importer_total_duration_seconds,
- 'Total time spent importing Bitbucket projects, in seconds'
+ 'Total time spent importing projects, in seconds',
+ {},
+ Gitlab::Import::Metrics::IMPORT_DURATION_BUCKETS
)
expect(counter).to receive(:increment)
- expect(histogram).to receive(:observe).with({ importer: described_class::IMPORTER }, anything)
+ expect(histogram).to receive(:observe).with({ importer: :bitbucket_importer }, anything)
subject.execute
end
diff --git a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
index 0dd8547a925..236e04a041b 100644
--- a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BitbucketImport::ProjectCreator do
+RSpec.describe Gitlab::BitbucketImport::ProjectCreator do
let(:user) { create(:user) }
let(:repo) do
diff --git a/spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb b/spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb
index 7b5c7847f2d..4c285d31979 100644
--- a/spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BitbucketImport::WikiFormatter do
+RSpec.describe Gitlab::BitbucketImport::WikiFormatter do
let(:project) do
create(:project,
namespace: create(:namespace, path: 'gitlabhq'),
diff --git a/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb
index cf39d2cb753..5eb27c51f9e 100644
--- a/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb
@@ -2,10 +2,13 @@
require 'spec_helper'
-describe Gitlab::BitbucketServerImport::Importer do
+RSpec.describe Gitlab::BitbucketServerImport::Importer do
include ImportSpecHelper
- let(:project) { create(:project, :repository, import_url: 'http://my-bitbucket') }
+ let(:import_url) { 'http://my-bitbucket' }
+ let(:user) { 'bitbucket' }
+ let(:password) { 'test' }
+ let(:project) { create(:project, :repository, import_url: import_url) }
let(:now) { Time.now.utc.change(usec: 0) }
let(:project_key) { 'TEST' }
let(:repo_slug) { 'rouge' }
@@ -16,7 +19,7 @@ describe Gitlab::BitbucketServerImport::Importer do
before do
data = project.create_or_update_import_data(
data: { project_key: project_key, repo_slug: repo_slug },
- credentials: { base_uri: 'http://my-bitbucket', user: 'bitbucket', password: 'test' }
+ credentials: { base_uri: import_url, user: user, password: password }
)
data.save
project.save
@@ -125,6 +128,48 @@ describe Gitlab::BitbucketServerImport::Importer do
expect(note.updated_at).to eq(@pr_note.created_at)
end
+ context 'metrics' do
+ let(:histogram) { double(:histogram) }
+ let(:counter) { double('counter', increment: true) }
+
+ before do
+ allow(Gitlab::Metrics).to receive(:counter) { counter }
+ allow(Gitlab::Metrics).to receive(:histogram) { histogram }
+ allow(subject.client).to receive(:activities).and_return([@merge_event])
+ end
+
+ it 'counts and measures duration of imported projects' do
+ expect(Gitlab::Metrics).to receive(:counter).with(
+ :bitbucket_server_importer_imported_projects_total,
+ 'The number of imported projects'
+ )
+
+ expect(Gitlab::Metrics).to receive(:histogram).with(
+ :bitbucket_server_importer_total_duration_seconds,
+ 'Total time spent importing projects, in seconds',
+ {},
+ Gitlab::Import::Metrics::IMPORT_DURATION_BUCKETS
+ )
+
+ expect(counter).to receive(:increment)
+ expect(histogram).to receive(:observe).with({ importer: :bitbucket_server_importer }, anything)
+
+ subject.execute
+ end
+
+ it 'counts imported pull requests' do
+ expect(Gitlab::Metrics).to receive(:counter).with(
+ :bitbucket_server_importer_imported_merge_requests_total,
+ 'The number of imported merge (pull) requests'
+ )
+
+ expect(counter).to receive(:increment)
+ allow(histogram).to receive(:observe).with({ importer: :bitbucket_server_importer }, anything)
+
+ subject.execute
+ end
+ end
+
it 'imports threaded discussions' do
reply = instance_double(
BitbucketServer::Representation::PullRequestComment,
diff --git a/spec/lib/gitlab/blame_spec.rb b/spec/lib/gitlab/blame_spec.rb
index e1afd5b25bb..e22399723ac 100644
--- a/spec/lib/gitlab/blame_spec.rb
+++ b/spec/lib/gitlab/blame_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Blame do
+RSpec.describe Gitlab::Blame do
let(:project) { create(:project, :repository) }
let(:path) { 'files/ruby/popen.rb' }
let(:commit) { project.commit('master') }
diff --git a/spec/lib/gitlab/blob_helper_spec.rb b/spec/lib/gitlab/blob_helper_spec.rb
index e057385b35f..65fa5bf0120 100644
--- a/spec/lib/gitlab/blob_helper_spec.rb
+++ b/spec/lib/gitlab/blob_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BlobHelper do
+RSpec.describe Gitlab::BlobHelper do
include FakeBlobHelpers
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/branch_push_merge_commit_analyzer_spec.rb b/spec/lib/gitlab/branch_push_merge_commit_analyzer_spec.rb
index a27f14cd621..f09f38a6127 100644
--- a/spec/lib/gitlab/branch_push_merge_commit_analyzer_spec.rb
+++ b/spec/lib/gitlab/branch_push_merge_commit_analyzer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BranchPushMergeCommitAnalyzer do
+RSpec.describe Gitlab::BranchPushMergeCommitAnalyzer do
let(:project) { create(:project, :repository) }
let(:oldrev) { 'merge-commit-analyze-before' }
let(:newrev) { 'merge-commit-analyze-after' }
diff --git a/spec/lib/gitlab/build_access_spec.rb b/spec/lib/gitlab/build_access_spec.rb
index b7af8ace5b5..c6248f94772 100644
--- a/spec/lib/gitlab/build_access_spec.rb
+++ b/spec/lib/gitlab/build_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BuildAccess do
+RSpec.describe Gitlab::BuildAccess do
let(:user) { create(:user) }
let(:project) { create(:project) }
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 fc9266f75fb..8d625cab1d8 100644
--- a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
+++ b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
@@ -2,8 +2,8 @@
require 'spec_helper'
-describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
- let!(:project) { create(:project, :repository) }
+RSpec.describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
+ let_it_be(:project) { create(:project, :repository) }
let(:pipeline_status) { described_class.new(project) }
let(:cache_key) { pipeline_status.cache_key }
@@ -77,6 +77,62 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
end
describe '#load_status' do
+ describe 'gitaly call counts', :request_store do
+ context 'not cached' do
+ before do
+ expect(pipeline_status).not_to be_has_cache
+ end
+
+ context 'ci_pipeline_status_omit_commit_sha_in_cache_key is enabled' do
+ before do
+ stub_feature_flags(ci_pipeline_status_omit_commit_sha_in_cache_key: project)
+ end
+
+ it 'makes a Gitaly call' do
+ expect { pipeline_status.load_status }.to change { Gitlab::GitalyClient.get_request_count }.by(1)
+ end
+ end
+
+ context 'ci_pipeline_status_omit_commit_sha_in_cache_key is disabled' do
+ before do
+ stub_feature_flags(ci_pipeline_status_omit_commit_sha_in_cache_key: false)
+ end
+
+ it 'makes a Gitaly calls' do
+ expect { pipeline_status.load_status }.to change { Gitlab::GitalyClient.get_request_count }.by(1)
+ end
+ end
+ end
+
+ context 'cached' do
+ before do
+ described_class.load_in_batch_for_projects([project])
+
+ expect(pipeline_status).to be_has_cache
+ end
+
+ context 'ci_pipeline_status_omit_commit_sha_in_cache_key is enabled' do
+ before do
+ stub_feature_flags(ci_pipeline_status_omit_commit_sha_in_cache_key: project)
+ end
+
+ it 'makes no Gitaly calls' do
+ expect { pipeline_status.load_status }.to change { Gitlab::GitalyClient.get_request_count }.by(0)
+ end
+ end
+
+ context 'ci_pipeline_status_omit_commit_sha_in_cache_key is disabled' do
+ before do
+ stub_feature_flags(ci_pipeline_status_omit_commit_sha_in_cache_key: false)
+ end
+
+ it 'makes a Gitaly calls' do
+ expect { pipeline_status.load_status }.to change { Gitlab::GitalyClient.get_request_count }.by(1)
+ end
+ end
+ end
+ end
+
it 'loads the status from the cache when there is one' do
expect(pipeline_status).to receive(:has_cache?).and_return(true)
expect(pipeline_status).to receive(:load_from_cache)
diff --git a/spec/lib/gitlab/cache/import/caching_spec.rb b/spec/lib/gitlab/cache/import/caching_spec.rb
index 7b4308d32ae..d6911dad9d4 100644
--- a/spec/lib/gitlab/cache/import/caching_spec.rb
+++ b/spec/lib/gitlab/cache/import/caching_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Cache::Import::Caching, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::Cache::Import::Caching, :clean_gitlab_redis_cache do
describe '.read' do
it 'reads a value from the cache' do
described_class.write('foo', 'bar')
diff --git a/spec/lib/gitlab/cache/request_cache_spec.rb b/spec/lib/gitlab/cache/request_cache_spec.rb
index 70a7f090d0a..57aee525ddd 100644
--- a/spec/lib/gitlab/cache/request_cache_spec.rb
+++ b/spec/lib/gitlab/cache/request_cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Cache::RequestCache do
+RSpec.describe Gitlab::Cache::RequestCache do
let(:klass) do
Class.new do
extend Gitlab::Cache::RequestCache
diff --git a/spec/lib/gitlab/changes_list_spec.rb b/spec/lib/gitlab/changes_list_spec.rb
index 911450f3a8b..8292764f561 100644
--- a/spec/lib/gitlab/changes_list_spec.rb
+++ b/spec/lib/gitlab/changes_list_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::ChangesList do
+RSpec.describe Gitlab::ChangesList do
let(:valid_changes_string) { "\n000000 570e7b2 refs/heads/my_branch\nd14d6c 6fd24d refs/heads/master" }
let(:invalid_changes) { 1 }
diff --git a/spec/lib/gitlab/chat/command_spec.rb b/spec/lib/gitlab/chat/command_spec.rb
index f7f344bf786..89c693daaa0 100644
--- a/spec/lib/gitlab/chat/command_spec.rb
+++ b/spec/lib/gitlab/chat/command_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Chat::Command do
+RSpec.describe Gitlab::Chat::Command do
let(:chat_name) { create(:chat_name) }
let(:command) do
diff --git a/spec/lib/gitlab/chat/output_spec.rb b/spec/lib/gitlab/chat/output_spec.rb
index b179f9e9d0a..38e17c39fad 100644
--- a/spec/lib/gitlab/chat/output_spec.rb
+++ b/spec/lib/gitlab/chat/output_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Chat::Output do
+RSpec.describe Gitlab::Chat::Output do
let(:build) do
create(:ci_build, pipeline: create(:ci_pipeline, source: :chat))
end
diff --git a/spec/lib/gitlab/chat/responder/base_spec.rb b/spec/lib/gitlab/chat/responder/base_spec.rb
index 7fa9bad9d38..667228cbab4 100644
--- a/spec/lib/gitlab/chat/responder/base_spec.rb
+++ b/spec/lib/gitlab/chat/responder/base_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Chat::Responder::Base do
+RSpec.describe Gitlab::Chat::Responder::Base do
let(:project) { double(:project) }
let(:pipeline) { double(:pipeline, project: project) }
let(:build) { double(:build, pipeline: pipeline) }
diff --git a/spec/lib/gitlab/chat/responder/mattermost_spec.rb b/spec/lib/gitlab/chat/responder/mattermost_spec.rb
index f3480dfef06..ca5f83d760a 100644
--- a/spec/lib/gitlab/chat/responder/mattermost_spec.rb
+++ b/spec/lib/gitlab/chat/responder/mattermost_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Chat::Responder::Mattermost do
+RSpec.describe Gitlab::Chat::Responder::Mattermost do
let(:chat_name) { create(:chat_name, chat_id: 'U123') }
let(:pipeline) do
diff --git a/spec/lib/gitlab/chat/responder/slack_spec.rb b/spec/lib/gitlab/chat/responder/slack_spec.rb
index a1553232b32..4801a1c6b66 100644
--- a/spec/lib/gitlab/chat/responder/slack_spec.rb
+++ b/spec/lib/gitlab/chat/responder/slack_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Chat::Responder::Slack do
+RSpec.describe Gitlab::Chat::Responder::Slack do
let(:chat_name) { create(:chat_name, chat_id: 'U123') }
let(:pipeline) do
diff --git a/spec/lib/gitlab/chat/responder_spec.rb b/spec/lib/gitlab/chat/responder_spec.rb
index 9893689cba9..6603dbe8d52 100644
--- a/spec/lib/gitlab/chat/responder_spec.rb
+++ b/spec/lib/gitlab/chat/responder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Chat::Responder do
+RSpec.describe Gitlab::Chat::Responder do
describe '.responder_for' do
context 'using a regular build' do
it 'returns nil' do
diff --git a/spec/lib/gitlab/chat_name_token_spec.rb b/spec/lib/gitlab/chat_name_token_spec.rb
index b2d4a466021..906c02d54db 100644
--- a/spec/lib/gitlab/chat_name_token_spec.rb
+++ b/spec/lib/gitlab/chat_name_token_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ChatNameToken do
+RSpec.describe Gitlab::ChatNameToken do
context 'when using unknown token' do
let(:token) { }
diff --git a/spec/lib/gitlab/chat_spec.rb b/spec/lib/gitlab/chat_spec.rb
index be606fe6db1..a9df35ace98 100644
--- a/spec/lib/gitlab/chat_spec.rb
+++ b/spec/lib/gitlab/chat_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Chat, :use_clean_rails_memory_store_caching do
+RSpec.describe Gitlab::Chat, :use_clean_rails_memory_store_caching do
describe '.available?' do
it 'returns true when the chatops feature is available' do
stub_feature_flags(chatops: true)
diff --git a/spec/lib/gitlab/checks/branch_check_spec.rb b/spec/lib/gitlab/checks/branch_check_spec.rb
index fd7eaa1603f..92452727017 100644
--- a/spec/lib/gitlab/checks/branch_check_spec.rb
+++ b/spec/lib/gitlab/checks/branch_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::BranchCheck do
+RSpec.describe Gitlab::Checks::BranchCheck do
include_context 'change access checks context'
describe '#validate!' do
diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb
index dfc8c59fd74..87936d19239 100644
--- a/spec/lib/gitlab/checks/change_access_spec.rb
+++ b/spec/lib/gitlab/checks/change_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::ChangeAccess do
+RSpec.describe Gitlab::Checks::ChangeAccess do
describe '#exec' do
include_context 'change access checks context'
diff --git a/spec/lib/gitlab/checks/diff_check_spec.rb b/spec/lib/gitlab/checks/diff_check_spec.rb
index 467b4ed3a21..2cca0aed9c6 100644
--- a/spec/lib/gitlab/checks/diff_check_spec.rb
+++ b/spec/lib/gitlab/checks/diff_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::DiffCheck do
+RSpec.describe Gitlab::Checks::DiffCheck do
include_context 'change access checks context'
describe '#validate!' do
diff --git a/spec/lib/gitlab/checks/force_push_spec.rb b/spec/lib/gitlab/checks/force_push_spec.rb
index 334dd8635a3..49e02fe5cec 100644
--- a/spec/lib/gitlab/checks/force_push_spec.rb
+++ b/spec/lib/gitlab/checks/force_push_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::ForcePush do
+RSpec.describe Gitlab::Checks::ForcePush do
let_it_be(:project) { create(:project, :repository) }
describe '.force_push?' do
diff --git a/spec/lib/gitlab/checks/lfs_check_spec.rb b/spec/lib/gitlab/checks/lfs_check_spec.rb
index c86481d1abe..713858e0e35 100644
--- a/spec/lib/gitlab/checks/lfs_check_spec.rb
+++ b/spec/lib/gitlab/checks/lfs_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::LfsCheck do
+RSpec.describe Gitlab::Checks::LfsCheck do
include_context 'change access checks context'
let(:blob_object) { project.repository.blob_at_branch('lfs', 'files/lfs/lfs_object.iso') }
diff --git a/spec/lib/gitlab/checks/lfs_integrity_spec.rb b/spec/lib/gitlab/checks/lfs_integrity_spec.rb
index 505f117034e..8fec702790c 100644
--- a/spec/lib/gitlab/checks/lfs_integrity_spec.rb
+++ b/spec/lib/gitlab/checks/lfs_integrity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::LfsIntegrity do
+RSpec.describe Gitlab::Checks::LfsIntegrity do
include ProjectForksHelper
let!(:time_left) { 50 }
diff --git a/spec/lib/gitlab/checks/project_created_spec.rb b/spec/lib/gitlab/checks/project_created_spec.rb
index bbc97155374..f099f19b061 100644
--- a/spec/lib/gitlab/checks/project_created_spec.rb
+++ b/spec/lib/gitlab/checks/project_created_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::ProjectCreated, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::Checks::ProjectCreated, :clean_gitlab_redis_shared_state do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, namespace: user.namespace) }
let(:protocol) { 'http' }
diff --git a/spec/lib/gitlab/checks/project_moved_spec.rb b/spec/lib/gitlab/checks/project_moved_spec.rb
index 1d1d6211088..e15fa90443b 100644
--- a/spec/lib/gitlab/checks/project_moved_spec.rb
+++ b/spec/lib/gitlab/checks/project_moved_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, :wiki_repo, namespace: user.namespace) }
let(:repository) { project.repository }
diff --git a/spec/lib/gitlab/checks/push_check_spec.rb b/spec/lib/gitlab/checks/push_check_spec.rb
index 857d71732fe..45ab13cf0cf 100644
--- a/spec/lib/gitlab/checks/push_check_spec.rb
+++ b/spec/lib/gitlab/checks/push_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::PushCheck do
+RSpec.describe Gitlab::Checks::PushCheck do
include_context 'change access checks context'
describe '#validate!' do
diff --git a/spec/lib/gitlab/checks/push_file_count_check_spec.rb b/spec/lib/gitlab/checks/push_file_count_check_spec.rb
index e05102a9ce8..e05070e8f35 100644
--- a/spec/lib/gitlab/checks/push_file_count_check_spec.rb
+++ b/spec/lib/gitlab/checks/push_file_count_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::PushFileCountCheck do
+RSpec.describe Gitlab::Checks::PushFileCountCheck do
let(:snippet) { create(:personal_snippet, :repository) }
let(:changes) { { oldrev: oldrev, newrev: newrev, ref: ref } }
let(:timeout) { Gitlab::GitAccess::INTERNAL_TIMEOUT }
diff --git a/spec/lib/gitlab/checks/snippet_check_spec.rb b/spec/lib/gitlab/checks/snippet_check_spec.rb
index 3eee5ccfc0a..2c027486bc9 100644
--- a/spec/lib/gitlab/checks/snippet_check_spec.rb
+++ b/spec/lib/gitlab/checks/snippet_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::SnippetCheck do
+RSpec.describe Gitlab::Checks::SnippetCheck do
include_context 'change access checks context'
let(:snippet) { create(:personal_snippet, :repository) }
diff --git a/spec/lib/gitlab/checks/tag_check_spec.rb b/spec/lib/gitlab/checks/tag_check_spec.rb
index 0c94171646e..e2e7d9c9648 100644
--- a/spec/lib/gitlab/checks/tag_check_spec.rb
+++ b/spec/lib/gitlab/checks/tag_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::TagCheck do
+RSpec.describe Gitlab::Checks::TagCheck do
include_context 'change access checks context'
describe '#validate!' do
diff --git a/spec/lib/gitlab/checks/timed_logger_spec.rb b/spec/lib/gitlab/checks/timed_logger_spec.rb
index 0ed3940c038..6c488212eca 100644
--- a/spec/lib/gitlab/checks/timed_logger_spec.rb
+++ b/spec/lib/gitlab/checks/timed_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Checks::TimedLogger do
+RSpec.describe Gitlab::Checks::TimedLogger do
let!(:timeout) { 50.seconds }
let!(:start) { Time.now }
let!(:ref) { "bar" }
diff --git a/spec/lib/gitlab/ci/ansi2html_spec.rb b/spec/lib/gitlab/ci/ansi2html_spec.rb
index ee789995bc2..f29a39e4e66 100644
--- a/spec/lib/gitlab/ci/ansi2html_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2html_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Ansi2html do
+RSpec.describe Gitlab::Ci::Ansi2html do
subject { described_class }
it "prints non-ansi as-is" do
diff --git a/spec/lib/gitlab/ci/ansi2json/line_spec.rb b/spec/lib/gitlab/ci/ansi2json/line_spec.rb
index 4b5c3f9489e..8b1cd812a70 100644
--- a/spec/lib/gitlab/ci/ansi2json/line_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2json/line_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Ansi2json::Line do
+RSpec.describe Gitlab::Ci::Ansi2json::Line do
let(:offset) { 0 }
let(:style) { Gitlab::Ci::Ansi2json::Style.new }
diff --git a/spec/lib/gitlab/ci/ansi2json/parser_spec.rb b/spec/lib/gitlab/ci/ansi2json/parser_spec.rb
index e161e74c1ff..cf93ebe0721 100644
--- a/spec/lib/gitlab/ci/ansi2json/parser_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2json/parser_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
# The rest of the specs for this class are covered in style_spec.rb
-describe Gitlab::Ci::Ansi2json::Parser do
+RSpec.describe Gitlab::Ci::Ansi2json::Parser do
subject { described_class }
describe 'bold?' do
diff --git a/spec/lib/gitlab/ci/ansi2json/result_spec.rb b/spec/lib/gitlab/ci/ansi2json/result_spec.rb
index 5b7b5481400..31c0da95f0a 100644
--- a/spec/lib/gitlab/ci/ansi2json/result_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2json/result_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Ansi2json::Result do
+RSpec.describe Gitlab::Ci::Ansi2json::Result do
let(:stream) { StringIO.new('hello') }
let(:state) { Gitlab::Ci::Ansi2json::State.new(nil, stream.size) }
let(:offset) { 0 }
diff --git a/spec/lib/gitlab/ci/ansi2json/style_spec.rb b/spec/lib/gitlab/ci/ansi2json/style_spec.rb
index ad05aa03e83..d27a642ecf3 100644
--- a/spec/lib/gitlab/ci/ansi2json/style_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2json/style_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Ansi2json::Style do
+RSpec.describe Gitlab::Ci::Ansi2json::Style do
describe '#set?' do
subject { described_class.new(params).set? }
diff --git a/spec/lib/gitlab/ci/ansi2json_spec.rb b/spec/lib/gitlab/ci/ansi2json_spec.rb
index 124379fa321..cb6949fddc2 100644
--- a/spec/lib/gitlab/ci/ansi2json_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2json_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Ansi2json do
+RSpec.describe Gitlab::Ci::Ansi2json do
subject { described_class }
describe 'lines' do
diff --git a/spec/lib/gitlab/ci/artifact_file_reader_spec.rb b/spec/lib/gitlab/ci/artifact_file_reader_spec.rb
index 04017b9ae3e..e982f0eb015 100644
--- a/spec/lib/gitlab/ci/artifact_file_reader_spec.rb
+++ b/spec/lib/gitlab/ci/artifact_file_reader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::ArtifactFileReader do
+RSpec.describe Gitlab::Ci::ArtifactFileReader do
let(:job) { create(:ci_build) }
let(:path) { 'generated.yml' } # included in the ci_build_artifacts.zip
diff --git a/spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb b/spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb
index cec3e70bb9f..98f909a0ed0 100644
--- a/spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Artifacts::Adapters::GzipStream do
+RSpec.describe Gitlab::Ci::Build::Artifacts::Adapters::GzipStream do
describe '#initialize' do
context 'when stream is passed' do
let(:stream) { File.open(expand_fixture_path('junit/junit.xml.gz'), 'rb') }
diff --git a/spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb b/spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb
index 66a234232e1..badff972e60 100644
--- a/spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Artifacts::Adapters::RawStream do
+RSpec.describe Gitlab::Ci::Build::Artifacts::Adapters::RawStream do
describe '#initialize' do
context 'when stream is passed' do
let(:stream) { File.open(expand_fixture_path('junit/junit.xml'), 'rb') }
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
index 7a413a7aeac..c8ace28108b 100644
--- a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
+RSpec.describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
let(:entries) do
{ 'path/' => {},
'path/dir_1/' => {},
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
index bfa65c66b33..77b8aa1d591 100644
--- a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Artifacts::Metadata do
+RSpec.describe Gitlab::Ci::Build::Artifacts::Metadata do
def metadata(path = '', **opts)
described_class.new(metadata_file_stream, path, **opts)
end
diff --git a/spec/lib/gitlab/ci/build/artifacts/path_spec.rb b/spec/lib/gitlab/ci/build/artifacts/path_spec.rb
index 7bbef0f5197..27b7dac2ae4 100644
--- a/spec/lib/gitlab/ci/build/artifacts/path_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/path_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Artifacts::Path do
+RSpec.describe Gitlab::Ci::Build::Artifacts::Path do
describe '#valid?' do
context 'when path contains a zero character' do
it 'is not valid' do
diff --git a/spec/lib/gitlab/ci/build/context/build_spec.rb b/spec/lib/gitlab/ci/build/context/build_spec.rb
index 1b73b9a083d..61ca8e759b5 100644
--- a/spec/lib/gitlab/ci/build/context/build_spec.rb
+++ b/spec/lib/gitlab/ci/build/context/build_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Context::Build do
+RSpec.describe Gitlab::Ci::Build::Context::Build do
let(:pipeline) { create(:ci_pipeline) }
let(:seed_attributes) { { 'name' => 'some-job' } }
diff --git a/spec/lib/gitlab/ci/build/context/global_spec.rb b/spec/lib/gitlab/ci/build/context/global_spec.rb
index 65cc41ed3f9..7394708f9b6 100644
--- a/spec/lib/gitlab/ci/build/context/global_spec.rb
+++ b/spec/lib/gitlab/ci/build/context/global_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Context::Global do
+RSpec.describe Gitlab::Ci::Build::Context::Global do
let(:pipeline) { create(:ci_pipeline) }
let(:yaml_variables) { {} }
diff --git a/spec/lib/gitlab/ci/build/credentials/factory_spec.rb b/spec/lib/gitlab/ci/build/credentials/factory_spec.rb
index 159f89f4985..613d360f78d 100644
--- a/spec/lib/gitlab/ci/build/credentials/factory_spec.rb
+++ b/spec/lib/gitlab/ci/build/credentials/factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Credentials::Factory do
+RSpec.describe Gitlab::Ci::Build::Credentials::Factory do
let(:build) { create(:ci_build, name: 'spinach', stage: 'test', stage_idx: 0) }
subject { described_class.new(build).create! }
diff --git a/spec/lib/gitlab/ci/build/credentials/registry_spec.rb b/spec/lib/gitlab/ci/build/credentials/registry_spec.rb
index 552580dcbbe..c0a76973f60 100644
--- a/spec/lib/gitlab/ci/build/credentials/registry_spec.rb
+++ b/spec/lib/gitlab/ci/build/credentials/registry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Credentials::Registry do
+RSpec.describe Gitlab::Ci::Build::Credentials::Registry do
let(:build) { create(:ci_build, name: 'spinach', stage: 'test', stage_idx: 0) }
let(:registry_url) { 'registry.example.com:5005' }
diff --git a/spec/lib/gitlab/ci/build/image_spec.rb b/spec/lib/gitlab/ci/build/image_spec.rb
index 4f7cfc9783a..71cd57d317c 100644
--- a/spec/lib/gitlab/ci/build/image_spec.rb
+++ b/spec/lib/gitlab/ci/build/image_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Image do
+RSpec.describe Gitlab::Ci::Build::Image do
let(:job) { create(:ci_build, :no_options) }
describe '#from_image' do
diff --git a/spec/lib/gitlab/ci/build/policy/changes_spec.rb b/spec/lib/gitlab/ci/build/policy/changes_spec.rb
index 07f52605929..016730e01cd 100644
--- a/spec/lib/gitlab/ci/build/policy/changes_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/changes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Policy::Changes do
+RSpec.describe Gitlab::Ci::Build::Policy::Changes do
let_it_be(:project) { create(:project) }
describe '#satisfied_by?' do
diff --git a/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb b/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb
index 6d96bdc30c7..81c0b76b453 100644
--- a/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Policy::Kubernetes do
+RSpec.describe Gitlab::Ci::Build::Policy::Kubernetes do
let(:pipeline) { create(:ci_pipeline, project: project) }
context 'when kubernetes service is active' do
diff --git a/spec/lib/gitlab/ci/build/policy/refs_spec.rb b/spec/lib/gitlab/ci/build/policy/refs_spec.rb
index c32fdc5c72e..7fd51102d71 100644
--- a/spec/lib/gitlab/ci/build/policy/refs_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/refs_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Policy::Refs do
+RSpec.describe Gitlab::Ci::Build::Policy::Refs do
describe '#satisfied_by?' do
context 'when matching ref' do
let(:pipeline) { build_stubbed(:ci_pipeline, ref: 'master') }
diff --git a/spec/lib/gitlab/ci/build/policy/variables_spec.rb b/spec/lib/gitlab/ci/build/policy/variables_spec.rb
index 0e75726b81c..f692aa6146e 100644
--- a/spec/lib/gitlab/ci/build/policy/variables_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/variables_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Policy::Variables do
+RSpec.describe Gitlab::Ci::Build::Policy::Variables do
let_it_be(:project) { create(:project) }
let(:pipeline) do
diff --git a/spec/lib/gitlab/ci/build/policy_spec.rb b/spec/lib/gitlab/ci/build/policy_spec.rb
index 80d7b2e9dc8..b85b093fd03 100644
--- a/spec/lib/gitlab/ci/build/policy_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Policy do
+RSpec.describe Gitlab::Ci::Build::Policy do
let(:policy) { spy('policy specification') }
before do
diff --git a/spec/lib/gitlab/ci/build/port_spec.rb b/spec/lib/gitlab/ci/build/port_spec.rb
index 1413780dfa6..480418e0851 100644
--- a/spec/lib/gitlab/ci/build/port_spec.rb
+++ b/spec/lib/gitlab/ci/build/port_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Port do
+RSpec.describe Gitlab::Ci::Build::Port do
subject { described_class.new(port) }
context 'when port is defined as an integer' do
diff --git a/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb
index 5187f99a441..a38ade4bfa5 100644
--- a/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb
+++ b/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Prerequisite::Factory do
+RSpec.describe Gitlab::Ci::Build::Prerequisite::Factory do
let(:build) { create(:ci_build) }
describe '.for_build' do
diff --git a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
index 66240380edd..94c14cfa479 100644
--- a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
+++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
+RSpec.describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
describe '#unmet?' do
let(:build) { create(:ci_build) }
diff --git a/spec/lib/gitlab/ci/build/releaser_spec.rb b/spec/lib/gitlab/ci/build/releaser_spec.rb
index 2f7bca777dd..fa5e90674a6 100644
--- a/spec/lib/gitlab/ci/build/releaser_spec.rb
+++ b/spec/lib/gitlab/ci/build/releaser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Releaser do
+RSpec.describe Gitlab::Ci::Build::Releaser do
subject { described_class.new(config: config[:release]).script }
describe '#script' do
@@ -13,13 +13,15 @@ describe Gitlab::Ci::Build::Releaser do
name: 'Release $CI_COMMIT_SHA',
description: 'Created using the release-cli $EXTRA_DESCRIPTION',
tag_name: 'release-$CI_COMMIT_SHA',
- ref: '$CI_COMMIT_SHA'
+ ref: '$CI_COMMIT_SHA',
+ milestones: %w[m1 m2 m3],
+ released_at: '2020-07-15T08:00:00Z'
}
}
end
it 'generates the script' do
- expect(subject).to eq('release-cli create --name "Release $CI_COMMIT_SHA" --description "Created using the release-cli $EXTRA_DESCRIPTION" --tag-name "release-$CI_COMMIT_SHA" --ref "$CI_COMMIT_SHA"')
+ expect(subject).to eq(['release-cli create --name "Release $CI_COMMIT_SHA" --description "Created using the release-cli $EXTRA_DESCRIPTION" --tag-name "release-$CI_COMMIT_SHA" --ref "$CI_COMMIT_SHA" --released-at "2020-07-15T08:00:00Z" --milestone "m1" --milestone "m2" --milestone "m3"'])
end
end
@@ -27,10 +29,12 @@ describe Gitlab::Ci::Build::Releaser do
using RSpec::Parameterized::TableSyntax
where(:node_name, :node_value, :result) do
- 'name' | 'Release $CI_COMMIT_SHA' | 'release-cli create --name "Release $CI_COMMIT_SHA"'
- 'description' | 'Release-cli $EXTRA_DESCRIPTION' | 'release-cli create --description "Release-cli $EXTRA_DESCRIPTION"'
- 'tag_name' | 'release-$CI_COMMIT_SHA' | 'release-cli create --tag-name "release-$CI_COMMIT_SHA"'
- 'ref' | '$CI_COMMIT_SHA' | 'release-cli create --ref "$CI_COMMIT_SHA"'
+ :name | 'Release $CI_COMMIT_SHA' | 'release-cli create --name "Release $CI_COMMIT_SHA"'
+ :description | 'Release-cli $EXTRA_DESCRIPTION' | 'release-cli create --description "Release-cli $EXTRA_DESCRIPTION"'
+ :tag_name | 'release-$CI_COMMIT_SHA' | 'release-cli create --tag-name "release-$CI_COMMIT_SHA"'
+ :ref | '$CI_COMMIT_SHA' | 'release-cli create --ref "$CI_COMMIT_SHA"'
+ :milestones | %w[m1 m2 m3] | 'release-cli create --milestone "m1" --milestone "m2" --milestone "m3"'
+ :released_at | '2020-07-15T08:00:00Z' | 'release-cli create --released-at "2020-07-15T08:00:00Z"'
end
with_them do
@@ -43,7 +47,7 @@ describe Gitlab::Ci::Build::Releaser do
end
it 'generates the script' do
- expect(subject).to eq(result)
+ expect(subject).to eq([result])
end
end
end
diff --git a/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb b/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb
index 076de3646b0..cf52f601006 100644
--- a/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb
+++ b/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do
+RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do
describe '#satisfied_by?' do
it_behaves_like 'a glob matching rule' do
let(:pipeline) { build(:ci_pipeline) }
diff --git a/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb b/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb
index 10843a1435a..86dd5569a96 100644
--- a/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb
+++ b/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Rules::Rule::Clause::Exists do
+RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Exists do
describe '#satisfied_by?' do
let(:pipeline) { build(:ci_pipeline, project: project, sha: project.repository.head_commit.sha) }
diff --git a/spec/lib/gitlab/ci/build/rules/rule_spec.rb b/spec/lib/gitlab/ci/build/rules/rule_spec.rb
index 04cdaa9d0ae..5694cd5d0a0 100644
--- a/spec/lib/gitlab/ci/build/rules/rule_spec.rb
+++ b/spec/lib/gitlab/ci/build/rules/rule_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Rules::Rule do
+RSpec.describe Gitlab::Ci::Build::Rules::Rule do
let(:seed) do
double('build seed',
to_resource: ci_build,
diff --git a/spec/lib/gitlab/ci/build/rules_spec.rb b/spec/lib/gitlab/ci/build/rules_spec.rb
index 31a9fa055e1..cbeae33fbcf 100644
--- a/spec/lib/gitlab/ci/build/rules_spec.rb
+++ b/spec/lib/gitlab/ci/build/rules_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Rules do
+RSpec.describe Gitlab::Ci::Build::Rules do
let(:pipeline) { create(:ci_pipeline) }
let(:ci_build) { build(:ci_build, pipeline: pipeline) }
diff --git a/spec/lib/gitlab/ci/build/step_spec.rb b/spec/lib/gitlab/ci/build/step_spec.rb
index 1cebda2cc7e..4b8f68b9fa8 100644
--- a/spec/lib/gitlab/ci/build/step_spec.rb
+++ b/spec/lib/gitlab/ci/build/step_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Build::Step do
+RSpec.describe Gitlab::Ci::Build::Step do
describe '#from_commands' do
subject { described_class.from_commands(job) }
@@ -62,7 +62,7 @@ describe Gitlab::Ci::Build::Step do
let(:job) { create(:ci_build, :release_options) }
it 'returns the release-cli command line' do
- expect(subject.script).to eq("release-cli create --name \"Release $CI_COMMIT_SHA\" --description \"Created using the release-cli $EXTRA_DESCRIPTION\" --tag-name \"release-$CI_COMMIT_SHA\" --ref \"$CI_COMMIT_SHA\"")
+ expect(subject.script).to eq(["release-cli create --name \"Release $CI_COMMIT_SHA\" --description \"Created using the release-cli $EXTRA_DESCRIPTION\" --tag-name \"release-$CI_COMMIT_SHA\" --ref \"$CI_COMMIT_SHA\""])
end
end
diff --git a/spec/lib/gitlab/ci/charts_spec.rb b/spec/lib/gitlab/ci/charts_spec.rb
index cfb7a3f72fa..e00e5ed3920 100644
--- a/spec/lib/gitlab/ci/charts_spec.rb
+++ b/spec/lib/gitlab/ci/charts_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Charts do
+RSpec.describe Gitlab::Ci::Charts do
context "yearchart" do
let(:project) { create(:project) }
let(:chart) { Gitlab::Ci::Charts::YearChart.new(project) }
diff --git a/spec/lib/gitlab/ci/config/edge_stages_injector_spec.rb b/spec/lib/gitlab/ci/config/edge_stages_injector_spec.rb
index 042f9b591b6..52ea925c1c4 100644
--- a/spec/lib/gitlab/ci/config/edge_stages_injector_spec.rb
+++ b/spec/lib/gitlab/ci/config/edge_stages_injector_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Ci::Config::EdgeStagesInjector do
+RSpec.describe Gitlab::Ci::Config::EdgeStagesInjector do
describe '#call' do
subject { described_class.new(config).to_hash }
diff --git a/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb b/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb
index 8cfd07df777..028dcd3e1e6 100644
--- a/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Artifacts do
+RSpec.describe Gitlab::Ci::Config::Entry::Artifacts do
let(:entry) { described_class.new(config) }
describe 'validation' do
diff --git a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
index d08ce30618d..f33176c3da3 100644
--- a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Bridge do
+RSpec.describe Gitlab::Ci::Config::Entry::Bridge do
subject { described_class.new(config, name: :my_bridge) }
it_behaves_like 'with inheritable CI config' do
diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
index f7b14360af3..3501812b76e 100644
--- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Cache do
+RSpec.describe Gitlab::Ci::Config::Entry::Cache do
subject(:entry) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/ci/config/entry/commands_spec.rb b/spec/lib/gitlab/ci/config/entry/commands_spec.rb
index 8e7f9ab9706..439799fe973 100644
--- a/spec/lib/gitlab/ci/config/entry/commands_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/commands_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Commands do
+RSpec.describe Gitlab::Ci::Config::Entry::Commands do
let(:entry) { described_class.new(config) }
context 'when entry config value is an array of strings' do
diff --git a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
index 877e3ec6216..c4353e822ae 100644
--- a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Coverage do
+RSpec.describe Gitlab::Ci::Config::Entry::Coverage do
let(:entry) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/ci/config/entry/default_spec.rb b/spec/lib/gitlab/ci/config/entry/default_spec.rb
index 23c62bbf92a..6e46d02a96e 100644
--- a/spec/lib/gitlab/ci/config/entry/default_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/default_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Default do
+RSpec.describe Gitlab::Ci::Config::Entry::Default do
let(:entry) { described_class.new(config) }
it_behaves_like 'with inheritable CI config' do
diff --git a/spec/lib/gitlab/ci/config/entry/environment_spec.rb b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
index 8c21d5342cc..0c18a7fb71e 100644
--- a/spec/lib/gitlab/ci/config/entry/environment_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Environment do
+RSpec.describe Gitlab::Ci::Config::Entry::Environment do
let(:entry) { described_class.new(config) }
before do
@@ -102,6 +102,17 @@ describe Gitlab::Ci::Config::Entry::Environment do
end
end
+ context 'when prepare action is used' do
+ let(:config) do
+ { name: 'production',
+ action: 'prepare' }
+ end
+
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
context 'when wrong action type is used' do
let(:config) do
{ name: 'production',
@@ -137,7 +148,7 @@ describe Gitlab::Ci::Config::Entry::Environment do
describe '#errors' do
it 'contains error about invalid action' do
expect(entry.errors)
- .to include 'environment action should be start or stop'
+ .to include 'environment action should be start, stop or prepare'
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/files_spec.rb b/spec/lib/gitlab/ci/config/entry/files_spec.rb
index 2bebbd7b198..6a101d80c7d 100644
--- a/spec/lib/gitlab/ci/config/entry/files_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/files_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Files do
+RSpec.describe Gitlab::Ci::Config::Entry::Files do
let(:entry) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/ci/config/entry/hidden_spec.rb b/spec/lib/gitlab/ci/config/entry/hidden_spec.rb
index 40b73352676..090ef67f39d 100644
--- a/spec/lib/gitlab/ci/config/entry/hidden_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/hidden_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Hidden do
+RSpec.describe Gitlab::Ci::Config::Entry::Hidden do
describe '.matching?' do
subject { described_class.matching?(name, {}) }
diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb
index de3e887a6ed..c3d91057328 100644
--- a/spec/lib/gitlab/ci/config/entry/image_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Image do
+RSpec.describe Gitlab::Ci::Config::Entry::Image do
let(:entry) { described_class.new(config) }
context 'when configuration is a string' do
diff --git a/spec/lib/gitlab/ci/config/entry/include_spec.rb b/spec/lib/gitlab/ci/config/entry/include_spec.rb
index bab11f26fa1..3e816f70c03 100644
--- a/spec/lib/gitlab/ci/config/entry/include_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/include_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::Ci::Config::Entry::Include do
+RSpec.describe ::Gitlab::Ci::Config::Entry::Include do
subject(:include_entry) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/ci/config/entry/inherit/default_spec.rb b/spec/lib/gitlab/ci/config/entry/inherit/default_spec.rb
index 073f93ce542..7cd9b0acb99 100644
--- a/spec/lib/gitlab/ci/config/entry/inherit/default_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/inherit/default_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::Ci::Config::Entry::Inherit::Default do
+RSpec.describe ::Gitlab::Ci::Config::Entry::Inherit::Default do
using RSpec::Parameterized::TableSyntax
subject { described_class.new(config) }
diff --git a/spec/lib/gitlab/ci/config/entry/inherit/variables_spec.rb b/spec/lib/gitlab/ci/config/entry/inherit/variables_spec.rb
index 06deed11c15..b1a8fbcdbe0 100644
--- a/spec/lib/gitlab/ci/config/entry/inherit/variables_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/inherit/variables_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::Ci::Config::Entry::Inherit::Variables do
+RSpec.describe ::Gitlab::Ci::Config::Entry::Inherit::Variables do
using RSpec::Parameterized::TableSyntax
subject { described_class.new(config) }
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index b6279485426..180c52ee1ab 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Job do
+RSpec.describe Gitlab::Ci::Config::Entry::Job do
let(:entry) { described_class.new(config, name: :rspec) }
it_behaves_like 'with inheritable CI config' do
@@ -33,7 +33,7 @@ describe Gitlab::Ci::Config::Entry::Job do
inherit]
end
- it { is_expected.to match_array result }
+ it { is_expected.to include(*result) }
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
index 203342ab620..8561bd330b7 100644
--- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Jobs do
+RSpec.describe Gitlab::Ci::Config::Entry::Jobs do
let(:entry) { described_class.new(config) }
let(:config) do
diff --git a/spec/lib/gitlab/ci/config/entry/key_spec.rb b/spec/lib/gitlab/ci/config/entry/key_spec.rb
index 327607e2266..4ee1ffb29ad 100644
--- a/spec/lib/gitlab/ci/config/entry/key_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/key_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Key do
+RSpec.describe Gitlab::Ci::Config::Entry::Key do
let(:entry) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/ci/config/entry/kubernetes_spec.rb b/spec/lib/gitlab/ci/config/entry/kubernetes_spec.rb
index 468e83ec506..53809d2d549 100644
--- a/spec/lib/gitlab/ci/config/entry/kubernetes_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/kubernetes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Kubernetes do
+RSpec.describe Gitlab::Ci::Config::Entry::Kubernetes do
subject { described_class.new(config) }
describe 'attributes' do
diff --git a/spec/lib/gitlab/ci/config/entry/need_spec.rb b/spec/lib/gitlab/ci/config/entry/need_spec.rb
index 92b71c5f6cc..5a826bf8282 100644
--- a/spec/lib/gitlab/ci/config/entry/need_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/need_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::Ci::Config::Entry::Need do
+RSpec.describe ::Gitlab::Ci::Config::Entry::Need do
subject(:need) { described_class.new(config) }
shared_examples 'job type' do
diff --git a/spec/lib/gitlab/ci/config/entry/needs_spec.rb b/spec/lib/gitlab/ci/config/entry/needs_spec.rb
index b8b84b5efd2..f3b9d0c3c84 100644
--- a/spec/lib/gitlab/ci/config/entry/needs_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/needs_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::Ci::Config::Entry::Needs do
+RSpec.describe ::Gitlab::Ci::Config::Entry::Needs do
subject(:needs) { described_class.new(config) }
before do
diff --git a/spec/lib/gitlab/ci/config/entry/paths_spec.rb b/spec/lib/gitlab/ci/config/entry/paths_spec.rb
index 221d5ae5863..dd4e7befc46 100644
--- a/spec/lib/gitlab/ci/config/entry/paths_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/paths_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Paths do
+RSpec.describe Gitlab::Ci::Config::Entry::Paths do
let(:entry) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
index a606eb303e7..46800055dd9 100644
--- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
@@ -4,7 +4,7 @@ require 'fast_spec_helper'
require 'support/helpers/stub_feature_flags'
require_dependency 'active_model'
-describe Gitlab::Ci::Config::Entry::Policy do
+RSpec.describe Gitlab::Ci::Config::Entry::Policy do
let(:entry) { described_class.new(config) }
context 'when using simplified policy' do
diff --git a/spec/lib/gitlab/ci/config/entry/port_spec.rb b/spec/lib/gitlab/ci/config/entry/port_spec.rb
index 5f8f294334e..e2840c07f6b 100644
--- a/spec/lib/gitlab/ci/config/entry/port_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/port_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Port do
+RSpec.describe Gitlab::Ci::Config::Entry::Port do
let(:entry) { described_class.new(config) }
before do
diff --git a/spec/lib/gitlab/ci/config/entry/ports_spec.rb b/spec/lib/gitlab/ci/config/entry/ports_spec.rb
index 2063bd1d86c..f738c4ee875 100644
--- a/spec/lib/gitlab/ci/config/entry/ports_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/ports_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Ports do
+RSpec.describe Gitlab::Ci::Config::Entry::Ports do
let(:entry) { described_class.new(config) }
before do
diff --git a/spec/lib/gitlab/ci/config/entry/prefix_spec.rb b/spec/lib/gitlab/ci/config/entry/prefix_spec.rb
index 8132a674488..b337747f5d6 100644
--- a/spec/lib/gitlab/ci/config/entry/prefix_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/prefix_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Prefix do
+RSpec.describe Gitlab::Ci::Config::Entry::Prefix do
let(:entry) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/ci/config/entry/processable_spec.rb b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
index 8447a29c772..fdf6008f89f 100644
--- a/spec/lib/gitlab/ci/config/entry/processable_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Processable do
+RSpec.describe Gitlab::Ci::Config::Entry::Processable do
let(:node_class) do
Class.new(::Gitlab::Config::Entry::Node) do
include Gitlab::Ci::Config::Entry::Processable
@@ -231,6 +231,12 @@ describe Gitlab::Ci::Config::Entry::Processable do
end
context 'when workflow rules is used' do
+ let(:workflow) { double('workflow', 'has_rules?' => true) }
+
+ before do
+ entry.compose!(deps)
+ end
+
context 'when rules are used' do
let(:config) { { script: 'ls', cache: { key: 'test' }, rules: [] } }
@@ -239,11 +245,11 @@ describe Gitlab::Ci::Config::Entry::Processable do
end
end
- context 'when rules are not used' do
+ context 'when rules are not used and only is defined' do
let(:config) { { script: 'ls', cache: { key: 'test' }, only: [] } }
- it 'does not define only' do
- expect(entry).not_to be_only_defined
+ it 'keeps only entry' do
+ expect(entry).to be_only_defined
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/release/assets/link_spec.rb b/spec/lib/gitlab/ci/config/entry/release/assets/link_spec.rb
index 0e346de3d9e..bc320258412 100644
--- a/spec/lib/gitlab/ci/config/entry/release/assets/link_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/release/assets/link_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Release::Assets::Link do
+RSpec.describe Gitlab::Ci::Config::Entry::Release::Assets::Link do
let(:entry) { described_class.new(config) }
describe 'validation' do
diff --git a/spec/lib/gitlab/ci/config/entry/release/assets/links_spec.rb b/spec/lib/gitlab/ci/config/entry/release/assets/links_spec.rb
index d12e8d966ab..440745d9eb3 100644
--- a/spec/lib/gitlab/ci/config/entry/release/assets/links_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/release/assets/links_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Release::Assets::Links do
+RSpec.describe Gitlab::Ci::Config::Entry::Release::Assets::Links do
let(:entry) { described_class.new(config) }
describe 'validation' do
diff --git a/spec/lib/gitlab/ci/config/entry/release/assets_spec.rb b/spec/lib/gitlab/ci/config/entry/release/assets_spec.rb
index 08ad5764eaa..3d44d7d9903 100644
--- a/spec/lib/gitlab/ci/config/entry/release/assets_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/release/assets_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Release::Assets do
+RSpec.describe Gitlab::Ci::Config::Entry::Release::Assets do
let(:entry) { described_class.new(config) }
describe 'validation' do
diff --git a/spec/lib/gitlab/ci/config/entry/release_spec.rb b/spec/lib/gitlab/ci/config/entry/release_spec.rb
index 500897569e9..e5155f91be4 100644
--- a/spec/lib/gitlab/ci/config/entry/release_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/release_spec.rb
@@ -2,24 +2,35 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Release do
+RSpec.describe Gitlab::Ci::Config::Entry::Release do
let(:entry) { described_class.new(config) }
+ shared_examples_for 'a valid entry' do
+ describe '#value' do
+ it 'returns release configuration' do
+ expect(entry.value).to eq config
+ end
+ end
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ shared_examples_for 'reports error' do |message|
+ it 'reports error' do
+ expect(entry.errors)
+ .to include message
+ end
+ end
+
describe 'validation' do
context 'when entry config value is correct' do
let(:config) { { tag_name: 'v0.06', description: "./release_changelog.txt" } }
- describe '#value' do
- it 'returns release configuration' do
- expect(entry.value).to eq config
- end
- end
-
- describe '#valid?' do
- it 'is valid' do
- expect(entry).to be_valid
- end
- end
+ it_behaves_like 'a valid entry'
end
context "when value includes 'assets' keyword" do
@@ -36,38 +47,144 @@ describe Gitlab::Ci::Config::Entry::Release do
}
end
- describe '#value' do
- it 'returns release configuration' do
- expect(entry.value).to eq config
- end
+ it_behaves_like 'a valid entry'
+ end
+
+ context "when value includes 'name' keyword" do
+ let(:config) do
+ {
+ tag_name: 'v0.06',
+ description: "./release_changelog.txt",
+ name: "Release $CI_TAG_NAME"
+ }
end
- describe '#valid?' do
- it 'is valid' do
- expect(entry).to be_valid
- end
+ it_behaves_like 'a valid entry'
+ end
+
+ context "when value includes 'ref' keyword" do
+ let(:config) do
+ {
+ tag_name: 'v0.06',
+ description: "./release_changelog.txt",
+ name: "Release $CI_TAG_NAME",
+ ref: 'b3235930aa443112e639f941c69c578912189bdd'
+ }
end
+
+ it_behaves_like 'a valid entry'
end
- context "when value includes 'name' keyword" do
+ context "when value includes 'released_at' keyword" do
let(:config) do
{
tag_name: 'v0.06',
description: "./release_changelog.txt",
- name: "Release $CI_TAG_NAME"
+ name: "Release $CI_TAG_NAME",
+ released_at: '2019-03-15T08:00:00Z'
}
end
- describe '#value' do
- it 'returns release configuration' do
- expect(entry.value).to eq config
- end
+ it_behaves_like 'a valid entry'
+ end
+
+ context "when value includes 'milestones' keyword" do
+ let(:config) do
+ {
+ tag_name: 'v0.06',
+ description: "./release_changelog.txt",
+ name: "Release $CI_TAG_NAME",
+ milestones: milestones
+ }
end
- describe '#valid?' do
- it 'is valid' do
- expect(entry).to be_valid
- end
+ context 'for an array of milestones' do
+ let(:milestones) { %w[m1 m2 m3] }
+
+ it_behaves_like 'a valid entry'
+ end
+
+ context 'for a single milestone' do
+ let(:milestones) { 'm1' }
+
+ it_behaves_like 'a valid entry'
+ end
+ end
+
+ context "when value includes 'ref' keyword" do
+ let(:config) do
+ {
+ tag_name: 'v0.06',
+ description: "./release_changelog.txt",
+ name: "Release $CI_TAG_NAME",
+ ref: ref
+ }
+ end
+
+ context "when 'ref' is a full commit SHA" do
+ let(:ref) { 'b3235930aa443112e639f941c69c578912189bdd' }
+
+ it_behaves_like 'a valid entry'
+ end
+
+ context "when 'ref' is a short commit SHA" do
+ let(:ref) { 'b3235930'}
+
+ it_behaves_like 'a valid entry'
+ end
+
+ context "when 'ref' is a branch name" do
+ let(:ref) { 'fix/123-branch-name'}
+
+ it_behaves_like 'a valid entry'
+ end
+
+ context "when 'ref' is a semantic versioning tag" do
+ let(:ref) { 'v1.2.3'}
+
+ it_behaves_like 'a valid entry'
+ end
+
+ context "when 'ref' is a semantic versioning tag rc" do
+ let(:ref) { 'v1.2.3-rc'}
+
+ it_behaves_like 'a valid entry'
+ end
+ end
+
+ context "when value includes 'released_at' keyword" do
+ let(:config) do
+ {
+ tag_name: 'v0.06',
+ description: "./release_changelog.txt",
+ name: "Release $CI_TAG_NAME",
+ released_at: '2019-03-15T08:00:00Z'
+ }
+ end
+
+ it_behaves_like 'a valid entry'
+ end
+
+ context "when value includes 'milestones' keyword" do
+ let(:config) do
+ {
+ tag_name: 'v0.06',
+ description: "./release_changelog.txt",
+ name: "Release $CI_TAG_NAME",
+ milestones: milestones
+ }
+ end
+
+ context 'for an array of milestones' do
+ let(:milestones) { %w[m1 m2 m3] }
+
+ it_behaves_like 'a valid entry'
+ end
+
+ context 'for a single milestone' do
+ let(:milestones) { 'm1' }
+
+ it_behaves_like 'a valid entry'
end
end
@@ -76,37 +193,43 @@ describe Gitlab::Ci::Config::Entry::Release do
context 'when value of attribute is invalid' do
let(:config) { { description: 10 } }
- it 'reports error' do
- expect(entry.errors)
- .to include 'release description should be a string'
- end
+ it_behaves_like 'reports error', 'release description should be a string'
end
context 'when release description is missing' do
let(:config) { { tag_name: 'v0.06' } }
- it 'reports error' do
- expect(entry.errors)
- .to include "release description can't be blank"
- end
+ it_behaves_like 'reports error', "release description can't be blank"
end
context 'when release tag_name is missing' do
let(:config) { { description: "./release_changelog.txt" } }
- it 'reports error' do
- expect(entry.errors)
- .to include "release tag name can't be blank"
- end
+ it_behaves_like 'reports error', "release tag name can't be blank"
end
context 'when there is an unknown key present' do
let(:config) { { test: 100 } }
- it 'reports error' do
- expect(entry.errors)
- .to include 'release config contains unknown keys: test'
- end
+ it_behaves_like 'reports error', 'release config contains unknown keys: test'
+ end
+
+ context 'when `released_at` is not a valid date' do
+ let(:config) { { released_at: 'ABC123' } }
+
+ it_behaves_like 'reports error', 'release released at must be a valid datetime'
+ end
+
+ context 'when `ref` is not valid' do
+ let(:config) { { ref: 'invalid\branch' } }
+
+ it_behaves_like 'reports error', 'release ref must be a valid ref'
+ end
+
+ context 'when `milestones` is not an array of strings' do
+ let(:config) { { milestones: [1, 2, 3] } }
+
+ it_behaves_like 'reports error', 'release milestones should be an array of strings or a string'
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/reports_spec.rb b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
index 2c12a88dedb..98105ebcd55 100644
--- a/spec/lib/gitlab/ci/config/entry/reports_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Reports do
+RSpec.describe Gitlab::Ci::Config::Entry::Reports do
let(:entry) { described_class.new(config) }
describe 'validates ALLOWED_KEYS' do
@@ -44,6 +44,9 @@ describe Gitlab::Ci::Config::Entry::Reports do
:license_management | 'gl-license-management-report.json'
:license_scanning | 'gl-license-scanning-report.json'
:performance | 'performance.json'
+ :browser_performance | 'browser-performance.json'
+ :browser_performance | 'performance.json'
+ :load_performance | 'load-performance.json'
:lsif | 'lsif.json'
:dotenv | 'build.dotenv'
:cobertura | 'cobertura-coverage.xml'
diff --git a/spec/lib/gitlab/ci/config/entry/retry_spec.rb b/spec/lib/gitlab/ci/config/entry/retry_spec.rb
index 67253c71f6b..b38387a437e 100644
--- a/spec/lib/gitlab/ci/config/entry/retry_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/retry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Retry do
+RSpec.describe Gitlab::Ci::Config::Entry::Retry do
let(:entry) { described_class.new(config) }
shared_context 'when retry value is a numeric', :numeric do
diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb
index ba2dbf72fba..140b3c4f55b 100644
--- a/spec/lib/gitlab/ci/config/entry/root_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Root do
+RSpec.describe Gitlab::Ci::Config::Entry::Root do
let(:root) { described_class.new(hash) }
describe '.nodes' do
diff --git a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
index 20db5f02fc7..4a43e6c9a86 100644
--- a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
@@ -5,7 +5,7 @@ require 'gitlab_chronic_duration'
require 'support/helpers/stub_feature_flags'
require_dependency 'active_model'
-describe Gitlab::Ci::Config::Entry::Rules::Rule do
+RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule do
let(:factory) do
Gitlab::Config::Entry::Factory.new(described_class)
.metadata(metadata)
diff --git a/spec/lib/gitlab/ci/config/entry/rules_spec.rb b/spec/lib/gitlab/ci/config/entry/rules_spec.rb
index 3c050801023..7d26365e7b3 100644
--- a/spec/lib/gitlab/ci/config/entry/rules_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/rules_spec.rb
@@ -4,7 +4,7 @@ require 'fast_spec_helper'
require 'support/helpers/stub_feature_flags'
require_dependency 'active_model'
-describe Gitlab::Ci::Config::Entry::Rules do
+RSpec.describe Gitlab::Ci::Config::Entry::Rules do
let(:factory) do
Gitlab::Config::Entry::Factory.new(described_class)
.metadata(metadata)
diff --git a/spec/lib/gitlab/ci/config/entry/script_spec.rb b/spec/lib/gitlab/ci/config/entry/script_spec.rb
index 57dc20ea628..1ddf7881e81 100644
--- a/spec/lib/gitlab/ci/config/entry/script_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/script_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Script do
+RSpec.describe Gitlab::Ci::Config::Entry::Script do
let(:entry) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb
index 66cca100688..9fbc14c19b9 100644
--- a/spec/lib/gitlab/ci/config/entry/service_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Service do
+RSpec.describe Gitlab::Ci::Config::Entry::Service do
let(:entry) { described_class.new(config) }
before do
diff --git a/spec/lib/gitlab/ci/config/entry/services_spec.rb b/spec/lib/gitlab/ci/config/entry/services_spec.rb
index 764f783b083..e4f8a348d21 100644
--- a/spec/lib/gitlab/ci/config/entry/services_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/services_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Services do
+RSpec.describe Gitlab::Ci::Config::Entry::Services do
let(:entry) { described_class.new(config) }
before do
diff --git a/spec/lib/gitlab/ci/config/entry/stage_spec.rb b/spec/lib/gitlab/ci/config/entry/stage_spec.rb
index 574fa00575a..2a105fb978e 100644
--- a/spec/lib/gitlab/ci/config/entry/stage_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Stage do
+RSpec.describe Gitlab::Ci::Config::Entry::Stage do
let(:stage) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/ci/config/entry/stages_spec.rb b/spec/lib/gitlab/ci/config/entry/stages_spec.rb
index 3e6ff8eca28..1dd11bb03f8 100644
--- a/spec/lib/gitlab/ci/config/entry/stages_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/stages_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Stages do
+RSpec.describe Gitlab::Ci::Config::Entry::Stages do
let(:entry) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/ci/config/entry/trigger_spec.rb b/spec/lib/gitlab/ci/config/entry/trigger_spec.rb
index dfd9807583c..5b4289741f3 100644
--- a/spec/lib/gitlab/ci/config/entry/trigger_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/trigger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Trigger do
+RSpec.describe Gitlab::Ci::Config::Entry::Trigger do
subject { described_class.new(config) }
context 'when trigger config is a non-empty string' do
diff --git a/spec/lib/gitlab/ci/config/entry/variables_spec.rb b/spec/lib/gitlab/ci/config/entry/variables_spec.rb
index 1320b366367..d6391092f63 100644
--- a/spec/lib/gitlab/ci/config/entry/variables_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/variables_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Variables do
+RSpec.describe Gitlab::Ci::Config::Entry::Variables do
let(:entry) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/ci/config/entry/workflow_spec.rb b/spec/lib/gitlab/ci/config/entry/workflow_spec.rb
index f2832b94bf0..3d19832e13d 100644
--- a/spec/lib/gitlab/ci/config/entry/workflow_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/workflow_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::Entry::Workflow do
+RSpec.describe Gitlab::Ci::Config::Entry::Workflow do
let(:factory) { Gitlab::Config::Entry::Factory.new(described_class).value(rules_hash) }
let(:config) { factory.create! }
diff --git a/spec/lib/gitlab/ci/config/extendable/entry_spec.rb b/spec/lib/gitlab/ci/config/extendable/entry_spec.rb
index e00104e3c68..69aa3bab77a 100644
--- a/spec/lib/gitlab/ci/config/extendable/entry_spec.rb
+++ b/spec/lib/gitlab/ci/config/extendable/entry_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Ci::Config::Extendable::Entry do
+RSpec.describe Gitlab::Ci::Config::Extendable::Entry do
describe '.new' do
context 'when entry key is not included in the context hash' do
it 'raises error' do
diff --git a/spec/lib/gitlab/ci/config/extendable_spec.rb b/spec/lib/gitlab/ci/config/extendable_spec.rb
index 874b224067b..481f55d790e 100644
--- a/spec/lib/gitlab/ci/config/extendable_spec.rb
+++ b/spec/lib/gitlab/ci/config/extendable_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Ci::Config::Extendable do
+RSpec.describe Gitlab::Ci::Config::Extendable do
subject { described_class.new(hash) }
describe '#each' do
diff --git a/spec/lib/gitlab/ci/config/external/context_spec.rb b/spec/lib/gitlab/ci/config/external/context_spec.rb
index 610646ca85a..4b9adf7e87b 100644
--- a/spec/lib/gitlab/ci/config/external/context_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/context_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Ci::Config::External::Context do
+RSpec.describe Gitlab::Ci::Config::External::Context do
let(:project) { double('Project') }
let(:user) { double('User') }
let(:sha) { '12345' }
diff --git a/spec/lib/gitlab/ci/config/external/file/artifact_spec.rb b/spec/lib/gitlab/ci/config/external/file/artifact_spec.rb
index a8eb13c47bc..8dd92c5b5fd 100644
--- a/spec/lib/gitlab/ci/config/external/file/artifact_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/artifact_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::External::File::Artifact do
+RSpec.describe Gitlab::Ci::Config::External::File::Artifact do
let(:parent_pipeline) { create(:ci_pipeline) }
let(:context) do
Gitlab::Ci::Config::External::Context.new(parent_pipeline: parent_pipeline)
diff --git a/spec/lib/gitlab/ci/config/external/file/base_spec.rb b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
index d472d6527e2..445edb253fd 100644
--- a/spec/lib/gitlab/ci/config/external/file/base_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::External::File::Base do
+RSpec.describe Gitlab::Ci::Config::External::File::Base do
let(:context_params) { { sha: 'HEAD' } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
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 c9851239859..993a07568de 100644
--- a/spec/lib/gitlab/ci/config/external/file/local_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::External::File::Local do
+RSpec.describe Gitlab::Ci::Config::External::File::Local do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:sha) { '12345' }
diff --git a/spec/lib/gitlab/ci/config/external/file/project_spec.rb b/spec/lib/gitlab/ci/config/external/file/project_spec.rb
index 4d8f0dbc861..a5e4e27df6f 100644
--- a/spec/lib/gitlab/ci/config/external/file/project_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::External::File::Project do
+RSpec.describe Gitlab::Ci::Config::External::File::Project do
let_it_be(:context_project) { create(:project) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
index a23cce9b757..ab60ac215ba 100644
--- a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::External::File::Remote do
+RSpec.describe Gitlab::Ci::Config::External::File::Remote do
include StubRequests
let(:context_params) { { sha: '12345' } }
diff --git a/spec/lib/gitlab/ci/config/external/file/template_spec.rb b/spec/lib/gitlab/ci/config/external/file/template_spec.rb
index 7ea5aadac52..ad1d93a64a1 100644
--- a/spec/lib/gitlab/ci/config/external/file/template_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/template_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::External::File::Template do
+RSpec.describe Gitlab::Ci::Config::External::File::Template do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let(:context_params) { { project: project, sha: '12345', user: user } }
diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
index fa358f36527..bf14d8d6b34 100644
--- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::External::Mapper do
+RSpec.describe Gitlab::Ci::Config::External::Mapper do
include StubRequests
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb
index 45f646660a7..b2cf36b2597 100644
--- a/spec/lib/gitlab/ci/config/external/processor_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config::External::Processor do
+RSpec.describe Gitlab::Ci::Config::External::Processor do
include StubRequests
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/ci/config/normalizer_spec.rb b/spec/lib/gitlab/ci/config/normalizer_spec.rb
index db62fb7524d..d3d165ba00f 100644
--- a/spec/lib/gitlab/ci/config/normalizer_spec.rb
+++ b/spec/lib/gitlab/ci/config/normalizer_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Ci::Config::Normalizer do
+RSpec.describe Gitlab::Ci::Config::Normalizer do
let(:job_name) { :rspec }
let(:job_config) { { script: 'rspec', parallel: 5, name: 'rspec' } }
let(:config) { { job_name => job_config } }
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
index 3b65dbe11ec..18be9558829 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Config do
+RSpec.describe Gitlab::Ci::Config do
include StubRequests
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/ci/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb
index 8f9f3d7fa37..f724825a9cc 100644
--- a/spec/lib/gitlab/ci/cron_parser_spec.rb
+++ b/spec/lib/gitlab/ci/cron_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::CronParser do
+RSpec.describe Gitlab::Ci::CronParser do
shared_examples_for "returns time in the future" do
it { is_expected.to be > Time.now }
end
diff --git a/spec/lib/gitlab/ci/jwt_spec.rb b/spec/lib/gitlab/ci/jwt_spec.rb
index f2897708b08..a15f3310dab 100644
--- a/spec/lib/gitlab/ci/jwt_spec.rb
+++ b/spec/lib/gitlab/ci/jwt_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Jwt do
+RSpec.describe Gitlab::Ci::Jwt do
let(:namespace) { build_stubbed(:namespace) }
let(:project) { build_stubbed(:project, namespace: namespace) }
let(:user) { build_stubbed(:user) }
diff --git a/spec/lib/gitlab/ci/mask_secret_spec.rb b/spec/lib/gitlab/ci/mask_secret_spec.rb
index 6607aaae399..7b2d6b58518 100644
--- a/spec/lib/gitlab/ci/mask_secret_spec.rb
+++ b/spec/lib/gitlab/ci/mask_secret_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::MaskSecret do
+RSpec.describe Gitlab::Ci::MaskSecret do
subject { described_class }
describe '#mask' do
diff --git a/spec/lib/gitlab/ci/parsers/accessibility/pa11y_spec.rb b/spec/lib/gitlab/ci/parsers/accessibility/pa11y_spec.rb
index 4d87e3b201a..b3edf452f36 100644
--- a/spec/lib/gitlab/ci/parsers/accessibility/pa11y_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/accessibility/pa11y_spec.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
+require 'spec_helper'
-describe Gitlab::Ci::Parsers::Accessibility::Pa11y do
+RSpec.describe Gitlab::Ci::Parsers::Accessibility::Pa11y do
describe '#parse!' do
subject { described_class.new.parse!(pa11y, accessibility_report) }
@@ -108,7 +108,7 @@ describe Gitlab::Ci::Parsers::Accessibility::Pa11y do
it "sets error_message" do
expect { subject }.not_to raise_error
- expect(accessibility_report.error_message).to include('Pa11y parsing failed')
+ expect(accessibility_report.error_message).to include('JSON parsing failed')
expect(accessibility_report.errors_count).to eq(0)
expect(accessibility_report.passes_count).to eq(0)
expect(accessibility_report.scans_count).to eq(0)
diff --git a/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb b/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb
index e97544683db..08a3fbd7867 100644
--- a/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Ci::Parsers::Coverage::Cobertura do
+RSpec.describe Gitlab::Ci::Parsers::Coverage::Cobertura do
describe '#parse!' do
subject { described_class.new.parse!(cobertura, coverage_report) }
diff --git a/spec/lib/gitlab/ci/parsers/terraform/tfplan_spec.rb b/spec/lib/gitlab/ci/parsers/terraform/tfplan_spec.rb
index fec27c0f31a..f487fccdab7 100644
--- a/spec/lib/gitlab/ci/parsers/terraform/tfplan_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/terraform/tfplan_spec.rb
@@ -2,39 +2,86 @@
require 'spec_helper'
-describe Gitlab::Ci::Parsers::Terraform::Tfplan do
+RSpec.describe Gitlab::Ci::Parsers::Terraform::Tfplan do
describe '#parse!' do
- let_it_be(:artifact) { create(:ci_job_artifact, :terraform) }
+ let(:artifact) { create(:ci_job_artifact, :terraform) }
let(:reports) { Gitlab::Ci::Reports::TerraformReports.new }
context 'when data is invalid' do
- context 'when there is no data' do
- it 'raises an error' do
- plan = '{}'
+ context 'when data is not a JSON file' do
+ it 'reports an invalid_json_format error' do
+ plan = 'Not a JSON file'
+
+ expect { subject.parse!(plan, reports, artifact: artifact) }.not_to raise_error
+
+ reports.plans.each do |key, hash_value|
+ expect(hash_value.keys).to match_array(%w[job_id job_name job_path tf_report_error])
+ end
- expect { subject.parse!(plan, reports, artifact: artifact) }.to raise_error(
- described_class::TfplanParserError
+ expect(reports.plans).to match(
+ a_hash_including(
+ artifact.job.id.to_s => a_hash_including(
+ 'tf_report_error' => :invalid_json_format
+ )
+ )
)
end
end
- context 'when data is not a JSON file' do
- it 'raises an error' do
- plan = { 'create' => 0, 'update' => 1, 'delete' => 0 }.to_s
+ context 'when JSON is missing a required key' do
+ it 'reports an invalid_json_keys error' do
+ plan = '{ "wrong_key": 1 }'
+
+ expect { subject.parse!(plan, reports, artifact: artifact) }.not_to raise_error
+
+ reports.plans.each do |key, hash_value|
+ expect(hash_value.keys).to match_array(%w[job_id job_name job_path tf_report_error])
+ end
- expect { subject.parse!(plan, reports, artifact: artifact) }.to raise_error(
- described_class::TfplanParserError
+ expect(reports.plans).to match(
+ a_hash_including(
+ artifact.job.id.to_s => a_hash_including(
+ 'tf_report_error' => :missing_json_keys
+ )
+ )
)
end
end
- context 'when JSON is missing a required key' do
- it 'raises an error' do
- plan = '{ "wrong_key": 1 }'
+ context 'when artifact is invalid' do
+ it 'reports an :unknown_error' do
+ expect { subject.parse!('{}', reports, artifact: nil) }.not_to raise_error
+
+ reports.plans.each do |key, hash_value|
+ expect(hash_value.keys).to match_array(%w[tf_report_error])
+ end
+
+ expect(reports.plans).to match(
+ a_hash_including(
+ 'failed_tf_plan' => a_hash_including(
+ 'tf_report_error' => :unknown_error
+ )
+ )
+ )
+ end
+ end
+
+ context 'when job is invalid' do
+ it 'reports an :unknown_error' do
+ artifact.job_id = nil
+ expect { subject.parse!('{}', reports, artifact: artifact) }.not_to raise_error
- expect { subject.parse!(plan, reports, artifact: artifact) }.to raise_error(
- described_class::TfplanParserError
+ reports.plans.each do |key, hash_value|
+ expect(hash_value.keys).to match_array(%w[tf_report_error])
+ end
+
+ expect(reports.plans).to match(
+ a_hash_including(
+ 'failed_tf_plan' => a_hash_including(
+ 'tf_report_error' => :unknown_error
+ )
+ )
)
end
end
@@ -47,7 +94,7 @@ describe Gitlab::Ci::Parsers::Terraform::Tfplan do
expect { subject.parse!(plan, reports, artifact: artifact) }.not_to raise_error
reports.plans.each do |key, hash_value|
- expect(hash_value.keys).to match_array(%w[create delete job_name job_path update])
+ expect(hash_value.keys).to match_array(%w[create delete job_id job_name job_path update])
end
expect(reports.plans).to match(
@@ -68,7 +115,7 @@ describe Gitlab::Ci::Parsers::Terraform::Tfplan do
expect { subject.parse!(plan, reports, artifact: artifact) }.not_to raise_error
reports.plans.each do |key, hash_value|
- expect(hash_value.keys).to match_array(%w[create delete job_name job_path update])
+ expect(hash_value.keys).to match_array(%w[create delete job_id job_name job_path update])
end
expect(reports.plans).to match(
diff --git a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
index 7b7ace02bba..1f497dea2bf 100644
--- a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Ci::Parsers::Test::Junit do
+RSpec.describe Gitlab::Ci::Parsers::Test::Junit do
describe '#parse!' do
subject { described_class.new.parse!(junit, test_suite, args) }
diff --git a/spec/lib/gitlab/ci/parsers_spec.rb b/spec/lib/gitlab/ci/parsers_spec.rb
index 0a266e7a206..db9a5775d9f 100644
--- a/spec/lib/gitlab/ci/parsers_spec.rb
+++ b/spec/lib/gitlab/ci/parsers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Parsers do
+RSpec.describe Gitlab::Ci::Parsers do
describe '.fabricate!' do
subject { described_class.fabricate!(file_type) }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb
index 542a2462b59..5fa414f5bd1 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Build::Associations do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Build::Associations do
let(:project) { create(:project, :repository) }
let(:user) { create(:user, developer_projects: [project]) }
let(:pipeline) { Ci::Pipeline.new }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
index 094563bd979..6da565a2bf6 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Build do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Build do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user, developer_projects: [project]) }
let(:pipeline) { Ci::Pipeline.new }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
index 56707f4e6e4..bc2012e83bd 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Command do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do
let_it_be(:project) { create(:project, :repository) }
describe '#initialize' do
@@ -270,4 +270,29 @@ describe Gitlab::Ci::Pipeline::Chain::Command do
it { is_expected. to eq(true) }
end
end
+
+ describe '#dangling_build?' do
+ let(:project) { create(:project, :repository) }
+ let(:command) { described_class.new(project: project, source: source) }
+
+ subject { command.dangling_build? }
+
+ context 'when source is :webide' do
+ let(:source) { :webide }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when source is :ondemand_dast_scan' do
+ let(:source) { :ondemand_dast_scan }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when source something else' do
+ let(:source) { :web }
+
+ it { is_expected.to eq(false) }
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb
index fc95bb602c2..42ec9ab6f5d 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb
@@ -2,10 +2,12 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Config::Content do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content do
let(:project) { create(:project, ci_config_path: ci_config_path) }
let(:pipeline) { build(:ci_pipeline, project: project) }
- let(:command) { Gitlab::Ci::Pipeline::Chain::Command.new(project: project) }
+ let(:content) { nil }
+ let(:source) { :push }
+ let(:command) { Gitlab::Ci::Pipeline::Chain::Command.new(project: project, content: content, source: source) }
subject { described_class.new(pipeline, command) }
@@ -141,6 +143,26 @@ describe Gitlab::Ci::Pipeline::Chain::Config::Content do
end
end
+ context 'when config is passed as a parameter' do
+ let(:source) { :ondemand_dast_scan }
+ let(:ci_config_path) { nil }
+ let(:content) do
+ <<~EOY
+ ---
+ stages:
+ - dast
+ EOY
+ end
+
+ it 'uses the parameter content' do
+ subject.perform!
+
+ expect(pipeline.config_source).to eq 'parameter_source'
+ expect(pipeline.pipeline_config.content).to eq(content)
+ expect(command.config_content).to eq(content)
+ end
+ end
+
context 'when config is not defined anywhere' do
let(:ci_config_path) { nil }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb
index f18ad05a704..d60ecc80a6e 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Create do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Create do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb
index 7b76adaf683..4ae51ac8bf9 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:pipeline) { build(:ci_pipeline, project: project) }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
index 6dab5679e60..8c02121857a 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Populate do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Populate do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb
index 92eadf5548c..8b9de16ce5f 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs do
+RSpec.describe ::Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs do
let(:project) { create(:project) }
let(:pipeline) do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
index 1e1d5c2a724..f83cd49d780 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Seed do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
let(:project) { create(:project, :repository) }
let(:user) { create(:user, developer_projects: [project]) }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb
index ea04862ed74..5d20b1b8fda 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Sequence do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Sequence do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb
index 55d6d35340e..e4768f2ef0d 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Skip do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Skip do
let_it_be(:project, reload: true) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:pipeline, reload: true) { create(:ci_pipeline, project: project) }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
index 8bf8bdf08ff..ae3270cb9b2 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
let_it_be(:project, reload: true) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
index 9a2cf014007..931c62701ce 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Validate::External do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:pipeline) { build(:ci_empty_pipeline, user: user, project: project) }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
index 83271007ab0..1dc2e0a1822 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:pipeline) { build_stubbed(:ci_pipeline) }
diff --git a/spec/lib/gitlab/ci/pipeline/duration_spec.rb b/spec/lib/gitlab/ci/pipeline/duration_spec.rb
index a4984092f35..e0b4928d7f7 100644
--- a/spec/lib/gitlab/ci/pipeline/duration_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/duration_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Duration do
+RSpec.describe Gitlab::Ci::Pipeline::Duration do
let(:calculated_duration) { calculate(data) }
shared_examples 'calculating duration' do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb
index 847d613dba3..6601537a2d3 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require 'rspec-parameterized'
-describe Gitlab::Ci::Pipeline::Expression::Lexeme::And do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::And do
let(:left) { double('left', evaluate: nil) }
let(:right) { double('right', evaluate: nil) }
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
index 0e13681a4cf..2bed47f0a87 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Expression::Lexeme::Equals do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Equals do
let(:left) { double('left') }
let(:right) { double('right') }
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
index a527783ffac..efcea0b0e09 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require_dependency 're2'
-describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do
let(:left) { double('left') }
let(:right) { double('right') }
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb
index a3a48f83b27..a81e1713ef0 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotEquals do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotEquals do
let(:left) { double('left') }
let(:right) { double('right') }
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
index fb4238ecaf3..f44fe19f86d 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require_dependency 're2'
-describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotMatches do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotMatches do
let(:left) { double('left') }
let(:right) { double('right') }
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb
index 7013c6bacbb..49686d1a9bd 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Expression::Lexeme::Null do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Null do
describe '.build' do
it 'creates a new instance of the token' do
expect(described_class.build('null'))
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb
index 15505ebc82b..7fe445975eb 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require 'rspec-parameterized'
-describe Gitlab::Ci::Pipeline::Expression::Lexeme::Or do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Or do
let(:left) { double('left', evaluate: nil) }
let(:right) { double('right', evaluate: nil) }
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
index 2cc25a07417..1a56a91c471 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do
describe '.build' do
it 'creates a new instance of the token' do
expect(described_class.build('/.*/'))
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 2a6b90d127f..c6d0d2534a5 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Expression::Lexeme::String do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::String do
describe '.build' do
it 'creates a new instance of the token' do
expect(described_class.build('"my string"'))
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb
index 29e26930249..115674edc48 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Expression::Lexeme::Variable do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Variable do
describe '.build' do
it 'creates a new instance of the token' do
expect(described_class.build('$VARIABLE'))
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
index 2b0cee2d6f2..61c6ced4dac 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Expression::Lexer do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexer do
let(:token_class) do
Gitlab::Ci::Pipeline::Expression::Token
end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb
index 10adfa18af6..1704cabfd2e 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Ci::Pipeline::Expression::Parser do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Parser do
describe '#tree' do
context 'when using two operators' do
it 'returns a reverse descent parse tree' do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
index 79ee4d07e3a..642d6816030 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require 'rspec-parameterized'
-describe Gitlab::Ci::Pipeline::Expression::Statement do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Statement do
subject do
described_class.new(text, variables)
end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb
index aa807cecb72..137e38d2ead 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Ci::Pipeline::Expression::Token do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Token do
let(:value) { '$VARIABLE' }
let(:lexeme) { Gitlab::Ci::Pipeline::Expression::Lexeme::Variable }
diff --git a/spec/lib/gitlab/ci/pipeline/preloader_spec.rb b/spec/lib/gitlab/ci/pipeline/preloader_spec.rb
index 40dfd893465..ae423fa04f9 100644
--- a/spec/lib/gitlab/ci/pipeline/preloader_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/preloader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Preloader do
+RSpec.describe Gitlab::Ci::Pipeline::Preloader do
let(:stage) { double(:stage) }
let(:commit) { double(:commit) }
@@ -28,8 +28,9 @@ describe Gitlab::Ci::Pipeline::Preloader do
end
end
- it 'preloads commit authors and number of warnings' do
+ it 'preloads commit authors, number of warnings and ref commits' do
expect(commit).to receive(:lazy_author)
+ expect(pipeline).to receive(:lazy_ref_commit)
expect(pipeline).to receive(:number_of_warnings)
expect(stage).to receive(:number_of_warnings)
@@ -38,6 +39,7 @@ describe Gitlab::Ci::Pipeline::Preloader do
it 'returns original collection' do
allow(commit).to receive(:lazy_author)
+ allow(pipeline).to receive(:lazy_ref_commit)
allow(pipeline).to receive(:number_of_warnings)
allow(stage).to receive(:number_of_warnings)
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb
index f5b43b5aeab..74c014b6408 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
+RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:head_sha) { project.repository.head_commit.id }
let_it_be(:pipeline) { create(:ci_pipeline, project: project, sha: head_sha) }
@@ -134,7 +134,7 @@ describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
it_behaves_like 'foo/bar directory key'
end
- context 'with directories ending in slash star', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/222356' do
+ context 'with directories ending in slash star' do
let(:files) { ['foo/bar/*'] }
it_behaves_like 'foo/bar directory key'
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build/resource_group_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build/resource_group_spec.rb
index bf6985156d3..8fcc242ba5f 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build/resource_group_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build/resource_group_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Seed::Build::ResourceGroup do
+RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::ResourceGroup do
let_it_be(:project) { create(:project) }
let(:job) { build(:ci_build, project: project) }
let(:seed) { described_class.new(job, resource_group_key) }
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 01f65939da7..2dea554fe56 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Seed::Build do
+RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
let(:project) { create(:project, :repository) }
let(:head_sha) { project.repository.head_commit.id }
let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: head_sha) }
diff --git a/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb
index ceb3cb28bc9..1f38c7aec63 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Seed::Deployment do
+RSpec.describe Gitlab::Ci::Pipeline::Seed::Deployment do
let_it_be(:project, refind: true) { create(:project, :repository) }
let(:pipeline) do
create(:ci_pipeline, project: project,
@@ -102,6 +102,19 @@ describe Gitlab::Ci::Pipeline::Seed::Deployment do
end
end
+ context 'when job has environment attribute with prepare action' do
+ let(:attributes) do
+ {
+ environment: 'production',
+ options: { environment: { name: 'production', action: 'prepare' } }
+ }
+ end
+
+ it 'returns nothing' do
+ is_expected.to be_nil
+ end
+ end
+
context 'when job does not have environment attribute' do
let(:attributes) { { name: 'test' } }
diff --git a/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
index 4c0464e5e7c..0c8a0de2f34 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Seed::Environment do
+RSpec.describe Gitlab::Ci::Pipeline::Seed::Environment do
let_it_be(:project) { create(:project) }
let(:job) { build(:ci_build, project: project) }
let(:seed) { described_class.new(job) }
diff --git a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
index 875fd457bd0..4b9db9fa6c6 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Pipeline::Seed::Stage do
+RSpec.describe Gitlab::Ci::Pipeline::Seed::Stage do
let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:previous_stages) { [] }
diff --git a/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb
index 31a330f46b1..240ede790e0 100644
--- a/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb
+++ b/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Reports::AccessibilityReportsComparer do
+RSpec.describe Gitlab::Ci::Reports::AccessibilityReportsComparer do
let(:comparer) { described_class.new(base_reports, head_reports) }
let(:base_reports) { Gitlab::Ci::Reports::AccessibilityReports.new }
let(:head_reports) { Gitlab::Ci::Reports::AccessibilityReports.new }
diff --git a/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb b/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb
index 0dc13b464b1..8c35b2a34cf 100644
--- a/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb
+++ b/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Reports::AccessibilityReports do
+RSpec.describe Gitlab::Ci::Reports::AccessibilityReports do
let(:accessibility_report) { described_class.new }
let(:url) { 'https://gitlab.com' }
let(:data) do
diff --git a/spec/lib/gitlab/ci/reports/coverage_reports_spec.rb b/spec/lib/gitlab/ci/reports/coverage_reports_spec.rb
index 7cf43ceab32..41ebae863ee 100644
--- a/spec/lib/gitlab/ci/reports/coverage_reports_spec.rb
+++ b/spec/lib/gitlab/ci/reports/coverage_reports_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Reports::CoverageReports do
+RSpec.describe Gitlab::Ci::Reports::CoverageReports do
let(:coverage_report) { described_class.new }
it { expect(coverage_report.files).to eq({}) }
diff --git a/spec/lib/gitlab/ci/reports/terraform_reports_spec.rb b/spec/lib/gitlab/ci/reports/terraform_reports_spec.rb
index bfab30543ed..5e94fe2bb3d 100644
--- a/spec/lib/gitlab/ci/reports/terraform_reports_spec.rb
+++ b/spec/lib/gitlab/ci/reports/terraform_reports_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Reports::TerraformReports do
+RSpec.describe Gitlab::Ci::Reports::TerraformReports do
it 'initializes plans with and empty hash' do
expect(subject.plans).to eq({})
end
diff --git a/spec/lib/gitlab/ci/reports/test_case_spec.rb b/spec/lib/gitlab/ci/reports/test_case_spec.rb
index b5883867983..8882defbd9e 100644
--- a/spec/lib/gitlab/ci/reports/test_case_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_case_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Reports::TestCase do
+RSpec.describe Gitlab::Ci::Reports::TestCase do
describe '#initialize' do
let(:test_case) { described_class.new(params)}
diff --git a/spec/lib/gitlab/ci/reports/test_report_summary_spec.rb b/spec/lib/gitlab/ci/reports/test_report_summary_spec.rb
new file mode 100644
index 00000000000..70d82851125
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/test_report_summary_spec.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::TestReportSummary do
+ let(:build_report_result_1) { build(:ci_build_report_result) }
+ let(:build_report_result_2) { build(:ci_build_report_result, :with_junit_success) }
+ let(:test_report_summary) { described_class.new([build_report_result_1, build_report_result_2]) }
+
+ describe '#total' do
+ subject { test_report_summary.total }
+
+ context 'when test report summary has several build report results' do
+ it 'returns test suite summary object' do
+ expect(subject).to be_a_kind_of(Gitlab::Ci::Reports::TestSuiteSummary)
+ end
+ end
+ end
+
+ describe '#total_time' do
+ subject { test_report_summary.total_time }
+
+ context 'when test report summary has several build report results' do
+ it 'returns the total' do
+ expect(subject).to eq(0.84)
+ end
+ end
+ end
+
+ describe '#total_count' do
+ subject { test_report_summary.total_count }
+
+ context 'when test report summary has several build report results' do
+ it 'returns the total count' do
+ expect(subject).to eq(4)
+ end
+ end
+ end
+
+ describe '#success_count' do
+ subject { test_report_summary.success_count }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the total success' do
+ expect(subject).to eq(2)
+ end
+ end
+ end
+
+ describe '#failed_count' do
+ subject { test_report_summary.failed_count }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the total failed' do
+ expect(subject).to eq(0)
+ end
+ end
+ end
+
+ describe '#error_count' do
+ subject { test_report_summary.error_count }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the total errored' do
+ expect(subject).to eq(2)
+ end
+ end
+ end
+
+ describe '#skipped_count' do
+ subject { test_report_summary.skipped_count }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the total skipped' do
+ expect(subject).to eq(0)
+ end
+ end
+ end
+
+ describe '#test_suites' do
+ subject { test_report_summary.test_suites }
+
+ context 'when test report summary has several build report results' do
+ it 'returns test suites grouped by name' do
+ expect(subject.keys).to eq(["rspec"])
+ expect(subject.keys.size).to eq(1)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb
index d731afe1fff..3483dddca3a 100644
--- a/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Reports::TestReportsComparer do
+RSpec.describe Gitlab::Ci::Reports::TestReportsComparer do
include TestReportsHelper
let(:comparer) { described_class.new(base_reports, head_reports) }
diff --git a/spec/lib/gitlab/ci/reports/test_reports_spec.rb b/spec/lib/gitlab/ci/reports/test_reports_spec.rb
index e51728496e1..502859852f2 100644
--- a/spec/lib/gitlab/ci/reports/test_reports_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_reports_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Reports::TestReports do
+RSpec.describe Gitlab::Ci::Reports::TestReports do
include TestReportsHelper
let(:test_reports) { described_class.new }
diff --git a/spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb
index 2d2179a690b..6bb6771678a 100644
--- a/spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Reports::TestSuiteComparer do
+RSpec.describe Gitlab::Ci::Reports::TestSuiteComparer do
include TestReportsHelper
let(:comparer) { described_class.new(name, base_suite, head_suite) }
diff --git a/spec/lib/gitlab/ci/reports/test_suite_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_spec.rb
index e0b2593353a..c4c4d2c3704 100644
--- a/spec/lib/gitlab/ci/reports/test_suite_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_suite_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Reports::TestSuite do
+RSpec.describe Gitlab::Ci::Reports::TestSuite do
include TestReportsHelper
let(:test_suite) { described_class.new('Rspec') }
@@ -139,6 +139,41 @@ describe Gitlab::Ci::Reports::TestSuite do
end
end
+ describe '#+' do
+ let(:test_suite_2) { described_class.new('Rspec') }
+
+ subject { test_suite + test_suite_2 }
+
+ context 'when adding multiple suites together' do
+ before do
+ test_suite.add_test_case(test_case_success)
+ test_suite.add_test_case(test_case_failed)
+ end
+
+ it 'returns a new test suite' do
+ expect(subject).to be_an_instance_of(described_class)
+ end
+
+ it 'returns the suite name' do
+ expect(subject.name).to eq('Rspec')
+ end
+
+ it 'returns the sum for total_time' do
+ expect(subject.total_time).to eq(3.33)
+ end
+
+ it 'merges tests cases hash', :aggregate_failures do
+ test_suite_2.add_test_case(create_test_case_java_success)
+
+ failed_keys = test_suite.test_cases['failed'].keys
+ success_keys = test_suite.test_cases['success'].keys + test_suite_2.test_cases['success'].keys
+
+ expect(subject.test_cases['failed'].keys).to contain_exactly(*failed_keys)
+ expect(subject.test_cases['success'].keys).to contain_exactly(*success_keys)
+ end
+ end
+ end
+
Gitlab::Ci::Reports::TestCase::STATUS_TYPES.each do |status_type|
describe "##{status_type}" do
subject { test_suite.public_send("#{status_type}") }
diff --git a/spec/lib/gitlab/ci/reports/test_suite_summary_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_summary_spec.rb
new file mode 100644
index 00000000000..12c96acdcf3
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/test_suite_summary_spec.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::TestSuiteSummary do
+ let(:build_report_result_1) { build(:ci_build_report_result) }
+ let(:build_report_result_2) { build(:ci_build_report_result, :with_junit_success) }
+ let(:test_suite_summary) { described_class.new([build_report_result_1, build_report_result_2]) }
+
+ describe '#name' do
+ subject { test_suite_summary.name }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the suite name' do
+ expect(subject).to eq("rspec")
+ end
+ end
+ end
+
+ describe '#build_ids' do
+ subject { test_suite_summary.build_ids }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the build ids' do
+ expect(subject).to contain_exactly(build_report_result_1.build_id, build_report_result_2.build_id)
+ end
+ end
+ end
+
+ describe '#total_time' do
+ subject { test_suite_summary.total_time }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the total time' do
+ expect(subject).to eq(0.84)
+ end
+ end
+ end
+
+ describe '#success_count' do
+ subject { test_suite_summary.success_count }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the total success' do
+ expect(subject).to eq(2)
+ end
+ end
+ end
+
+ describe '#failed_count' do
+ subject { test_suite_summary.failed_count }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the total failed' do
+ expect(subject).to eq(0)
+ end
+ end
+ end
+
+ describe '#error_count' do
+ subject { test_suite_summary.error_count }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the total errored' do
+ expect(subject).to eq(2)
+ end
+ end
+ end
+
+ describe '#skipped_count' do
+ subject { test_suite_summary.skipped_count }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the total skipped' do
+ expect(subject).to eq(0)
+ end
+ end
+ end
+
+ describe '#total_count' do
+ subject { test_suite_summary.total_count }
+
+ context 'when test suite summary has several build report results' do
+ it 'returns the total count' do
+ expect(subject).to eq(4)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/bridge/factory_spec.rb b/spec/lib/gitlab/ci/status/bridge/factory_spec.rb
index 6c67864855d..021b777a0ff 100644
--- a/spec/lib/gitlab/ci/status/bridge/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/bridge/factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Bridge::Factory do
+RSpec.describe Gitlab::Ci::Status::Bridge::Factory do
let(:user) { create(:user) }
let(:project) { bridge.project }
let(:status) { factory.fabricate! }
diff --git a/spec/lib/gitlab/ci/status/build/action_spec.rb b/spec/lib/gitlab/ci/status/build/action_spec.rb
index 3aae7e18d6d..de237183feb 100644
--- a/spec/lib/gitlab/ci/status/build/action_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/action_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Action do
+RSpec.describe Gitlab::Ci::Status::Build::Action do
let(:status) { double('core status') }
let(:user) { double('user') }
diff --git a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
index 3841dae91c7..95920518793 100644
--- a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Cancelable do
+RSpec.describe Gitlab::Ci::Status::Build::Cancelable do
let(:status) { double('core status') }
let(:user) { double('user') }
diff --git a/spec/lib/gitlab/ci/status/build/canceled_spec.rb b/spec/lib/gitlab/ci/status/build/canceled_spec.rb
index 4b43c78f1a7..e30a2211c8f 100644
--- a/spec/lib/gitlab/ci/status/build/canceled_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/canceled_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Canceled do
+RSpec.describe Gitlab::Ci::Status::Build::Canceled do
let(:user) { create(:user) }
subject do
diff --git a/spec/lib/gitlab/ci/status/build/common_spec.rb b/spec/lib/gitlab/ci/status/build/common_spec.rb
index 5114540708f..924ee5ee1a4 100644
--- a/spec/lib/gitlab/ci/status/build/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/common_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Common do
+RSpec.describe Gitlab::Ci::Status::Build::Common do
let(:user) { create(:user) }
let(:build) { create(:ci_build) }
let(:project) { build.project }
diff --git a/spec/lib/gitlab/ci/status/build/created_spec.rb b/spec/lib/gitlab/ci/status/build/created_spec.rb
index 6e3aa442810..49468674140 100644
--- a/spec/lib/gitlab/ci/status/build/created_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/created_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Created do
+RSpec.describe Gitlab::Ci::Status::Build::Created do
let(:user) { create(:user) }
subject do
diff --git a/spec/lib/gitlab/ci/status/build/erased_spec.rb b/spec/lib/gitlab/ci/status/build/erased_spec.rb
index af9c296da0c..75467cc6b31 100644
--- a/spec/lib/gitlab/ci/status/build/erased_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/erased_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Erased do
+RSpec.describe Gitlab::Ci::Status::Build::Erased do
let(:user) { create(:user) }
subject do
diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb
index 11be17bfc53..21eca97331e 100644
--- a/spec/lib/gitlab/ci/status/build/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Factory do
+RSpec.describe Gitlab::Ci::Status::Build::Factory do
let(:user) { create(:user) }
let(:project) { build.project }
let(:status) { factory.fabricate! }
diff --git a/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
index 01500689619..0ff7c3796c0 100644
--- a/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::FailedAllowed do
+RSpec.describe Gitlab::Ci::Status::Build::FailedAllowed do
let(:status) { double('core status') }
let(:user) { double('user') }
let(:build) { create(:ci_build, :failed, :allowed_to_fail) }
diff --git a/spec/lib/gitlab/ci/status/build/failed_spec.rb b/spec/lib/gitlab/ci/status/build/failed_spec.rb
index 78f5214ca81..1d5069fe180 100644
--- a/spec/lib/gitlab/ci/status/build/failed_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/failed_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Failed do
+RSpec.describe Gitlab::Ci::Status::Build::Failed do
let(:build) { create(:ci_build, :script_failure) }
let(:status) { double('core status') }
let(:user) { double('user') }
diff --git a/spec/lib/gitlab/ci/status/build/manual_spec.rb b/spec/lib/gitlab/ci/status/build/manual_spec.rb
index bffe2c10d12..78193055139 100644
--- a/spec/lib/gitlab/ci/status/build/manual_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/manual_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Manual do
+RSpec.describe Gitlab::Ci::Status::Build::Manual do
let(:user) { create(:user) }
subject do
diff --git a/spec/lib/gitlab/ci/status/build/pending_spec.rb b/spec/lib/gitlab/ci/status/build/pending_spec.rb
index 64d57954c15..7b695d33877 100644
--- a/spec/lib/gitlab/ci/status/build/pending_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/pending_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Pending do
+RSpec.describe Gitlab::Ci::Status::Build::Pending do
let(:user) { create(:user) }
subject do
diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb
index bb12a900b55..bb406623d2f 100644
--- a/spec/lib/gitlab/ci/status/build/play_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/play_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Play do
+RSpec.describe Gitlab::Ci::Status::Build::Play do
let(:user) { create(:user) }
let(:project) { create(:project, :stubbed_repository) }
let(:build) { create(:ci_build, :manual, project: project) }
diff --git a/spec/lib/gitlab/ci/status/build/preparing_spec.rb b/spec/lib/gitlab/ci/status/build/preparing_spec.rb
index 4d8945845ba..5bb18ac3799 100644
--- a/spec/lib/gitlab/ci/status/build/preparing_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/preparing_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Preparing do
+RSpec.describe Gitlab::Ci::Status::Build::Preparing do
subject do
described_class.new(double('subject'))
end
diff --git a/spec/lib/gitlab/ci/status/build/retried_spec.rb b/spec/lib/gitlab/ci/status/build/retried_spec.rb
index fce497d40a1..522228f0a1d 100644
--- a/spec/lib/gitlab/ci/status/build/retried_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/retried_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Retried do
+RSpec.describe Gitlab::Ci::Status::Build::Retried do
let(:build) { create(:ci_build, :retried) }
let(:status) { double('core status') }
let(:user) { double('user') }
diff --git a/spec/lib/gitlab/ci/status/build/retryable_spec.rb b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
index 5b0ae315927..cb1cf85169d 100644
--- a/spec/lib/gitlab/ci/status/build/retryable_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Retryable do
+RSpec.describe Gitlab::Ci::Status::Build::Retryable do
let(:status) { double('core status') }
let(:user) { double('user') }
diff --git a/spec/lib/gitlab/ci/status/build/scheduled_spec.rb b/spec/lib/gitlab/ci/status/build/scheduled_spec.rb
index 8f87da10815..b0cd1ac4dc5 100644
--- a/spec/lib/gitlab/ci/status/build/scheduled_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/scheduled_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Scheduled do
+RSpec.describe Gitlab::Ci::Status::Build::Scheduled do
let(:user) { create(:user) }
let(:project) { create(:project, :stubbed_repository) }
let(:build) { create(:ci_build, :scheduled, project: project) }
diff --git a/spec/lib/gitlab/ci/status/build/skipped_spec.rb b/spec/lib/gitlab/ci/status/build/skipped_spec.rb
index 7ce5142da78..0b998a52a57 100644
--- a/spec/lib/gitlab/ci/status/build/skipped_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/skipped_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Skipped do
+RSpec.describe Gitlab::Ci::Status::Build::Skipped do
let(:user) { create(:user) }
subject do
diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb
index d3e98400a53..7376afe0e83 100644
--- a/spec/lib/gitlab/ci/status/build/stop_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Stop do
+RSpec.describe Gitlab::Ci::Status::Build::Stop do
let(:status) { double('core status') }
let(:user) { double('user') }
diff --git a/spec/lib/gitlab/ci/status/build/unschedule_spec.rb b/spec/lib/gitlab/ci/status/build/unschedule_spec.rb
index c18fc3252b4..74002c7d0d7 100644
--- a/spec/lib/gitlab/ci/status/build/unschedule_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/unschedule_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Build::Unschedule do
+RSpec.describe Gitlab::Ci::Status::Build::Unschedule do
let(:status) { double('core status') }
let(:user) { double('user') }
diff --git a/spec/lib/gitlab/ci/status/canceled_spec.rb b/spec/lib/gitlab/ci/status/canceled_spec.rb
index 6cfcea4fdde..a35efae5c57 100644
--- a/spec/lib/gitlab/ci/status/canceled_spec.rb
+++ b/spec/lib/gitlab/ci/status/canceled_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Canceled do
+RSpec.describe Gitlab::Ci::Status::Canceled do
subject do
described_class.new(double('subject'), double('user'))
end
diff --git a/spec/lib/gitlab/ci/status/composite_spec.rb b/spec/lib/gitlab/ci/status/composite_spec.rb
index 8a226b382b0..47bbc4169b6 100644
--- a/spec/lib/gitlab/ci/status/composite_spec.rb
+++ b/spec/lib/gitlab/ci/status/composite_spec.rb
@@ -2,15 +2,15 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Composite do
+RSpec.describe Gitlab::Ci::Status::Composite do
let_it_be(:pipeline) { create(:ci_pipeline) }
before_all do
- @statuses = HasStatus::STATUSES_ENUM.map do |status, idx|
+ @statuses = Ci::HasStatus::STATUSES_ENUM.map do |status, idx|
[status, create(:ci_build, pipeline: pipeline, status: status, importing: true)]
end.to_h
- @statuses_with_allow_failure = HasStatus::STATUSES_ENUM.map do |status, idx|
+ @statuses_with_allow_failure = Ci::HasStatus::STATUSES_ENUM.map do |status, idx|
[status, create(:ci_build, pipeline: pipeline, status: status, allow_failure: true, importing: true)]
end.to_h
end
@@ -26,7 +26,7 @@ describe Gitlab::Ci::Status::Composite do
end
shared_examples 'validate all combinations' do |perms|
- HasStatus::STATUSES_ENUM.keys.combination(perms).each do |statuses|
+ Ci::HasStatus::STATUSES_ENUM.keys.combination(perms).each do |statuses|
context "with #{statuses.join(",")}" do
it_behaves_like 'compares composite with SQL status' do
let(:all_statuses) do
@@ -38,7 +38,7 @@ describe Gitlab::Ci::Status::Composite do
end
end
- HasStatus::STATUSES_ENUM.each do |allow_failure_status, _|
+ Ci::HasStatus::STATUSES_ENUM.each do |allow_failure_status, _|
context "and allow_failure #{allow_failure_status}" do
it_behaves_like 'compares composite with SQL status' do
let(:all_statuses) do
diff --git a/spec/lib/gitlab/ci/status/created_spec.rb b/spec/lib/gitlab/ci/status/created_spec.rb
index aeb41e9cfc3..1ddced923f6 100644
--- a/spec/lib/gitlab/ci/status/created_spec.rb
+++ b/spec/lib/gitlab/ci/status/created_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Created do
+RSpec.describe Gitlab::Ci::Status::Created do
subject do
described_class.new(double('subject'), double('user'))
end
diff --git a/spec/lib/gitlab/ci/status/extended_spec.rb b/spec/lib/gitlab/ci/status/extended_spec.rb
index 8accfc4a2f9..3e1004754ba 100644
--- a/spec/lib/gitlab/ci/status/extended_spec.rb
+++ b/spec/lib/gitlab/ci/status/extended_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Extended do
+RSpec.describe Gitlab::Ci::Status::Extended do
it 'requires subclass to implement matcher' do
expect { described_class.matches?(double, double) }
.to raise_error(NotImplementedError)
diff --git a/spec/lib/gitlab/ci/status/external/common_spec.rb b/spec/lib/gitlab/ci/status/external/common_spec.rb
index 983522fa2d6..6de08ce9281 100644
--- a/spec/lib/gitlab/ci/status/external/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/external/common_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::External::Common do
+RSpec.describe Gitlab::Ci::Status::External::Common do
let(:user) { create(:user) }
let(:project) { external_status.project }
let(:external_target_url) { 'http://example.gitlab.com/status' }
diff --git a/spec/lib/gitlab/ci/status/external/factory_spec.rb b/spec/lib/gitlab/ci/status/external/factory_spec.rb
index 9c11e42fc5a..a31a11cd14a 100644
--- a/spec/lib/gitlab/ci/status/external/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/external/factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::External::Factory do
+RSpec.describe Gitlab::Ci::Status::External::Factory do
let(:user) { create(:user) }
let(:project) { resource.project }
let(:status) { factory.fabricate! }
@@ -14,7 +14,7 @@ describe Gitlab::Ci::Status::External::Factory do
end
context 'when external status has a simple core status' do
- HasStatus::AVAILABLE_STATUSES.each do |simple_status|
+ Ci::HasStatus::AVAILABLE_STATUSES.each do |simple_status|
context "when core status is #{simple_status}" do
let(:resource) do
create(:generic_commit_status, status: simple_status,
diff --git a/spec/lib/gitlab/ci/status/factory_spec.rb b/spec/lib/gitlab/ci/status/factory_spec.rb
index 219eb53d9df..641cb0183d3 100644
--- a/spec/lib/gitlab/ci/status/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/factory_spec.rb
@@ -2,13 +2,13 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Factory do
+RSpec.describe Gitlab::Ci::Status::Factory do
let(:user) { create(:user) }
let(:fabricated_status) { factory.fabricate! }
let(:factory) { described_class.new(resource, user) }
context 'when object has a core status' do
- HasStatus::AVAILABLE_STATUSES.each do |simple_status|
+ Ci::HasStatus::AVAILABLE_STATUSES.each do |simple_status|
context "when simple core status is #{simple_status}" do
let(:resource) { double('resource', status: simple_status) }
diff --git a/spec/lib/gitlab/ci/status/failed_spec.rb b/spec/lib/gitlab/ci/status/failed_spec.rb
index 5c7393fc8cf..e8bd728b740 100644
--- a/spec/lib/gitlab/ci/status/failed_spec.rb
+++ b/spec/lib/gitlab/ci/status/failed_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Failed do
+RSpec.describe Gitlab::Ci::Status::Failed do
subject do
described_class.new(double('subject'), double('user'))
end
diff --git a/spec/lib/gitlab/ci/status/group/common_spec.rb b/spec/lib/gitlab/ci/status/group/common_spec.rb
index 35fff30ea9d..2bfd816e45d 100644
--- a/spec/lib/gitlab/ci/status/group/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/group/common_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Group::Common do
+RSpec.describe Gitlab::Ci::Status::Group::Common do
subject do
Gitlab::Ci::Status::Core.new(double, double)
.extend(described_class)
diff --git a/spec/lib/gitlab/ci/status/group/factory_spec.rb b/spec/lib/gitlab/ci/status/group/factory_spec.rb
index be76a1d5a65..6267b26aa78 100644
--- a/spec/lib/gitlab/ci/status/group/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/group/factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Group::Factory do
+RSpec.describe Gitlab::Ci::Status::Group::Factory do
it 'inherits from the core factory' do
expect(described_class)
.to be < Gitlab::Ci::Status::Factory
diff --git a/spec/lib/gitlab/ci/status/manual_spec.rb b/spec/lib/gitlab/ci/status/manual_spec.rb
index 0839452ec22..a9203438898 100644
--- a/spec/lib/gitlab/ci/status/manual_spec.rb
+++ b/spec/lib/gitlab/ci/status/manual_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Manual do
+RSpec.describe Gitlab::Ci::Status::Manual do
subject do
described_class.new(double('subject'), double('user'))
end
diff --git a/spec/lib/gitlab/ci/status/pending_spec.rb b/spec/lib/gitlab/ci/status/pending_spec.rb
index 5f830e5bb56..0e47b19d9c1 100644
--- a/spec/lib/gitlab/ci/status/pending_spec.rb
+++ b/spec/lib/gitlab/ci/status/pending_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Pending do
+RSpec.describe Gitlab::Ci::Status::Pending do
subject do
described_class.new(double('subject'), double('user'))
end
diff --git a/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb b/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb
index 664915ba552..8fd974972e4 100644
--- a/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Pipeline::Blocked do
+RSpec.describe Gitlab::Ci::Status::Pipeline::Blocked do
let(:pipeline) { double('pipeline') }
subject do
diff --git a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb
index d3251d138b8..575acc48a61 100644
--- a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Pipeline::Common do
+RSpec.describe Gitlab::Ci::Status::Pipeline::Common do
let(:user) { create(:user) }
let(:project) { create(:project, :private) }
let(:pipeline) { create(:ci_pipeline, project: project) }
diff --git a/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb b/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb
index aba403de712..1302c2069ff 100644
--- a/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Pipeline::Delayed do
+RSpec.describe Gitlab::Ci::Status::Pipeline::Delayed do
let(:pipeline) { double('pipeline') }
subject do
diff --git a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
index 838154759cb..a6f23180860 100644
--- a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Pipeline::Factory do
+RSpec.describe Gitlab::Ci::Status::Pipeline::Factory do
let(:user) { create(:user) }
let(:project) { pipeline.project }
let(:status) { factory.fabricate! }
@@ -13,7 +13,7 @@ describe Gitlab::Ci::Status::Pipeline::Factory do
end
context 'when pipeline has a core status' do
- (HasStatus::AVAILABLE_STATUSES - HasStatus::BLOCKED_STATUS).each do |simple_status|
+ (Ci::HasStatus::AVAILABLE_STATUSES - Ci::HasStatus::BLOCKED_STATUS).each do |simple_status|
context "when core status is #{simple_status}" do
let(:pipeline) { create(:ci_pipeline, status: simple_status) }
diff --git a/spec/lib/gitlab/ci/status/preparing_spec.rb b/spec/lib/gitlab/ci/status/preparing_spec.rb
index 33f6bab8d65..6d33eb77560 100644
--- a/spec/lib/gitlab/ci/status/preparing_spec.rb
+++ b/spec/lib/gitlab/ci/status/preparing_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Preparing do
+RSpec.describe Gitlab::Ci::Status::Preparing do
subject do
described_class.new(double('subject'), nil)
end
diff --git a/spec/lib/gitlab/ci/status/running_spec.rb b/spec/lib/gitlab/ci/status/running_spec.rb
index 75ff58c5c98..fbc7bfd81b3 100644
--- a/spec/lib/gitlab/ci/status/running_spec.rb
+++ b/spec/lib/gitlab/ci/status/running_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Running do
+RSpec.describe Gitlab::Ci::Status::Running do
subject do
described_class.new(double('subject'), double('user'))
end
diff --git a/spec/lib/gitlab/ci/status/scheduled_spec.rb b/spec/lib/gitlab/ci/status/scheduled_spec.rb
index a0374e1a87b..4a1dae937ca 100644
--- a/spec/lib/gitlab/ci/status/scheduled_spec.rb
+++ b/spec/lib/gitlab/ci/status/scheduled_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Scheduled do
+RSpec.describe Gitlab::Ci::Status::Scheduled do
subject do
described_class.new(double('subject'), double('user'))
end
diff --git a/spec/lib/gitlab/ci/status/skipped_spec.rb b/spec/lib/gitlab/ci/status/skipped_spec.rb
index 7f68d4a2fa9..f402bbe5221 100644
--- a/spec/lib/gitlab/ci/status/skipped_spec.rb
+++ b/spec/lib/gitlab/ci/status/skipped_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Skipped do
+RSpec.describe Gitlab::Ci::Status::Skipped do
subject do
described_class.new(double('subject'), double('user'))
end
diff --git a/spec/lib/gitlab/ci/status/stage/common_spec.rb b/spec/lib/gitlab/ci/status/stage/common_spec.rb
index 26ff0e901fd..bbd2ce6c83b 100644
--- a/spec/lib/gitlab/ci/status/stage/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/common_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Stage::Common do
+RSpec.describe Gitlab::Ci::Status::Stage::Common do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
index 317756ea13c..e0f5531f370 100644
--- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Stage::Factory do
+RSpec.describe Gitlab::Ci::Status::Stage::Factory do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
@@ -24,7 +24,7 @@ describe Gitlab::Ci::Status::Stage::Factory do
end
context 'when stage has a core status' do
- (HasStatus::AVAILABLE_STATUSES - %w(manual skipped scheduled)).each do |core_status|
+ (Ci::HasStatus::AVAILABLE_STATUSES - %w(manual skipped scheduled)).each do |core_status|
context "when core status is #{core_status}" do
before do
create(:ci_build, pipeline: pipeline, stage: 'test', status: core_status)
@@ -68,7 +68,7 @@ describe Gitlab::Ci::Status::Stage::Factory do
end
context 'when stage has manual builds' do
- (HasStatus::BLOCKED_STATUS + ['skipped']).each do |core_status|
+ (Ci::HasStatus::BLOCKED_STATUS + ['skipped']).each do |core_status|
context "when status is #{core_status}" do
before do
create(:ci_build, pipeline: pipeline, stage: 'test', status: core_status)
diff --git a/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb b/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb
index bdcbfed918f..25b79ff2099 100644
--- a/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Stage::PlayManual do
+RSpec.describe Gitlab::Ci::Status::Stage::PlayManual do
let(:stage) { double('stage') }
let(:play_manual) { described_class.new(stage) }
diff --git a/spec/lib/gitlab/ci/status/success_spec.rb b/spec/lib/gitlab/ci/status/success_spec.rb
index d4b3a9f12cc..2d1c50448d4 100644
--- a/spec/lib/gitlab/ci/status/success_spec.rb
+++ b/spec/lib/gitlab/ci/status/success_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::Success do
+RSpec.describe Gitlab::Ci::Status::Success do
subject do
described_class.new(double('subject'), double('user'))
end
diff --git a/spec/lib/gitlab/ci/status/success_warning_spec.rb b/spec/lib/gitlab/ci/status/success_warning_spec.rb
index af952011e21..86b826ad272 100644
--- a/spec/lib/gitlab/ci/status/success_warning_spec.rb
+++ b/spec/lib/gitlab/ci/status/success_warning_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::SuccessWarning do
+RSpec.describe Gitlab::Ci::Status::SuccessWarning do
let(:status) { double('status') }
subject do
diff --git a/spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb b/spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb
index ed00dac8560..de18198c6c2 100644
--- a/spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb
+++ b/spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Status::WaitingForResource do
+RSpec.describe Gitlab::Ci::Status::WaitingForResource do
subject do
described_class.new(double('subject'), double('user'))
end
diff --git a/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb
index b2a9e3f5cf4..4f8faa5ddb1 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/build_gitlab_ci_yaml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Jobs/Build.gitlab-ci.yml' do
+RSpec.describe 'Jobs/Build.gitlab-ci.yml' do
subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Jobs/Build') }
describe 'the created pipeline' do
diff --git a/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb
index 9c5b2fd5099..e685ad3b46e 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Jobs/Code-Quality.gitlab-ci.yml' do
+RSpec.describe 'Jobs/Code-Quality.gitlab-ci.yml' do
subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Jobs/Code-Quality') }
describe 'the created pipeline' do
diff --git a/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb
index a6ae23c85d3..ea9bd5bd02c 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Jobs/Deploy.gitlab-ci.yml' do
+RSpec.describe 'Jobs/Deploy.gitlab-ci.yml' do
subject(:template) do
<<~YAML
stages:
diff --git a/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb
index 2186bf038eb..f475785be98 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Jobs/Test.gitlab-ci.yml' do
+RSpec.describe 'Jobs/Test.gitlab-ci.yml' do
subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Jobs/Test') }
describe 'the created pipeline' do
diff --git a/spec/lib/gitlab/ci/templates/Jobs/browser_performance_testing_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb
index 54c3500b0a0..9711df55226 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/browser_performance_testing_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Verify/load_performance_testing_gitlab_ci_yaml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Jobs/Browser-Performance-Testing.gitlab-ci.yml' do
+RSpec.describe 'Verify/Load-Performance-Testing.gitlab-ci.yml' do
subject(:template) do
<<~YAML
stages:
@@ -10,7 +10,7 @@ describe 'Jobs/Browser-Performance-Testing.gitlab-ci.yml' do
- performance
include:
- - template: 'Jobs/Browser-Performance-Testing.gitlab-ci.yml'
+ - template: 'Verify/Load-Performance-Testing.gitlab-ci.yml'
placeholder:
script:
@@ -20,11 +20,7 @@ describe 'Jobs/Browser-Performance-Testing.gitlab-ci.yml' do
describe 'the created pipeline' do
let(:user) { create(:admin) }
- let(:project) do
- create(:project, :repository, variables: [
- build(:ci_variable, key: 'CI_KUBERNETES_ACTIVE', value: 'true')
- ])
- end
+ let(:project) { create(:project, :repository) }
let(:default_branch) { 'master' }
let(:pipeline_ref) { default_branch }
@@ -43,32 +39,26 @@ describe 'Jobs/Browser-Performance-Testing.gitlab-ci.yml' do
expect(pipeline.errors).to be_empty
end
- shared_examples_for 'performance job on tag or branch' do
+ shared_examples_for 'load_performance job on tag or branch' do
it 'by default' do
- expect(build_names).to include('performance')
- end
-
- it 'when PERFORMANCE_DISABLED' do
- create(:ci_variable, project: project, key: 'PERFORMANCE_DISABLED', value: '1')
-
- expect(build_names).not_to include('performance')
+ expect(build_names).to include('load_performance')
end
end
context 'on master' do
- it_behaves_like 'performance job on tag or branch'
+ it_behaves_like 'load_performance job on tag or branch'
end
context 'on another branch' do
let(:pipeline_ref) { 'feature' }
- it_behaves_like 'performance job on tag or branch'
+ it_behaves_like 'load_performance job on tag or branch'
end
context 'on tag' do
let(:pipeline_ref) { 'v1.0.0' }
- it_behaves_like 'performance job on tag or branch'
+ it_behaves_like 'load_performance job on tag or branch'
end
context 'on merge request' do
diff --git a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
index af6ec25b9d6..4d90e7ca9e6 100644
--- a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Auto-DevOps.gitlab-ci.yml' do
+RSpec.describe 'Auto-DevOps.gitlab-ci.yml' do
subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps') }
describe 'the created pipeline' do
@@ -37,6 +37,7 @@ describe 'Auto-DevOps.gitlab-ci.yml' do
context 'when the project is set for deployment to AWS' do
let(:platform_value) { 'ECS' }
+ let(:review_prod_build_names) { build_names.select {|n| n.include?('review') || n.include?('production')} }
before do
create(:ci_variable, project: project, key: 'AUTO_DEVOPS_PLATFORM_TARGET', value: platform_value)
@@ -67,8 +68,15 @@ describe 'Auto-DevOps.gitlab-ci.yml' do
end
it 'creates an ECS deployment job for production only' do
- expect(build_names).not_to include('review_ecs')
- expect(build_names).to include('production_ecs')
+ expect(review_prod_build_names).to contain_exactly('production_ecs')
+ end
+
+ context 'with FARGATE as a launch type' do
+ let(:platform_value) { 'FARGATE' }
+
+ it 'creates a FARGATE deployment job for production only' do
+ expect(review_prod_build_names).to contain_exactly('production_fargate')
+ end
end
context 'and we are not on the default branch' do
@@ -79,15 +87,22 @@ describe 'Auto-DevOps.gitlab-ci.yml' do
project.repository.create_branch(pipeline_branch)
end
- it_behaves_like 'no ECS job when AUTO_DEVOPS_PLATFORM_TARGET is not present' do
- let(:job_name) { 'review_ecs' }
+ %w(review_ecs review_fargate).each do |job|
+ it_behaves_like 'no ECS job when AUTO_DEVOPS_PLATFORM_TARGET is not present' do
+ let(:job_name) { job }
+ end
end
it 'creates an ECS deployment job for review only' do
- expect(build_names).to include('review_ecs')
- expect(build_names).not_to include('production_ecs')
- expect(build_names).not_to include('review')
- expect(build_names).not_to include('production')
+ expect(review_prod_build_names).to contain_exactly('review_ecs')
+ end
+
+ context 'with FARGATE as a launch type' do
+ let(:platform_value) { 'FARGATE' }
+
+ it 'creates an FARGATE deployment job for review only' do
+ expect(review_prod_build_names).to contain_exactly('review_fargate')
+ end
end
end
@@ -190,6 +205,7 @@ describe 'Auto-DevOps.gitlab-ci.yml' do
'Buildpack' | { 'README.md' => '' } | { 'BUILDPACK_URL' => 'http://example.com' } | %w(build test) | %w()
'Explicit set' | { 'README.md' => '' } | { 'AUTO_DEVOPS_EXPLICITLY_ENABLED' => '1' } | %w(build test) | %w()
'Explicit unset' | { 'README.md' => '' } | { 'AUTO_DEVOPS_EXPLICITLY_ENABLED' => '0' } | %w() | %w(build test)
+ 'DOCKERFILE_PATH' | { 'README.md' => '' } | { 'DOCKERFILE_PATH' => 'Docker.file' } | %w(build test) | %w()
'Dockerfile' | { 'Dockerfile' => '' } | {} | %w(build test) | %w()
'Clojure' | { 'project.clj' => '' } | {} | %w(build test) | %w()
'Go modules' | { 'go.mod' => '' } | {} | %w(build test) | %w()
diff --git a/spec/lib/gitlab/ci/templates/managed_cluster_applications_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/managed_cluster_applications_gitlab_ci_yaml_spec.rb
index 2a6314755ef..0e458e01a2c 100644
--- a/spec/lib/gitlab/ci/templates/managed_cluster_applications_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/managed_cluster_applications_gitlab_ci_yaml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Managed-Cluster-Applications.gitlab-ci.yml' do
+RSpec.describe 'Managed-Cluster-Applications.gitlab-ci.yml' do
subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Managed-Cluster-Applications') }
describe 'the created pipeline' do
diff --git a/spec/lib/gitlab/ci/templates/templates_spec.rb b/spec/lib/gitlab/ci/templates/templates_spec.rb
index bc3d5b89220..def4d1b3bf6 100644
--- a/spec/lib/gitlab/ci/templates/templates_spec.rb
+++ b/spec/lib/gitlab/ci/templates/templates_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'CI YML Templates' do
+RSpec.describe 'CI YML Templates' do
subject { Gitlab::Ci::YamlProcessor.new(content) }
let(:all_templates) { Gitlab::Template::GitlabCiYmlTemplate.all.map(&:full_name) }
diff --git a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
index 92b3e5562a9..a2903391c6f 100644
--- a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
+++ b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do
include ChunkedIOHelpers
let_it_be(:build) { create(:ci_build, :running) }
diff --git a/spec/lib/gitlab/ci/trace/section_parser_spec.rb b/spec/lib/gitlab/ci/trace/section_parser_spec.rb
index 24ce4d34411..b6bb52a5c06 100644
--- a/spec/lib/gitlab/ci/trace/section_parser_spec.rb
+++ b/spec/lib/gitlab/ci/trace/section_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Trace::SectionParser do
+RSpec.describe Gitlab::Ci::Trace::SectionParser do
def lines_with_pos(text)
pos = 0
StringIO.new(text).each_line do |line|
diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb
index ea79073840d..568c10bbac2 100644
--- a/spec/lib/gitlab/ci/trace/stream_spec.rb
+++ b/spec/lib/gitlab/ci/trace/stream_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
let_it_be(:build) { create(:ci_build, :running) }
before do
diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb
index 574c2b73722..85edf27d3e7 100644
--- a/spec/lib/gitlab/ci/trace_spec.rb
+++ b/spec/lib/gitlab/ci/trace_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state do
let(:build) { create(:ci_build) }
let(:trace) { described_class.new(build) }
diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
index 1bdca753cd3..eba2f29836d 100644
--- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Variables::Collection::Item do
+RSpec.describe Gitlab::Ci::Variables::Collection::Item do
let(:variable_key) { 'VAR' }
let(:variable_value) { 'something' }
let(:expected_value) { variable_value }
diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb
index 59b9f7d4fb9..ac84313ad9f 100644
--- a/spec/lib/gitlab/ci/variables/collection_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::Variables::Collection do
+RSpec.describe Gitlab::Ci::Variables::Collection do
describe '.new' do
it 'can be initialized with an array' do
variable = { key: 'VAR', value: 'value', public: true, masked: false }
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 1668149d8f5..5c6d748d66c 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
module Gitlab
module Ci
- describe YamlProcessor do
+ RSpec.describe YamlProcessor do
include StubRequests
subject { described_class.new(config, user: nil) }
@@ -435,6 +435,153 @@ module Gitlab
end
end
+ describe '#warnings' do
+ before do
+ stub_feature_flags(ci_raise_job_rules_without_workflow_rules_warning: true)
+ end
+
+ context 'when a warning is raised in a given entry' do
+ let(:config) do
+ <<-EOYML
+ rspec:
+ script: rspec
+ rules:
+ - if: '$VAR == "value"'
+ EOYML
+ end
+
+ it 'is propagated all the way up to the processor' do
+ expect(subject.warnings).to contain_exactly('jobs:rspec uses `rules` without defining `workflow:rules`')
+ end
+ end
+
+ context 'when a warning is raised together with errors' do
+ let(:config) do
+ <<-EOYML
+ rspec:
+ script: rspec
+ rules:
+ - if: '$VAR == "value"'
+ invalid:
+ script: echo
+ artifacts:
+ - wrong_key: value
+ EOYML
+ end
+
+ it 'is propagated all the way up into the raised exception' do
+ expect { subject }.to raise_error do |error|
+ expect(error).to be_a(described_class::ValidationError)
+ expect(error.message).to eq('jobs:invalid:artifacts config should be a hash')
+ expect(error.warnings).to contain_exactly('jobs:rspec uses `rules` without defining `workflow:rules`')
+ end
+ end
+ end
+
+ context 'when error is raised before composing the config' do
+ let(:config) do
+ <<-EOYML
+ include: unknown/file.yml
+ rspec:
+ script: rspec
+ rules:
+ - if: '$VAR == "value"'
+ EOYML
+ end
+
+ it 'raises an exception with empty warnings array' do
+ expect { subject }.to raise_error do |error|
+ expect(error).to be_a(described_class::ValidationError)
+ expect(error.message).to eq('Local file `unknown/file.yml` does not have project!')
+ expect(error.warnings).to be_empty
+ end
+ end
+ end
+
+ context 'when error is raised after composing the config with warnings' do
+ shared_examples 'has warnings and expected error' do |error_message|
+ it 'raises an exception including warnings' do
+ expect { subject }.to raise_error do |error|
+ expect(error).to be_a(described_class::ValidationError)
+ expect(error.message).to match(error_message)
+ expect(error.warnings).to be_present
+ end
+ end
+ end
+
+ context 'when stage does not exist' do
+ let(:config) do
+ <<-EOYML
+ rspec:
+ stage: custom_stage
+ script: rspec
+ rules:
+ - if: '$VAR == "value"'
+ EOYML
+ end
+
+ it_behaves_like 'has warnings and expected error', /rspec job: chosen stage does not exist/
+ end
+
+ context 'job dependency does not exist' do
+ let(:config) do
+ <<-EOYML
+ build:
+ stage: build
+ script: echo
+ rules:
+ - if: '$VAR == "value"'
+ test:
+ stage: test
+ script: echo
+ needs: [unknown_job]
+ EOYML
+ end
+
+ it_behaves_like 'has warnings and expected error', /test job: undefined need: unknown_job/
+ end
+
+ context 'job dependency defined in later stage' do
+ let(:config) do
+ <<-EOYML
+ build:
+ stage: build
+ script: echo
+ needs: [test]
+ rules:
+ - if: '$VAR == "value"'
+ test:
+ stage: test
+ script: echo
+ EOYML
+ end
+
+ it_behaves_like 'has warnings and expected error', /build job: need test is not defined in prior stages/
+ end
+ end
+
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(ci_raise_job_rules_without_workflow_rules_warning: false)
+ end
+
+ context 'job rules used without workflow rules' do
+ let(:config) do
+ <<-EOYML
+ rspec:
+ script: rspec
+ rules:
+ - if: '$VAR == "value"'
+ EOYML
+ end
+
+ it 'does not raise the warning' do
+ expect(subject.warnings).to be_empty
+ end
+ end
+ end
+ end
+
describe 'only / except policies validations' do
context 'when `only` has an invalid value' do
let(:config) { { rspec: { script: "rspec", type: "test", only: only } } }
@@ -1397,6 +1544,9 @@ module Gitlab
tag_name: "$CI_COMMIT_TAG",
name: "Release $CI_TAG_NAME",
description: "./release_changelog.txt",
+ ref: 'b3235930aa443112e639f941c69c578912189bdd',
+ released_at: '2019-03-15T08:00:00Z',
+ milestones: %w[m1 m2 m3],
assets: {
links: [
{
@@ -2514,7 +2664,7 @@ module Gitlab
it 'returns errors and empty configuration' do
expect(subject.valid?).to eq(false)
expect(subject.errors).to eq(['Invalid configuration format'])
- expect(subject.content).to be_blank
+ expect(subject.config).to be_blank
end
end
@@ -2524,7 +2674,7 @@ module Gitlab
it 'returns errors and empty configuration' do
expect(subject.valid?).to eq(false)
expect(subject.errors).to eq(['jobs:rspec:tags config should be an array of strings'])
- expect(subject.content).to be_blank
+ expect(subject.config).to be_blank
end
end
@@ -2536,7 +2686,7 @@ module Gitlab
expect(subject.errors).to contain_exactly(
'jobs:rspec config contains unknown keys: bad_tags',
'jobs:rspec rules should be an array of hashes')
- expect(subject.content).to be_blank
+ expect(subject.config).to be_blank
end
end
@@ -2546,7 +2696,7 @@ module Gitlab
it 'returns errors and empty configuration' do
expect(subject.valid?).to eq(false)
expect(subject.errors).to eq(['Please provide content of .gitlab-ci.yml'])
- expect(subject.content).to be_blank
+ expect(subject.config).to be_blank
end
end
@@ -2556,7 +2706,7 @@ module Gitlab
it 'returns errors and empty configuration' do
expect(subject.valid?).to eq(false)
expect(subject.errors).to eq(['Unknown alias: bad_alias'])
- expect(subject.content).to be_blank
+ expect(subject.config).to be_blank
end
end
@@ -2566,7 +2716,7 @@ module Gitlab
it 'returns errors and empty configuration' do
expect(subject.valid?).to eq(true)
expect(subject.errors).to be_empty
- expect(subject.content).to be_present
+ expect(subject.config).to be_present
end
end
end
diff --git a/spec/lib/gitlab/ci_access_spec.rb b/spec/lib/gitlab/ci_access_spec.rb
index 3c68d209eb6..9b573c6eb7a 100644
--- a/spec/lib/gitlab/ci_access_spec.rb
+++ b/spec/lib/gitlab/ci_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CiAccess do
+RSpec.describe Gitlab::CiAccess do
let(:access) { described_class.new }
describe '#can_do_action?' do
diff --git a/spec/lib/gitlab/class_attributes_spec.rb b/spec/lib/gitlab/class_attributes_spec.rb
new file mode 100644
index 00000000000..f8766f20495
--- /dev/null
+++ b/spec/lib/gitlab/class_attributes_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::ClassAttributes do
+ let(:klass) do
+ Class.new do
+ include Gitlab::ClassAttributes
+
+ def self.get_attribute(name)
+ get_class_attribute(name)
+ end
+
+ def self.set_attribute(name, value)
+ class_attributes[name] = value
+ end
+ end
+ end
+
+ let(:subclass) { Class.new(klass) }
+
+ describe ".get_class_attribute" do
+ it "returns values set on the class" do
+ klass.set_attribute(:foo, :bar)
+
+ expect(klass.get_attribute(:foo)).to eq(:bar)
+ end
+
+ it "returns values set on a superclass" do
+ klass.set_attribute(:foo, :bar)
+
+ expect(subclass.get_attribute(:foo)).to eq(:bar)
+ end
+
+ it "returns values from the subclass over attributes from a superclass" do
+ klass.set_attribute(:foo, :baz)
+ subclass.set_attribute(:foo, :bar)
+
+ expect(subclass.get_attribute(:foo)).to eq(:bar)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb
index 4d8edfeac80..d03d4f64a0f 100644
--- a/spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb
+++ b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Cleanup::OrphanJobArtifactFilesBatch do
+RSpec.describe Gitlab::Cleanup::OrphanJobArtifactFilesBatch do
let(:batch_size) { 10 }
let(:dry_run) { true }
diff --git a/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb
index 966c4c8b2fb..8a7425a4156 100644
--- a/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb
+++ b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Cleanup::OrphanJobArtifactFiles do
+RSpec.describe Gitlab::Cleanup::OrphanJobArtifactFiles do
let(:null_logger) { Logger.new('/dev/null') }
subject(:cleanup) { described_class.new(logger: null_logger) }
diff --git a/spec/lib/gitlab/cleanup/orphan_lfs_file_references_spec.rb b/spec/lib/gitlab/cleanup/orphan_lfs_file_references_spec.rb
index 01cc0b30784..47b2cf5dc4a 100644
--- a/spec/lib/gitlab/cleanup/orphan_lfs_file_references_spec.rb
+++ b/spec/lib/gitlab/cleanup/orphan_lfs_file_references_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Cleanup::OrphanLfsFileReferences do
+RSpec.describe Gitlab::Cleanup::OrphanLfsFileReferences do
let(:null_logger) { Logger.new('/dev/null') }
let(:project) { create(:project, :repository, lfs_enabled: true) }
let(:lfs_object) { create(:lfs_object) }
diff --git a/spec/lib/gitlab/cleanup/project_uploads_spec.rb b/spec/lib/gitlab/cleanup/project_uploads_spec.rb
index d1e3a73686e..05d744d95e2 100644
--- a/spec/lib/gitlab/cleanup/project_uploads_spec.rb
+++ b/spec/lib/gitlab/cleanup/project_uploads_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Cleanup::ProjectUploads do
+RSpec.describe Gitlab::Cleanup::ProjectUploads do
subject { described_class.new(logger: logger) }
let(:logger) { double(:logger) }
diff --git a/spec/lib/gitlab/cleanup/remote_uploads_spec.rb b/spec/lib/gitlab/cleanup/remote_uploads_spec.rb
index 35642cd6e50..1752608f844 100644
--- a/spec/lib/gitlab/cleanup/remote_uploads_spec.rb
+++ b/spec/lib/gitlab/cleanup/remote_uploads_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::Cleanup::RemoteUploads do
+RSpec.describe Gitlab::Cleanup::RemoteUploads do
context 'when object_storage is enabled' do
let(:connection) { double }
let(:directory) { double }
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index 4e1bf2840dc..f2bc6390032 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ClosingIssueExtractor do
+RSpec.describe Gitlab::ClosingIssueExtractor do
let(:project) { create(:project) }
let(:project2) { create(:project) }
let(:forked_project) { Projects::ForkService.new(project, project2.creator).execute }
diff --git a/spec/lib/gitlab/cluster/mixins/puma_cluster_spec.rb b/spec/lib/gitlab/cluster/mixins/puma_cluster_spec.rb
index 864529a6bf6..5b69b34d04b 100644
--- a/spec/lib/gitlab/cluster/mixins/puma_cluster_spec.rb
+++ b/spec/lib/gitlab/cluster/mixins/puma_cluster_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
# For easier debugging set `PUMA_DEBUG=1`
-describe Gitlab::Cluster::Mixins::PumaCluster do
+RSpec.describe Gitlab::Cluster::Mixins::PumaCluster do
before do
stub_const('PUMA_STARTUP_TIMEOUT', 30)
end
diff --git a/spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb b/spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb
index 3965eb722a0..0aaca0a79c2 100644
--- a/spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb
+++ b/spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
# For easier debugging set `UNICORN_DEBUG=1`
-describe Gitlab::Cluster::Mixins::UnicornHttpServer do
+RSpec.describe Gitlab::Cluster::Mixins::UnicornHttpServer do
before do
stub_const('UNICORN_STARTUP_TIMEOUT', 30)
end
diff --git a/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb b/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb
index 6ed9dda08d7..948de161235 100644
--- a/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb
+++ b/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Cluster::PumaWorkerKillerObserver do
+RSpec.describe Gitlab::Cluster::PumaWorkerKillerObserver do
let(:counter) { Gitlab::Metrics::NullMetric.instance }
before do
diff --git a/spec/lib/gitlab/cluster/rack_timeout_observer_spec.rb b/spec/lib/gitlab/cluster/rack_timeout_observer_spec.rb
index 68e5435450c..05df4089075 100644
--- a/spec/lib/gitlab/cluster/rack_timeout_observer_spec.rb
+++ b/spec/lib/gitlab/cluster/rack_timeout_observer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Cluster::RackTimeoutObserver do
+RSpec.describe Gitlab::Cluster::RackTimeoutObserver do
let(:counter) { Gitlab::Metrics::NullMetric.instance }
before do
diff --git a/spec/lib/gitlab/code_navigation_path_spec.rb b/spec/lib/gitlab/code_navigation_path_spec.rb
index 07d4dfba622..4dc864b158d 100644
--- a/spec/lib/gitlab/code_navigation_path_spec.rb
+++ b/spec/lib/gitlab/code_navigation_path_spec.rb
@@ -2,10 +2,10 @@
require 'spec_helper'
-describe Gitlab::CodeNavigationPath do
+RSpec.describe Gitlab::CodeNavigationPath do
context 'when there is an artifact with code navigation data' do
let_it_be(:project) { create(:project, :repository) }
- let_it_be(:sha) { project.repository.commits('master', limit: 5).last.id }
+ let_it_be(:sha) { project.repository.commits('master', limit: Gitlab::CodeNavigationPath::LATEST_COMMITS_LIMIT).last.id }
let_it_be(:pipeline) { create(:ci_pipeline, project: project, sha: sha) }
let_it_be(:job) { create(:ci_build, pipeline: pipeline) }
let_it_be(:artifact) { create(:ci_job_artifact, :lsif, job: job) }
diff --git a/spec/lib/gitlab/color_schemes_spec.rb b/spec/lib/gitlab/color_schemes_spec.rb
index ba5573f6901..fd9fccc2bf7 100644
--- a/spec/lib/gitlab/color_schemes_spec.rb
+++ b/spec/lib/gitlab/color_schemes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ColorSchemes do
+RSpec.describe Gitlab::ColorSchemes do
describe '.body_classes' do
it 'returns a space-separated list of class names' do
css = described_class.body_classes
diff --git a/spec/lib/gitlab/conan_token_spec.rb b/spec/lib/gitlab/conan_token_spec.rb
new file mode 100644
index 00000000000..b17f2eaa8d8
--- /dev/null
+++ b/spec/lib/gitlab/conan_token_spec.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Gitlab::ConanToken do
+ let(:base_secret) { SecureRandom.base64(64) }
+
+ let(:jwt_secret) do
+ OpenSSL::HMAC.hexdigest(
+ OpenSSL::Digest::SHA256.new,
+ base_secret,
+ described_class::HMAC_KEY
+ )
+ end
+
+ before do
+ allow(Settings).to receive(:attr_encrypted_db_key_base).and_return(base_secret)
+ end
+
+ def build_jwt(access_token_id:, user_id:, expire_time: nil)
+ JSONWebToken::HMACToken.new(jwt_secret).tap do |jwt|
+ jwt['access_token'] = access_token_id
+ jwt['user_id'] = user_id || user_id
+ jwt.expire_time = expire_time || jwt.issued_at + 1.hour
+ end
+ end
+
+ describe '.from_personal_access_token' do
+ it 'sets access token id and user id' do
+ access_token = double(id: 123, user_id: 456)
+
+ token = described_class.from_personal_access_token(access_token)
+
+ expect(token.access_token_id).to eq(123)
+ expect(token.user_id).to eq(456)
+ end
+ end
+
+ describe '.from_job' do
+ it 'sets access token id and user id' do
+ user = double(id: 456)
+ job = double(token: 123, user: user)
+
+ token = described_class.from_job(job)
+
+ expect(token.access_token_id).to eq(123)
+ expect(token.user_id).to eq(456)
+ end
+ end
+
+ describe '.from_deploy_token' do
+ it 'sets access token id and user id' do
+ deploy_token = double(token: '123', username: 'bob')
+
+ token = described_class.from_deploy_token(deploy_token)
+
+ expect(token.access_token_id).to eq('123')
+ expect(token.user_id).to eq('bob')
+ end
+ end
+
+ describe '.decode' do
+ it 'sets access token id and user id' do
+ jwt = build_jwt(access_token_id: 123, user_id: 456)
+
+ token = described_class.decode(jwt.encoded)
+
+ expect(token.access_token_id).to eq(123)
+ expect(token.user_id).to eq(456)
+ end
+
+ it 'returns nil for invalid JWT' do
+ expect(described_class.decode('invalid-jwt')).to be_nil
+ end
+
+ it 'returns nil for expired JWT' do
+ jwt = build_jwt(access_token_id: 123,
+ user_id: 456,
+ expire_time: Time.zone.now - 2.hours)
+
+ expect(described_class.decode(jwt.encoded)).to be_nil
+ end
+ end
+
+ describe '#to_jwt' do
+ it 'returns the encoded JWT' do
+ allow(SecureRandom).to receive(:uuid).and_return('u-u-i-d')
+
+ Timecop.freeze do
+ jwt = build_jwt(access_token_id: 123, user_id: 456)
+
+ token = described_class.new(access_token_id: 123, user_id: 456)
+
+ expect(token.to_jwt).to eq(jwt.encoded)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/config/entry/attributable_spec.rb b/spec/lib/gitlab/config/entry/attributable_spec.rb
index 64a4670f483..1e7880ed898 100644
--- a/spec/lib/gitlab/config/entry/attributable_spec.rb
+++ b/spec/lib/gitlab/config/entry/attributable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Config::Entry::Attributable do
+RSpec.describe Gitlab::Config::Entry::Attributable do
let(:node) do
Class.new do
include Gitlab::Config::Entry::Attributable
diff --git a/spec/lib/gitlab/config/entry/boolean_spec.rb b/spec/lib/gitlab/config/entry/boolean_spec.rb
index 0b8b720dd80..86cd92e22d7 100644
--- a/spec/lib/gitlab/config/entry/boolean_spec.rb
+++ b/spec/lib/gitlab/config/entry/boolean_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Config::Entry::Boolean do
+RSpec.describe Gitlab::Config::Entry::Boolean do
let(:entry) { described_class.new(config) }
describe 'validations' do
diff --git a/spec/lib/gitlab/config/entry/configurable_spec.rb b/spec/lib/gitlab/config/entry/configurable_spec.rb
index 8c3a4490d08..c72efa66024 100644
--- a/spec/lib/gitlab/config/entry/configurable_spec.rb
+++ b/spec/lib/gitlab/config/entry/configurable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Config::Entry::Configurable do
+RSpec.describe Gitlab::Config::Entry::Configurable do
let(:entry) do
Class.new(Gitlab::Config::Entry::Node) do
include Gitlab::Config::Entry::Configurable
diff --git a/spec/lib/gitlab/config/entry/factory_spec.rb b/spec/lib/gitlab/config/entry/factory_spec.rb
index 81ca5f2cba1..a00c45169ef 100644
--- a/spec/lib/gitlab/config/entry/factory_spec.rb
+++ b/spec/lib/gitlab/config/entry/factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Config::Entry::Factory do
+RSpec.describe Gitlab::Config::Entry::Factory do
describe '#create!' do
before do
stub_const('Script', Class.new(Gitlab::Config::Entry::Node))
diff --git a/spec/lib/gitlab/config/entry/simplifiable_spec.rb b/spec/lib/gitlab/config/entry/simplifiable_spec.rb
index 5c208cab449..2011587a342 100644
--- a/spec/lib/gitlab/config/entry/simplifiable_spec.rb
+++ b/spec/lib/gitlab/config/entry/simplifiable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Config::Entry::Simplifiable do
+RSpec.describe Gitlab::Config::Entry::Simplifiable do
describe '.strategy' do
let(:entry) do
Class.new(described_class) do
diff --git a/spec/lib/gitlab/config/entry/undefined_spec.rb b/spec/lib/gitlab/config/entry/undefined_spec.rb
index 83c3a6aec72..36faabd8e31 100644
--- a/spec/lib/gitlab/config/entry/undefined_spec.rb
+++ b/spec/lib/gitlab/config/entry/undefined_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Config::Entry::Undefined do
+RSpec.describe Gitlab::Config::Entry::Undefined do
let(:entry) { described_class.new }
describe '#leaf?' do
diff --git a/spec/lib/gitlab/config/entry/unspecified_spec.rb b/spec/lib/gitlab/config/entry/unspecified_spec.rb
index 32c52594ecf..35ba992f62a 100644
--- a/spec/lib/gitlab/config/entry/unspecified_spec.rb
+++ b/spec/lib/gitlab/config/entry/unspecified_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Config::Entry::Unspecified do
+RSpec.describe Gitlab::Config::Entry::Unspecified do
let(:unspecified) { described_class.new(entry) }
let(:entry) { spy('Entry') }
diff --git a/spec/lib/gitlab/config/entry/validatable_spec.rb b/spec/lib/gitlab/config/entry/validatable_spec.rb
index 925db3594ba..5e66de60367 100644
--- a/spec/lib/gitlab/config/entry/validatable_spec.rb
+++ b/spec/lib/gitlab/config/entry/validatable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Config::Entry::Validatable do
+RSpec.describe Gitlab::Config::Entry::Validatable do
let(:entry) do
Class.new(Gitlab::Config::Entry::Node) do
include Gitlab::Config::Entry::Validatable
diff --git a/spec/lib/gitlab/config/entry/validator_spec.rb b/spec/lib/gitlab/config/entry/validator_spec.rb
index 7bf350912df..d41f7f794ec 100644
--- a/spec/lib/gitlab/config/entry/validator_spec.rb
+++ b/spec/lib/gitlab/config/entry/validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Config::Entry::Validator do
+RSpec.describe Gitlab::Config::Entry::Validator do
let(:validator) { Class.new(described_class) }
let(:validator_instance) { validator.new(node) }
let(:node) { spy('node') }
diff --git a/spec/lib/gitlab/config/loader/yaml_spec.rb b/spec/lib/gitlab/config/loader/yaml_spec.rb
index 623fe927233..731ee12d7f4 100644
--- a/spec/lib/gitlab/config/loader/yaml_spec.rb
+++ b/spec/lib/gitlab/config/loader/yaml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Config::Loader::Yaml do
+RSpec.describe Gitlab::Config::Loader::Yaml do
let(:loader) { described_class.new(yml) }
let(:yml) do
diff --git a/spec/lib/gitlab/config_checker/external_database_checker_spec.rb b/spec/lib/gitlab/config_checker/external_database_checker_spec.rb
index d86d132c237..316696bc584 100644
--- a/spec/lib/gitlab/config_checker/external_database_checker_spec.rb
+++ b/spec/lib/gitlab/config_checker/external_database_checker_spec.rb
@@ -2,55 +2,58 @@
require 'spec_helper'
-describe Gitlab::ConfigChecker::ExternalDatabaseChecker do
+RSpec.describe Gitlab::ConfigChecker::ExternalDatabaseChecker do
describe '#check' do
subject { described_class.check }
- context 'database version is not deprecated' do
+ let_it_be(:deprecation_warning) { "Please upgrade" }
+ let_it_be(:upcoming_deprecation_warning) { "Please consider upgrading" }
+
+ context 'when database meets minimum version and there is no upcoming deprecation' do
before do
- allow(described_class).to receive(:db_version_deprecated?).and_return(false)
+ allow(Gitlab::Database).to receive(:postgresql_minimum_supported_version?).and_return(true)
+ allow(Gitlab::Database).to receive(:postgresql_upcoming_deprecation?).and_return(false)
end
it { is_expected.to be_empty }
end
- context 'database version is deprecated' do
+ context 'when database does not meet minimum version and there is no upcoming deprecation' do
before do
- allow(described_class).to receive(:db_version_deprecated?).and_return(true)
- end
-
- let(:notice_deprecated_database) do
- {
- type: 'warning',
- message: _('Note that PostgreSQL 11 will become the minimum required PostgreSQL version in GitLab 13.0 (May 2020). '\
- 'PostgreSQL 9.6 and PostgreSQL 10 will no longer be supported in GitLab 13.0. '\
- 'Please consider upgrading your PostgreSQL version (%{db_version}) soon.') % { db_version: Gitlab::Database.version.to_s }
- }
+ allow(Gitlab::Database).to receive(:postgresql_minimum_supported_version?).and_return(false)
+ allow(Gitlab::Database).to receive(:postgresql_upcoming_deprecation?).and_return(false)
end
- it 'reports deprecated database notices' do
- is_expected.to contain_exactly(notice_deprecated_database)
+ it 'only returns notice about deprecated database version' do
+ is_expected.to include(a_hash_including(message: include(deprecation_warning)))
+ is_expected.not_to include(a_hash_including(message: include(upcoming_deprecation_warning)))
end
end
- end
- describe '#db_version_deprecated' do
- subject { described_class.db_version_deprecated? }
-
- context 'database version is not deprecated' do
+ context 'when database meets minimum version and there is an upcoming deprecation' do
before do
- allow(Gitlab::Database).to receive(:version).and_return(11)
+ allow(Gitlab::Database).to receive(:postgresql_minimum_supported_version?).and_return(true)
+ allow(Gitlab::Database).to receive(:postgresql_upcoming_deprecation?).and_return(true)
end
- it { is_expected.to be false }
+ it 'only returns notice about an upcoming deprecation' do
+ is_expected.to include(a_hash_including(message: include(upcoming_deprecation_warning)))
+ is_expected.not_to include(a_hash_including(message: include(deprecation_warning)))
+ end
end
- context 'database version is deprecated' do
+ context 'when database does not meet minimum version and there is an upcoming deprecation' do
before do
- allow(Gitlab::Database).to receive(:version).and_return(10)
+ allow(Gitlab::Database).to receive(:postgresql_minimum_supported_version?).and_return(false)
+ allow(Gitlab::Database).to receive(:postgresql_upcoming_deprecation?).and_return(true)
end
- it { is_expected.to be true }
+ it 'returns notice about deprecated database version and an upcoming deprecation' do
+ is_expected.to include(
+ a_hash_including(message: include(deprecation_warning)),
+ a_hash_including(message: include(upcoming_deprecation_warning))
+ )
+ end
end
end
end
diff --git a/spec/lib/gitlab/config_checker/puma_rugged_checker_spec.rb b/spec/lib/gitlab/config_checker/puma_rugged_checker_spec.rb
index badfd56d571..afee3c5536e 100644
--- a/spec/lib/gitlab/config_checker/puma_rugged_checker_spec.rb
+++ b/spec/lib/gitlab/config_checker/puma_rugged_checker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ConfigChecker::PumaRuggedChecker do
+RSpec.describe Gitlab::ConfigChecker::PumaRuggedChecker do
describe '#check' do
subject { described_class.check }
diff --git a/spec/lib/gitlab/conflict/file_collection_spec.rb b/spec/lib/gitlab/conflict/file_collection_spec.rb
index f3cdb1a9e59..6da7f8d530d 100644
--- a/spec/lib/gitlab/conflict/file_collection_spec.rb
+++ b/spec/lib/gitlab/conflict/file_collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Conflict::FileCollection do
+RSpec.describe Gitlab::Conflict::FileCollection do
let(:merge_request) { create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start') }
let(:file_collection) { described_class.new(merge_request) }
diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb
index 966648bcc5a..b54fe40bb5f 100644
--- a/spec/lib/gitlab/conflict/file_spec.rb
+++ b/spec/lib/gitlab/conflict/file_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Conflict::File do
+RSpec.describe Gitlab::Conflict::File do
include GitHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb
index bbbbf91bd44..a94fd6acd32 100644
--- a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb
+++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ContentSecurityPolicy::ConfigLoader do
+RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
let(:policy) { ActionDispatch::ContentSecurityPolicy.new }
let(:csp_config) do
{
diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb
index 97742a3e815..67b2ea7a1d4 100644
--- a/spec/lib/gitlab/contributions_calendar_spec.rb
+++ b/spec/lib/gitlab/contributions_calendar_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ContributionsCalendar do
+RSpec.describe Gitlab::ContributionsCalendar do
let(:contributor) { create(:user) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/cross_project_access/check_collection_spec.rb b/spec/lib/gitlab/cross_project_access/check_collection_spec.rb
index 1aa5480b670..178188f5555 100644
--- a/spec/lib/gitlab/cross_project_access/check_collection_spec.rb
+++ b/spec/lib/gitlab/cross_project_access/check_collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CrossProjectAccess::CheckCollection do
+RSpec.describe Gitlab::CrossProjectAccess::CheckCollection do
subject(:collection) { described_class.new }
describe '#add_collection' do
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 7d2471b6327..5327030daf0 100644
--- a/spec/lib/gitlab/cross_project_access/check_info_spec.rb
+++ b/spec/lib/gitlab/cross_project_access/check_info_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CrossProjectAccess::CheckInfo do
+RSpec.describe Gitlab::CrossProjectAccess::CheckInfo do
let(:dummy_controller) { double }
before do
diff --git a/spec/lib/gitlab/cross_project_access/class_methods_spec.rb b/spec/lib/gitlab/cross_project_access/class_methods_spec.rb
index 17d265542dd..cc2c431fc07 100644
--- a/spec/lib/gitlab/cross_project_access/class_methods_spec.rb
+++ b/spec/lib/gitlab/cross_project_access/class_methods_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CrossProjectAccess::ClassMethods do
+RSpec.describe Gitlab::CrossProjectAccess::ClassMethods do
let(:dummy_class) do
Class.new do
extend Gitlab::CrossProjectAccess::ClassMethods
diff --git a/spec/lib/gitlab/cross_project_access_spec.rb b/spec/lib/gitlab/cross_project_access_spec.rb
index ce18d207413..fb72b85f161 100644
--- a/spec/lib/gitlab/cross_project_access_spec.rb
+++ b/spec/lib/gitlab/cross_project_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CrossProjectAccess do
+RSpec.describe Gitlab::CrossProjectAccess do
let(:super_class) { Class.new }
let(:descendant_class) { Class.new(super_class) }
let(:current_instance) { described_class.new }
diff --git a/spec/lib/gitlab/crypto_helper_spec.rb b/spec/lib/gitlab/crypto_helper_spec.rb
index 71bbeccb17b..c07089d8ef0 100644
--- a/spec/lib/gitlab/crypto_helper_spec.rb
+++ b/spec/lib/gitlab/crypto_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CryptoHelper do
+RSpec.describe Gitlab::CryptoHelper do
describe '.sha256' do
it 'generates SHA256 digest Base46 encoded' do
digest = described_class.sha256('some-value')
diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb
index bfd9980ee9c..fd4e8bc1cd0 100644
--- a/spec/lib/gitlab/current_settings_spec.rb
+++ b/spec/lib/gitlab/current_settings_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CurrentSettings do
+RSpec.describe Gitlab::CurrentSettings do
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
end
diff --git a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
index 3eea791d61a..056c1b5bc9f 100644
--- a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::BaseEventFetcher do
+RSpec.describe Gitlab::CycleAnalytics::BaseEventFetcher do
let(:max_events) { 2 }
let(:project) { create(:project, :repository) }
let(:user) { create(:user, :admin) }
diff --git a/spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb
index 326a41a3af7..a1a173abe57 100644
--- a/spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::CodeEventFetcher do
+RSpec.describe Gitlab::CycleAnalytics::CodeEventFetcher do
let(:stage_name) { :code }
it_behaves_like 'default query config' do
diff --git a/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
index 9a4193b09f5..afab19de2ab 100644
--- a/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::CodeStage do
+RSpec.describe Gitlab::CycleAnalytics::CodeStage do
let(:stage_name) { :code }
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/cycle_analytics/events_spec.rb b/spec/lib/gitlab/cycle_analytics/events_spec.rb
index 9eee7e89062..246003cde84 100644
--- a/spec/lib/gitlab/cycle_analytics/events_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/events_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'cycle analytics events' do
+RSpec.describe 'cycle analytics events' do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
diff --git a/spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb
index a72e2952782..7a49ee53e8f 100644
--- a/spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::IssueEventFetcher do
+RSpec.describe Gitlab::CycleAnalytics::IssueEventFetcher do
let(:stage_name) { :issue }
it_behaves_like 'default query config'
diff --git a/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
index 021d31bf160..9ec71e6ed72 100644
--- a/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::IssueStage do
+RSpec.describe Gitlab::CycleAnalytics::IssueStage do
let(:stage_name) { :issue }
let(:project) { create(:project) }
let(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
diff --git a/spec/lib/gitlab/cycle_analytics/permissions_spec.rb b/spec/lib/gitlab/cycle_analytics/permissions_spec.rb
index 2896e973a43..3fd48993e5f 100644
--- a/spec/lib/gitlab/cycle_analytics/permissions_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/permissions_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::Permissions do
+RSpec.describe Gitlab::CycleAnalytics::Permissions do
let(:project) { create(:project, public_builds: false) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb
index 587f185b970..bc14a772d34 100644
--- a/spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::PlanEventFetcher do
+RSpec.describe Gitlab::CycleAnalytics::PlanEventFetcher do
let(:stage_name) { :plan }
it_behaves_like 'default query config' do
diff --git a/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
index e391fa6b999..66d00edacb7 100644
--- a/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::PlanStage do
+RSpec.describe Gitlab::CycleAnalytics::PlanStage do
let(:stage_name) { :plan }
let(:project) { create(:project) }
let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
diff --git a/spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb
index aeca72e8c91..86b07a95cbb 100644
--- a/spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::ProductionEventFetcher do
+RSpec.describe Gitlab::CycleAnalytics::ProductionEventFetcher do
let(:stage_name) { :production }
it_behaves_like 'default query config'
diff --git a/spec/lib/gitlab/cycle_analytics/production_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/production_stage_spec.rb
index aeeae291e2e..73b17194f72 100644
--- a/spec/lib/gitlab/cycle_analytics/production_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/production_stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::ProductionStage do
+RSpec.describe Gitlab::CycleAnalytics::ProductionStage do
let(:stage_name) { 'Total' }
it_behaves_like 'base stage'
diff --git a/spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb
index 3eb62b45e6f..fe13cc6b065 100644
--- a/spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::ReviewEventFetcher do
+RSpec.describe Gitlab::CycleAnalytics::ReviewEventFetcher do
let(:stage_name) { :review }
it_behaves_like 'default query config'
diff --git a/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
index 14100ee6f73..cdd1cca6837 100644
--- a/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::ReviewStage do
+RSpec.describe Gitlab::CycleAnalytics::ReviewStage do
let(:stage_name) { :review }
let(:project) { create(:project) }
let(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
diff --git a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
index ccc99017e37..9ece24074e7 100644
--- a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::StageSummary do
+RSpec.describe Gitlab::CycleAnalytics::StageSummary do
let(:project) { create(:project, :repository) }
let(:options) { { from: 1.day.ago, current_user: user } }
let(:user) { create(:user, :admin) }
diff --git a/spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb
index 525f1608a70..bdf1b99c4c9 100644
--- a/spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::StagingEventFetcher do
+RSpec.describe Gitlab::CycleAnalytics::StagingEventFetcher do
let(:stage_name) { :staging }
it_behaves_like 'default query config' do
diff --git a/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
index 930892edd31..69e42adb139 100644
--- a/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::StagingStage do
+RSpec.describe Gitlab::CycleAnalytics::StagingStage do
let(:stage_name) { :staging }
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/cycle_analytics/summary/value_spec.rb b/spec/lib/gitlab/cycle_analytics/summary/value_spec.rb
index d9bdfa92a04..c955b288500 100644
--- a/spec/lib/gitlab/cycle_analytics/summary/value_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/summary/value_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::Summary::Value do
+RSpec.describe Gitlab::CycleAnalytics::Summary::Value do
describe Gitlab::CycleAnalytics::Summary::Value::None do
it 'returns `-`' do
expect(described_class.new.to_s).to eq('-')
diff --git a/spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb
index d550f083600..1277385d0b4 100644
--- a/spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::TestEventFetcher do
+RSpec.describe Gitlab::CycleAnalytics::TestEventFetcher do
let(:stage_name) { :test }
it_behaves_like 'default query config' do
diff --git a/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
index 56e90520e72..9a207d32167 100644
--- a/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::TestStage do
+RSpec.describe Gitlab::CycleAnalytics::TestStage do
let(:stage_name) { :test }
let(:project) { create(:project) }
let(:stage_options) { { from: 2.days.ago, current_user: project.creator, project: project } }
diff --git a/spec/lib/gitlab/cycle_analytics/updater_spec.rb b/spec/lib/gitlab/cycle_analytics/updater_spec.rb
index 67f386f9144..f7095cffc19 100644
--- a/spec/lib/gitlab/cycle_analytics/updater_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/updater_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::Updater do
+RSpec.describe Gitlab::CycleAnalytics::Updater do
describe 'updates authors' do
let(:user) { create(:user) }
let(:events) { [{ 'author_id' => user.id }] }
diff --git a/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb b/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb
index 41ce9355708..9ebdacb16de 100644
--- a/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::UsageData do
+RSpec.describe Gitlab::CycleAnalytics::UsageData do
describe '#to_json' do
before do
# Since git commits only have second precision, round up to the
diff --git a/spec/lib/gitlab/daemon_spec.rb b/spec/lib/gitlab/daemon_spec.rb
index cf1f089c577..075a1e414c7 100644
--- a/spec/lib/gitlab/daemon_spec.rb
+++ b/spec/lib/gitlab/daemon_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Daemon do
+RSpec.describe Gitlab::Daemon do
subject { described_class.new }
before do
diff --git a/spec/lib/gitlab/danger/changelog_spec.rb b/spec/lib/gitlab/danger/changelog_spec.rb
index 130a4708cec..f5954cd8c1e 100644
--- a/spec/lib/gitlab/danger/changelog_spec.rb
+++ b/spec/lib/gitlab/danger/changelog_spec.rb
@@ -1,13 +1,11 @@
# frozen_string_literal: true
require 'fast_spec_helper'
-require 'rspec-parameterized'
require_relative 'danger_spec_helper'
require 'gitlab/danger/changelog'
-describe Gitlab::Danger::Changelog do
- using RSpec::Parameterized::TableSyntax
+RSpec.describe Gitlab::Danger::Changelog do
include DangerSpecHelper
let(:added_files) { nil }
@@ -26,34 +24,36 @@ describe Gitlab::Danger::Changelog do
subject(:changelog) { fake_danger.new(git: fake_git, gitlab: fake_gitlab, helper: fake_helper) }
describe '#needed?' do
- subject { changelog.needed? }
+ let(:category_with_changelog) { :backend }
+ let(:label_with_changelog) { 'frontend' }
+ let(:category_without_changelog) { Gitlab::Danger::Changelog::NO_CHANGELOG_CATEGORIES.first }
+ let(:label_without_changelog) { Gitlab::Danger::Changelog::NO_CHANGELOG_LABELS.first }
- where(:categories, :labels) do
- { backend: nil } | %w[backend backstage]
- { frontend: nil, docs: nil } | ['ci-build']
- { engineering_productivity: nil, none: nil } | ['meta']
- end
+ subject { changelog.needed? }
- with_them do
- let(:changes_by_category) { categories }
- let(:mr_labels) { labels }
+ context 'when MR contains only categories requiring no changelog' do
+ let(:changes_by_category) { { category_without_changelog => nil } }
+ let(:mr_labels) { [] }
- it "is falsy when categories and labels require no changelog" do
+ it 'is falsey' do
is_expected.to be_falsy
end
end
- where(:categories, :labels) do
- { frontend: nil, docs: nil } | ['database::review pending', 'feature']
- { backend: nil } | ['backend', 'technical debt']
- { engineering_productivity: nil, none: nil } | ['frontend']
+ context 'when MR contains a label that require no changelog' do
+ let(:changes_by_category) { { category_with_changelog => nil } }
+ let(:mr_labels) { [label_with_changelog, label_without_changelog] }
+
+ it 'is falsey' do
+ is_expected.to be_falsy
+ end
end
- with_them do
- let(:changes_by_category) { categories }
- let(:mr_labels) { labels }
+ context 'when MR contains a category that require changelog and a category that require no changelog' do
+ let(:changes_by_category) { { category_with_changelog => nil, category_without_changelog => nil } }
+ let(:mr_labels) { [] }
- it "is truthy when categories and labels require a changelog" do
+ it 'is truthy' do
is_expected.to be_truthy
end
end
diff --git a/spec/lib/gitlab/danger/commit_linter_spec.rb b/spec/lib/gitlab/danger/commit_linter_spec.rb
index e57ccd12fa5..06bec6f793d 100644
--- a/spec/lib/gitlab/danger/commit_linter_spec.rb
+++ b/spec/lib/gitlab/danger/commit_linter_spec.rb
@@ -6,7 +6,7 @@ require_relative 'danger_spec_helper'
require 'gitlab/danger/commit_linter'
-describe Gitlab::Danger::CommitLinter do
+RSpec.describe Gitlab::Danger::CommitLinter do
using RSpec::Parameterized::TableSyntax
let(:total_files_changed) { 2 }
@@ -156,7 +156,7 @@ describe Gitlab::Danger::CommitLinter do
context 'when subject is a WIP' do
let(:final_message) { 'A B C' }
# commit message with prefix will be over max length. commit message without prefix will be of maximum size
- let(:commit_message) { described_class::WIP_PREFIX + final_message + 'D' * (described_class::WARN_SUBJECT_LENGTH - final_message.size) }
+ let(:commit_message) { described_class::WIP_PREFIX + final_message + 'D' * (described_class::MAX_LINE_LENGTH - final_message.size) }
it 'does not have any problems' do
commit_linter.lint
@@ -176,16 +176,6 @@ describe Gitlab::Danger::CommitLinter do
end
end
- context 'when subject is above warning' do
- let(:commit_message) { 'A B ' + 'C' * described_class::WARN_SUBJECT_LENGTH }
-
- it 'adds a problem' do
- expect(commit_linter).to receive(:add_problem).with(:subject_above_warning, described_class::DEFAULT_SUBJECT_DESCRIPTION)
-
- commit_linter.lint
- end
- end
-
context 'when subject starts with lowercase' do
let(:commit_message) { 'a B C' }
diff --git a/spec/lib/gitlab/danger/emoji_checker_spec.rb b/spec/lib/gitlab/danger/emoji_checker_spec.rb
index 0cdc18ce626..6092c751e1c 100644
--- a/spec/lib/gitlab/danger/emoji_checker_spec.rb
+++ b/spec/lib/gitlab/danger/emoji_checker_spec.rb
@@ -5,7 +5,7 @@ require 'rspec-parameterized'
require 'gitlab/danger/emoji_checker'
-describe Gitlab::Danger::EmojiChecker do
+RSpec.describe Gitlab::Danger::EmojiChecker do
using RSpec::Parameterized::TableSyntax
describe '#includes_text_emoji?' do
diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb
index 809064a540c..e73742b5911 100644
--- a/spec/lib/gitlab/danger/helper_spec.rb
+++ b/spec/lib/gitlab/danger/helper_spec.rb
@@ -6,7 +6,7 @@ require_relative 'danger_spec_helper'
require 'gitlab/danger/helper'
-describe Gitlab::Danger::Helper do
+RSpec.describe Gitlab::Danger::Helper do
using RSpec::Parameterized::TableSyntax
include DangerSpecHelper
@@ -165,125 +165,152 @@ describe Gitlab::Danger::Helper do
end
end
- describe '#category_for_file' do
- where(:path, :expected_category) do
- 'doc/foo' | :none
- 'CONTRIBUTING.md' | :none
- 'LICENSE' | :none
- 'MAINTENANCE.md' | :none
- 'PHILOSOPHY.md' | :none
- 'PROCESS.md' | :none
- 'README.md' | :none
-
- 'ee/doc/foo' | :unknown
- 'ee/README' | :unknown
-
- 'app/assets/foo' | :frontend
- 'app/views/foo' | :frontend
- 'public/foo' | :frontend
- 'scripts/frontend/foo' | :frontend
- 'spec/javascripts/foo' | :frontend
- 'spec/frontend/bar' | :frontend
- 'vendor/assets/foo' | :frontend
- 'babel.config.js' | :frontend
- 'jest.config.js' | :frontend
- 'package.json' | :frontend
- 'yarn.lock' | :frontend
- 'config/foo.js' | :frontend
- 'config/deep/foo.js' | :frontend
-
- 'ee/app/assets/foo' | :frontend
- 'ee/app/views/foo' | :frontend
- 'ee/spec/javascripts/foo' | :frontend
- 'ee/spec/frontend/bar' | :frontend
-
- 'app/models/foo' | :backend
- 'bin/foo' | :backend
- 'config/foo' | :backend
- 'lib/foo' | :backend
- 'rubocop/foo' | :backend
- 'spec/foo' | :backend
- 'spec/foo/bar' | :backend
-
- 'ee/app/foo' | :backend
- 'ee/bin/foo' | :backend
- 'ee/spec/foo' | :backend
- 'ee/spec/foo/bar' | :backend
-
- 'generator_templates/foo' | :backend
- 'vendor/languages.yml' | :backend
- 'vendor/licenses.csv' | :backend
- 'file_hooks/examples/' | :backend
-
- 'Gemfile' | :backend
- 'Gemfile.lock' | :backend
- 'Rakefile' | :backend
- 'FOO_VERSION' | :backend
-
- 'Dangerfile' | :engineering_productivity
- 'danger/commit_messages/Dangerfile' | :engineering_productivity
- 'ee/danger/commit_messages/Dangerfile' | :engineering_productivity
- 'danger/commit_messages/' | :engineering_productivity
- 'ee/danger/commit_messages/' | :engineering_productivity
- '.gitlab-ci.yml' | :engineering_productivity
- '.gitlab/ci/cng.gitlab-ci.yml' | :engineering_productivity
- '.gitlab/ci/ee-specific-checks.gitlab-ci.yml' | :engineering_productivity
- 'scripts/foo' | :engineering_productivity
- 'lib/gitlab/danger/foo' | :engineering_productivity
- 'ee/lib/gitlab/danger/foo' | :engineering_productivity
- '.overcommit.yml.example' | :engineering_productivity
- '.editorconfig' | :engineering_productivity
- 'tooling/overcommit/foo' | :engineering_productivity
- '.codeclimate.yml' | :engineering_productivity
-
- 'lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml' | :backend
-
- 'ee/FOO_VERSION' | :unknown
-
- 'db/schema.rb' | :database
- 'db/structure.sql' | :database
- 'db/migrate/foo' | :database
- 'db/post_migrate/foo' | :database
- 'ee/db/migrate/foo' | :database
- 'ee/db/post_migrate/foo' | :database
- 'ee/db/geo/migrate/foo' | :database
- 'ee/db/geo/post_migrate/foo' | :database
- 'app/models/project_authorization.rb' | :database
- 'app/services/users/refresh_authorized_projects_service.rb' | :database
- 'lib/gitlab/background_migration.rb' | :database
- 'lib/gitlab/background_migration/foo' | :database
- 'ee/lib/gitlab/background_migration/foo' | :database
- 'lib/gitlab/database.rb' | :database
- 'lib/gitlab/database/foo' | :database
- 'ee/lib/gitlab/database/foo' | :database
- 'lib/gitlab/github_import.rb' | :database
- 'lib/gitlab/github_import/foo' | :database
- 'lib/gitlab/sql/foo' | :database
- 'rubocop/cop/migration/foo' | :database
-
- 'db/fixtures/foo.rb' | :backend
- 'ee/db/fixtures/foo.rb' | :backend
-
- 'qa/foo' | :qa
- 'ee/qa/foo' | :qa
-
- 'changelogs/foo' | :none
- 'ee/changelogs/foo' | :none
- 'locale/gitlab.pot' | :none
-
- 'FOO' | :unknown
- 'foo' | :unknown
-
- 'foo/bar.rb' | :backend
- 'foo/bar.js' | :frontend
- 'foo/bar.txt' | :none
- 'foo/bar.md' | :none
+ describe '#categories_for_file' do
+ before do
+ allow(fake_git).to receive(:diff_for_file).with('usage_data.rb') { double(:diff, patch: "+ count(User.active)") }
+ end
+
+ where(:path, :expected_categories) do
+ 'usage_data.rb' | [:database, :backend]
+ 'doc/foo.md' | [:docs]
+ 'CONTRIBUTING.md' | [:docs]
+ 'LICENSE' | [:docs]
+ 'MAINTENANCE.md' | [:docs]
+ 'PHILOSOPHY.md' | [:docs]
+ 'PROCESS.md' | [:docs]
+ 'README.md' | [:docs]
+
+ 'ee/doc/foo' | [:unknown]
+ 'ee/README' | [:unknown]
+
+ 'app/assets/foo' | [:frontend]
+ 'app/views/foo' | [:frontend]
+ 'public/foo' | [:frontend]
+ 'scripts/frontend/foo' | [:frontend]
+ 'spec/javascripts/foo' | [:frontend]
+ 'spec/frontend/bar' | [:frontend]
+ 'vendor/assets/foo' | [:frontend]
+ 'babel.config.js' | [:frontend]
+ 'jest.config.js' | [:frontend]
+ 'package.json' | [:frontend]
+ 'yarn.lock' | [:frontend]
+ 'config/foo.js' | [:frontend]
+ 'config/deep/foo.js' | [:frontend]
+
+ 'ee/app/assets/foo' | [:frontend]
+ 'ee/app/views/foo' | [:frontend]
+ 'ee/spec/javascripts/foo' | [:frontend]
+ 'ee/spec/frontend/bar' | [:frontend]
+
+ '.gitlab/ci/frontend.gitlab-ci.yml' | %i[frontend engineering_productivity]
+
+ 'app/models/foo' | [:backend]
+ 'bin/foo' | [:backend]
+ 'config/foo' | [:backend]
+ 'lib/foo' | [:backend]
+ 'rubocop/foo' | [:backend]
+ 'spec/foo' | [:backend]
+ 'spec/foo/bar' | [:backend]
+
+ 'ee/app/foo' | [:backend]
+ 'ee/bin/foo' | [:backend]
+ 'ee/spec/foo' | [:backend]
+ 'ee/spec/foo/bar' | [:backend]
+
+ 'generator_templates/foo' | [:backend]
+ 'vendor/languages.yml' | [:backend]
+ 'vendor/licenses.csv' | [:backend]
+ 'file_hooks/examples/' | [:backend]
+
+ 'Gemfile' | [:backend]
+ 'Gemfile.lock' | [:backend]
+ 'Rakefile' | [:backend]
+ 'FOO_VERSION' | [:backend]
+
+ 'Dangerfile' | [:engineering_productivity]
+ 'danger/commit_messages/Dangerfile' | [:engineering_productivity]
+ 'ee/danger/commit_messages/Dangerfile' | [:engineering_productivity]
+ 'danger/commit_messages/' | [:engineering_productivity]
+ 'ee/danger/commit_messages/' | [:engineering_productivity]
+ '.gitlab-ci.yml' | [:engineering_productivity]
+ '.gitlab/ci/cng.gitlab-ci.yml' | [:engineering_productivity]
+ '.gitlab/ci/ee-specific-checks.gitlab-ci.yml' | [:engineering_productivity]
+ 'scripts/foo' | [:engineering_productivity]
+ 'lib/gitlab/danger/foo' | [:engineering_productivity]
+ 'ee/lib/gitlab/danger/foo' | [:engineering_productivity]
+ '.overcommit.yml.example' | [:engineering_productivity]
+ '.editorconfig' | [:engineering_productivity]
+ 'tooling/overcommit/foo' | [:engineering_productivity]
+ '.codeclimate.yml' | [:engineering_productivity]
+
+ 'lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml' | [:backend]
+
+ 'ee/FOO_VERSION' | [:unknown]
+
+ 'db/schema.rb' | [:database]
+ 'db/structure.sql' | [:database]
+ 'db/migrate/foo' | [:database]
+ 'db/post_migrate/foo' | [:database]
+ 'ee/db/migrate/foo' | [:database]
+ 'ee/db/post_migrate/foo' | [:database]
+ 'ee/db/geo/migrate/foo' | [:database]
+ 'ee/db/geo/post_migrate/foo' | [:database]
+ 'app/models/project_authorization.rb' | [:database]
+ 'app/services/users/refresh_authorized_projects_service.rb' | [:database]
+ 'lib/gitlab/background_migration.rb' | [:database]
+ 'lib/gitlab/background_migration/foo' | [:database]
+ 'ee/lib/gitlab/background_migration/foo' | [:database]
+ 'lib/gitlab/database.rb' | [:database]
+ 'lib/gitlab/database/foo' | [:database]
+ 'ee/lib/gitlab/database/foo' | [:database]
+ 'lib/gitlab/github_import.rb' | [:database]
+ 'lib/gitlab/github_import/foo' | [:database]
+ 'lib/gitlab/sql/foo' | [:database]
+ 'rubocop/cop/migration/foo' | [:database]
+
+ 'db/fixtures/foo.rb' | [:backend]
+ 'ee/db/fixtures/foo.rb' | [:backend]
+
+ 'qa/foo' | [:qa]
+ 'ee/qa/foo' | [:qa]
+
+ 'changelogs/foo' | [:none]
+ 'ee/changelogs/foo' | [:none]
+ 'locale/gitlab.pot' | [:none]
+
+ 'FOO' | [:unknown]
+ 'foo' | [:unknown]
+
+ 'foo/bar.rb' | [:backend]
+ 'foo/bar.js' | [:frontend]
+ 'foo/bar.txt' | [:none]
+ 'foo/bar.md' | [:none]
end
with_them do
- subject { helper.category_for_file(path) }
+ subject { helper.categories_for_file(path) }
- it { is_expected.to eq(expected_category) }
+ it { is_expected.to eq(expected_categories) }
+ end
+
+ context 'having specific changes' do
+ it 'has database and backend categories' do
+ allow(fake_git).to receive(:diff_for_file).with('usage_data.rb') { double(:diff, patch: "+ count(User.active)") }
+
+ expect(helper.categories_for_file('usage_data.rb')).to eq([:database, :backend])
+ end
+
+ it 'has backend category' do
+ allow(fake_git).to receive(:diff_for_file).with('usage_data.rb') { double(:diff, patch: "+ alt_usage_data(User.active)") }
+
+ expect(helper.categories_for_file('usage_data.rb')).to eq([:backend])
+ end
+
+ it 'has backend category for changes outside usage_data files' do
+ allow(fake_git).to receive(:diff_for_file).with('user.rb') { double(:diff, patch: "+ count(User.active)") }
+
+ expect(helper.categories_for_file('user.rb')).to eq([:backend])
+ end
end
end
@@ -296,6 +323,7 @@ describe Gitlab::Danger::Helper do
:frontend | '~frontend'
:none | ''
:qa | '~QA'
+ :engineering_productivity | '~"Engineering Productivity" for CI, Danger'
end
with_them do
@@ -335,6 +363,11 @@ describe Gitlab::Danger::Helper do
where(:mr_title, :expected_mr_title) do
'My MR title' | 'My MR title'
'WIP: My MR title' | 'My MR title'
+ 'Draft: My MR title' | 'My MR title'
+ '(Draft) My MR title' | 'My MR title'
+ '[Draft] My MR title' | 'My MR title'
+ '[DRAFT] My MR title' | 'My MR title'
+ 'DRAFT: My MR title' | 'My MR title'
end
with_them do
@@ -366,6 +399,69 @@ describe Gitlab::Danger::Helper do
end
end
+ describe '#cherry_pick_mr?' do
+ it 'returns false when `gitlab_helper` is unavailable' do
+ expect(helper).to receive(:gitlab_helper).and_return(nil)
+
+ expect(helper).not_to be_cherry_pick_mr
+ end
+
+ context 'when MR title does not mention a cherry-pick' do
+ it 'returns false' do
+ expect(fake_gitlab).to receive(:mr_json)
+ .and_return('title' => 'Add feature xyz')
+
+ expect(helper).not_to be_cherry_pick_mr
+ end
+ end
+
+ context 'when MR title mentions a cherry-pick' do
+ [
+ 'Cherry Pick !1234',
+ 'cherry-pick !1234',
+ 'CherryPick !1234'
+ ].each do |mr_title|
+ it 'returns true' do
+ expect(fake_gitlab).to receive(:mr_json)
+ .and_return('title' => mr_title)
+
+ expect(helper).to be_cherry_pick_mr
+ end
+ end
+ end
+ end
+
+ describe '#stable_branch?' do
+ it 'returns false when `gitlab_helper` is unavailable' do
+ expect(helper).to receive(:gitlab_helper).and_return(nil)
+
+ expect(helper).not_to be_stable_branch
+ end
+
+ context 'when MR target branch is not a stable branch' do
+ it 'returns false' do
+ expect(fake_gitlab).to receive(:mr_json)
+ .and_return('target_branch' => 'my-feature-branch')
+
+ expect(helper).not_to be_stable_branch
+ end
+ end
+
+ context 'when MR target branch is a stable branch' do
+ %w[
+ 13-1-stable-ee
+ 13-1-stable-ee-patch-1
+ ].each do |target_branch|
+ it 'returns true' do
+ expect(fake_gitlab).to receive(:mr_json)
+ .and_return('target_branch' => target_branch)
+
+ expect(helper).to be_stable_branch
+ end
+ end
+ end
+ end
+
describe '#mr_has_label?' do
it 'returns false when `gitlab_helper` is unavailable' do
expect(helper).to receive(:gitlab_helper).and_return(nil)
diff --git a/spec/lib/gitlab/danger/roulette_spec.rb b/spec/lib/gitlab/danger/roulette_spec.rb
index b6148cd1407..676edca2459 100644
--- a/spec/lib/gitlab/danger/roulette_spec.rb
+++ b/spec/lib/gitlab/danger/roulette_spec.rb
@@ -2,16 +2,23 @@
require 'fast_spec_helper'
require 'webmock/rspec'
+require 'timecop'
require 'gitlab/danger/roulette'
-describe Gitlab::Danger::Roulette do
+RSpec.describe Gitlab::Danger::Roulette do
+ around do |example|
+ Timecop.freeze(Time.utc(2020, 06, 22, 10)) { example.run }
+ end
+
let(:backend_maintainer) do
{
username: 'backend-maintainer',
name: 'Backend maintainer',
role: 'Backend engineer',
- projects: { 'gitlab' => 'maintainer backend' }
+ projects: { 'gitlab' => 'maintainer backend' },
+ available: true,
+ tz_offset_hours: 2.0
}
end
let(:frontend_reviewer) do
@@ -19,7 +26,9 @@ describe Gitlab::Danger::Roulette do
username: 'frontend-reviewer',
name: 'Frontend reviewer',
role: 'Frontend engineer',
- projects: { 'gitlab' => 'reviewer frontend' }
+ projects: { 'gitlab' => 'reviewer frontend' },
+ available: true,
+ tz_offset_hours: 2.0
}
end
let(:frontend_maintainer) do
@@ -27,7 +36,9 @@ describe Gitlab::Danger::Roulette do
username: 'frontend-maintainer',
name: 'Frontend maintainer',
role: 'Frontend engineer',
- projects: { 'gitlab' => "maintainer frontend" }
+ projects: { 'gitlab' => "maintainer frontend" },
+ available: true,
+ tz_offset_hours: 2.0
}
end
let(:software_engineer_in_test) do
@@ -38,7 +49,9 @@ describe Gitlab::Danger::Roulette do
projects: {
'gitlab' => 'reviewer qa',
'gitlab-qa' => 'maintainer'
- }
+ },
+ available: true,
+ tz_offset_hours: 2.0
}
end
let(:engineering_productivity_reviewer) do
@@ -46,7 +59,9 @@ describe Gitlab::Danger::Roulette do
username: 'eng-prod-reviewer',
name: 'EP engineer',
role: 'Engineering Productivity',
- projects: { 'gitlab' => 'reviewer backend' }
+ projects: { 'gitlab' => 'reviewer backend' },
+ available: true,
+ tz_offset_hours: 2.0
}
end
@@ -73,10 +88,17 @@ describe Gitlab::Danger::Roulette do
def matching_spin(category, reviewer: { username: nil }, maintainer: { username: nil }, optional: nil)
satisfy do |spin|
- spin.category == category &&
- spin.reviewer&.username == reviewer[:username] &&
- spin.maintainer&.username == maintainer[:username] &&
- spin.optional_role == optional
+ bool = spin.category == category
+ bool &&= spin.reviewer&.username == reviewer[:username]
+
+ bool &&=
+ if maintainer
+ spin.maintainer&.username == maintainer[:username]
+ else
+ spin.maintainer.nil?
+ end
+
+ bool && spin.optional_role == optional
end
end
@@ -85,67 +107,114 @@ describe Gitlab::Danger::Roulette do
let!(:branch_name) { 'a-branch' }
let!(:mr_labels) { ['backend', 'devops::create'] }
let!(:author) { Gitlab::Danger::Teammate.new('username' => 'filipa') }
-
- before do
- [
- backend_maintainer,
- frontend_reviewer,
- frontend_maintainer,
- software_engineer_in_test,
- engineering_productivity_reviewer
- ].each do |person|
- stub_person_status(instance_double(Gitlab::Danger::Teammate, username: person[:username]), message: 'making GitLab magic')
- end
-
+ let(:timezone_experiment) { false }
+ let(:spins) do
+ # Stub the request at the latest time so that we can modify the raw data, e.g. available fields.
WebMock
.stub_request(:get, described_class::ROULETTE_DATA_URL)
.to_return(body: teammate_json)
+
+ subject.spin(project, categories, branch_name, timezone_experiment: timezone_experiment)
+ end
+
+ before do
allow(subject).to receive_message_chain(:gitlab, :mr_author).and_return(author.username)
allow(subject).to receive_message_chain(:gitlab, :mr_labels).and_return(mr_labels)
end
- context 'when change contains backend category' do
- it 'assigns backend reviewer and maintainer' do
- categories = [:backend]
- spins = subject.spin(project, categories, branch_name)
+ context 'when timezone_experiment == false' do
+ context 'when change contains backend category' do
+ let(:categories) { [:backend] }
- expect(spins).to contain_exactly(matching_spin(:backend, reviewer: engineering_productivity_reviewer, maintainer: backend_maintainer))
+ it 'assigns backend reviewer and maintainer' do
+ expect(spins).to contain_exactly(matching_spin(:backend, reviewer: engineering_productivity_reviewer, maintainer: backend_maintainer))
+ end
+
+ context 'when teammate is not available' do
+ before do
+ backend_maintainer[:available] = false
+ end
+
+ it 'assigns backend reviewer and no maintainer' do
+ expect(spins).to contain_exactly(matching_spin(:backend, reviewer: engineering_productivity_reviewer, maintainer: nil))
+ end
+ end
end
- end
- context 'when change contains frontend category' do
- it 'assigns frontend reviewer and maintainer' do
- categories = [:frontend]
- spins = subject.spin(project, categories, branch_name)
+ context 'when change contains frontend category' do
+ let(:categories) { [:frontend] }
- expect(spins).to contain_exactly(matching_spin(:frontend, reviewer: frontend_reviewer, maintainer: frontend_maintainer))
+ it 'assigns frontend reviewer and maintainer' do
+ expect(spins).to contain_exactly(matching_spin(:frontend, reviewer: frontend_reviewer, maintainer: frontend_maintainer))
+ end
end
- end
- context 'when change contains QA category' do
- it 'assigns QA reviewer and sets optional QA maintainer' do
- categories = [:qa]
- spins = subject.spin(project, categories, branch_name)
+ context 'when change contains QA category' do
+ let(:categories) { [:qa] }
- expect(spins).to contain_exactly(matching_spin(:qa, reviewer: software_engineer_in_test, optional: :maintainer))
+ it 'assigns QA reviewer' do
+ expect(spins).to contain_exactly(matching_spin(:qa, reviewer: software_engineer_in_test))
+ end
end
- end
- context 'when change contains Engineering Productivity category' do
- it 'assigns Engineering Productivity reviewer and fallback to backend maintainer' do
- categories = [:engineering_productivity]
- spins = subject.spin(project, categories, branch_name)
+ context 'when change contains Engineering Productivity category' do
+ let(:categories) { [:engineering_productivity] }
- expect(spins).to contain_exactly(matching_spin(:engineering_productivity, reviewer: engineering_productivity_reviewer, maintainer: backend_maintainer))
+ it 'assigns Engineering Productivity reviewer and fallback to backend maintainer' do
+ expect(spins).to contain_exactly(matching_spin(:engineering_productivity, reviewer: engineering_productivity_reviewer, maintainer: backend_maintainer))
+ end
+ end
+
+ context 'when change contains test category' do
+ let(:categories) { [:test] }
+
+ it 'assigns corresponding SET' do
+ expect(spins).to contain_exactly(matching_spin(:test, reviewer: software_engineer_in_test))
+ end
end
end
- context 'when change contains test category' do
- it 'assigns corresponding SET and sets optional test maintainer' do
- categories = [:test]
- spins = subject.spin(project, categories, branch_name)
+ context 'when timezone_experiment == true' do
+ let(:timezone_experiment) { true }
+
+ context 'when change contains backend category' do
+ let(:categories) { [:backend] }
+
+ it 'assigns backend reviewer and maintainer' do
+ expect(spins).to contain_exactly(matching_spin(:backend, reviewer: engineering_productivity_reviewer, maintainer: backend_maintainer))
+ end
+
+ context 'when teammate is not in a good timezone' do
+ before do
+ backend_maintainer[:tz_offset_hours] = 5.0
+ end
- expect(spins).to contain_exactly(matching_spin(:test, reviewer: software_engineer_in_test, optional: :maintainer))
+ it 'assigns backend reviewer and no maintainer' do
+ expect(spins).to contain_exactly(matching_spin(:backend, reviewer: engineering_productivity_reviewer, maintainer: nil))
+ end
+ end
+ end
+
+ context 'when change includes a category with timezone disabled' do
+ let(:categories) { [:backend] }
+
+ before do
+ stub_const("#{described_class}::INCLUDE_TIMEZONE_FOR_CATEGORY", backend: false)
+ end
+
+ it 'assigns backend reviewer and maintainer' do
+ expect(spins).to contain_exactly(matching_spin(:backend, reviewer: engineering_productivity_reviewer, maintainer: backend_maintainer))
+ end
+
+ context 'when teammate is not in a good timezone' do
+ before do
+ backend_maintainer[:tz_offset_hours] = 5.0
+ end
+
+ it 'assigns backend reviewer and maintainer' do
+ expect(spins).to contain_exactly(matching_spin(:backend, reviewer: engineering_productivity_reviewer, maintainer: backend_maintainer))
+ end
+ end
end
end
end
@@ -217,51 +286,83 @@ describe Gitlab::Danger::Roulette do
end
describe '#spin_for_person' do
- let(:person1) { Gitlab::Danger::Teammate.new('username' => 'rymai') }
- let(:person2) { Gitlab::Danger::Teammate.new('username' => 'godfat') }
- let(:author) { Gitlab::Danger::Teammate.new('username' => 'filipa') }
- let(:ooo) { Gitlab::Danger::Teammate.new('username' => 'jacopo-beschi') }
- let(:no_capacity) { Gitlab::Danger::Teammate.new('username' => 'uncharged') }
+ let(:person_tz_offset_hours) { 0.0 }
+ let(:person1) do
+ Gitlab::Danger::Teammate.new(
+ 'username' => 'rymai',
+ 'available' => true,
+ 'tz_offset_hours' => person_tz_offset_hours
+ )
+ end
+ let(:person2) do
+ Gitlab::Danger::Teammate.new(
+ 'username' => 'godfat',
+ 'available' => true,
+ 'tz_offset_hours' => person_tz_offset_hours)
+ end
+ let(:author) do
+ Gitlab::Danger::Teammate.new(
+ 'username' => 'filipa',
+ 'available' => true,
+ 'tz_offset_hours' => 0.0)
+ end
+ let(:unavailable) do
+ Gitlab::Danger::Teammate.new(
+ 'username' => 'jacopo-beschi',
+ 'available' => false,
+ 'tz_offset_hours' => 0.0)
+ end
before do
- stub_person_status(person1, message: 'making GitLab magic')
- stub_person_status(person2, message: 'making GitLab magic')
- stub_person_status(ooo, message: 'OOO till 15th')
- stub_person_status(no_capacity, message: 'At capacity for the next few days', emoji: 'red_circle')
- # we don't stub Filipa, as she is the author and
- # we should not fire request checking for her
-
allow(subject).to receive_message_chain(:gitlab, :mr_author).and_return(author.username)
end
- it 'returns a random person' do
- persons = [person1, person2]
+ (-4..4).each do |utc_offset|
+ context "when local hour for person is #{10 + utc_offset} (offset: #{utc_offset})" do
+ let(:person_tz_offset_hours) { utc_offset }
- selected = subject.spin_for_person(persons, random: Random.new)
+ [false, true].each do |timezone_experiment|
+ context "with timezone_experiment == #{timezone_experiment}" do
+ it 'returns a random person' do
+ persons = [person1, person2]
- expect(selected.username).to be_in(persons.map(&:username))
- end
+ selected = subject.spin_for_person(persons, random: Random.new, timezone_experiment: timezone_experiment)
- it 'excludes OOO persons' do
- expect(subject.spin_for_person([ooo], random: Random.new)).to be_nil
+ expect(selected.username).to be_in(persons.map(&:username))
+ end
+ end
+ end
+ end
end
- it 'excludes mr.author' do
- expect(subject.spin_for_person([author], random: Random.new)).to be_nil
+ ((-12..-5).to_a + (5..12).to_a).each do |utc_offset|
+ context "when local hour for person is #{10 + utc_offset} (offset: #{utc_offset})" do
+ let(:person_tz_offset_hours) { utc_offset }
+
+ [false, true].each do |timezone_experiment|
+ context "with timezone_experiment == #{timezone_experiment}" do
+ it 'returns a random person or nil' do
+ persons = [person1, person2]
+
+ selected = subject.spin_for_person(persons, random: Random.new, timezone_experiment: timezone_experiment)
+
+ if timezone_experiment
+ expect(selected).to be_nil
+ else
+ expect(selected.username).to be_in(persons.map(&:username))
+ end
+ end
+ end
+ end
+ end
end
- it 'excludes person with no capacity' do
- expect(subject.spin_for_person([no_capacity], random: Random.new)).to be_nil
+ it 'excludes unavailable persons' do
+ expect(subject.spin_for_person([unavailable], random: Random.new)).to be_nil
end
- end
- private
-
- def stub_person_status(person, message: 'dummy message', emoji: 'unicorn')
- body = { message: message, emoji: emoji }.to_json
-
- WebMock
- .stub_request(:get, "https://gitlab.com/api/v4/users/#{person.username}/status")
- .to_return(body: body)
+ it 'excludes mr.author' do
+ expect(subject.spin_for_person([author], random: Random.new)).to be_nil
+ end
end
end
diff --git a/spec/lib/gitlab/danger/sidekiq_queues_spec.rb b/spec/lib/gitlab/danger/sidekiq_queues_spec.rb
new file mode 100644
index 00000000000..7dd1a2e6924
--- /dev/null
+++ b/spec/lib/gitlab/danger/sidekiq_queues_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+require_relative 'danger_spec_helper'
+
+require 'gitlab/danger/sidekiq_queues'
+
+RSpec.describe Gitlab::Danger::SidekiqQueues do
+ using RSpec::Parameterized::TableSyntax
+ include DangerSpecHelper
+
+ let(:fake_git) { double('fake-git') }
+ let(:fake_danger) { new_fake_danger.include(described_class) }
+
+ subject(:sidekiq_queues) { fake_danger.new(git: fake_git) }
+
+ describe '#changed_queue_files' do
+ where(:modified_files, :changed_queue_files) do
+ %w(app/workers/all_queues.yml ee/app/workers/all_queues.yml foo) | %w(app/workers/all_queues.yml ee/app/workers/all_queues.yml)
+ %w(app/workers/all_queues.yml ee/app/workers/all_queues.yml) | %w(app/workers/all_queues.yml ee/app/workers/all_queues.yml)
+ %w(app/workers/all_queues.yml foo) | %w(app/workers/all_queues.yml)
+ %w(ee/app/workers/all_queues.yml foo) | %w(ee/app/workers/all_queues.yml)
+ %w(foo) | %w()
+ %w() | %w()
+ end
+
+ with_them do
+ it do
+ allow(fake_git).to receive(:modified_files).and_return(modified_files)
+
+ expect(sidekiq_queues.changed_queue_files).to match_array(changed_queue_files)
+ end
+ end
+ end
+
+ describe '#added_queue_names' do
+ it 'returns queue names added by this change' do
+ old_queues = { post_receive: nil }
+
+ allow(sidekiq_queues).to receive(:old_queues).and_return(old_queues)
+ allow(sidekiq_queues).to receive(:new_queues).and_return(old_queues.merge(merge: nil, process_commit: nil))
+
+ expect(sidekiq_queues.added_queue_names).to contain_exactly(:merge, :process_commit)
+ end
+ end
+
+ describe '#changed_queue_names' do
+ it 'returns names for queues whose attributes were changed' do
+ old_queues = {
+ merge: { name: :merge, urgency: :low },
+ post_receive: { name: :post_receive, urgency: :high },
+ process_commit: { name: :process_commit, urgency: :high }
+ }
+
+ new_queues = old_queues.merge(mailers: { name: :mailers, urgency: :high },
+ post_receive: { name: :post_receive, urgency: :low },
+ process_commit: { name: :process_commit, urgency: :low })
+
+ allow(sidekiq_queues).to receive(:old_queues).and_return(old_queues)
+ allow(sidekiq_queues).to receive(:new_queues).and_return(new_queues)
+
+ expect(sidekiq_queues.changed_queue_names).to contain_exactly(:post_receive, :process_commit)
+ end
+
+ it 'ignores removed queues' do
+ old_queues = {
+ merge: { name: :merge, urgency: :low },
+ post_receive: { name: :post_receive, urgency: :high }
+ }
+
+ new_queues = {
+ post_receive: { name: :post_receive, urgency: :low }
+ }
+
+ allow(sidekiq_queues).to receive(:old_queues).and_return(old_queues)
+ allow(sidekiq_queues).to receive(:new_queues).and_return(new_queues)
+
+ expect(sidekiq_queues.changed_queue_names).to contain_exactly(:post_receive)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/danger/teammate_spec.rb b/spec/lib/gitlab/danger/teammate_spec.rb
index ea5aecbc597..a0540a9fbf5 100644
--- a/spec/lib/gitlab/danger/teammate_spec.rb
+++ b/spec/lib/gitlab/danger/teammate_spec.rb
@@ -2,14 +2,27 @@
require 'fast_spec_helper'
+require 'timecop'
require 'rspec-parameterized'
require 'gitlab/danger/teammate'
-describe Gitlab::Danger::Teammate do
+RSpec.describe Gitlab::Danger::Teammate do
+ using RSpec::Parameterized::TableSyntax
+
subject { described_class.new(options.stringify_keys) }
- let(:options) { { username: 'luigi', projects: projects, role: role } }
+ let(:tz_offset_hours) { 2.0 }
+ let(:options) do
+ {
+ username: 'luigi',
+ projects: projects,
+ role: role,
+ markdown_name: '[Luigi](https://gitlab.com/luigi) (`@luigi`)',
+ tz_offset_hours: tz_offset_hours
+ }
+ end
+ let(:capabilities) { ['reviewer backend'] }
let(:projects) { { project => capabilities } }
let(:role) { 'Engineer, Manage' }
let(:labels) { [] }
@@ -115,78 +128,72 @@ describe Gitlab::Danger::Teammate do
end
end
- describe '#status' do
- let(:capabilities) { ['dish washing'] }
-
- context 'with empty cache' do
- context 'for successful request' do
- it 'returns the response' do
- mock_status = double(does_not: 'matter')
- expect(Gitlab::Danger::RequestHelper).to receive(:http_get_json)
- .and_return(mock_status)
+ describe '#local_hour' do
+ around do |example|
+ Timecop.freeze(Time.utc(2020, 6, 23, 10)) { example.run }
+ end
- expect(subject.status).to be mock_status
- end
+ context 'when author is given' do
+ where(:tz_offset_hours, :expected_local_hour) do
+ -12 | 22
+ -10 | 0
+ 2 | 12
+ 4 | 14
+ 12 | 22
end
- context 'for failing request' do
- it 'returns nil' do
- expect(Gitlab::Danger::RequestHelper).to receive(:http_get_json)
- .and_raise(Gitlab::Danger::RequestHelper::HTTPError.new)
-
- expect(subject.status).to be nil
+ with_them do
+ it 'returns the correct local_hour' do
+ expect(subject.local_hour).to eq(expected_local_hour)
end
end
end
+ end
- context 'with filled cache' do
- it 'returns the cached response' do
- mock_status = double(does_not: 'matter')
- expect(Gitlab::Danger::RequestHelper).to receive(:http_get_json)
- .and_return(mock_status)
- subject.status
-
- expect(Gitlab::Danger::RequestHelper).not_to receive(:http_get_json)
- expect(subject.status).to be mock_status
+ describe '#markdown_name' do
+ context 'when timezone_experiment == false' do
+ it 'returns markdown name as-is' do
+ expect(subject.markdown_name).to eq(options[:markdown_name])
+ expect(subject.markdown_name(timezone_experiment: false)).to eq(options[:markdown_name])
end
end
- end
- describe '#available?' do
- using RSpec::Parameterized::TableSyntax
-
- let(:capabilities) { ['dry head'] }
-
- where(:status, :result) do
- {} | true
- { message: 'dear reader' } | true
- { message: 'OOO: massage' } | false
- { message: 'love it SOOO much' } | false
- { emoji: 'red_circle' } | false
- { emoji: 'palm_tree' } | false
- { emoji: 'beach' } | false
- { emoji: 'beach_umbrella' } | false
- { emoji: 'beach_with_umbrella' } | false
- { emoji: nil } | true
- { emoji: '' } | true
- { emoji: 'dancer' } | true
- end
+ context 'when timezone_experiment == true' do
+ it 'returns markdown name with timezone info' do
+ expect(subject.markdown_name(timezone_experiment: true)).to eq("#{options[:markdown_name]} (UTC+2)")
+ end
+
+ context 'when offset is 1.5' do
+ let(:tz_offset_hours) { 1.5 }
- with_them do
- before do
- expect(Gitlab::Danger::RequestHelper).to receive(:http_get_json)
- .and_return(status&.stringify_keys)
+ it 'returns markdown name with timezone info, not truncated' do
+ expect(subject.markdown_name(timezone_experiment: true)).to eq("#{options[:markdown_name]} (UTC+1.5)")
+ end
end
- it { expect(subject.available?).to be result }
- end
+ context 'when author is given' do
+ where(:tz_offset_hours, :author_offset, :diff_text) do
+ -12 | -10 | "2 hours behind `@mario`"
+ -10 | -12 | "2 hours ahead `@mario`"
+ -10 | 2 | "12 hours behind `@mario`"
+ 2 | 4 | "2 hours behind `@mario`"
+ 4 | 2 | "2 hours ahead `@mario`"
+ 2 | 3 | "1 hour behind `@mario`"
+ 3 | 2 | "1 hour ahead `@mario`"
+ 2 | 2 | "same timezone as `@mario`"
+ end
- it 'returns true if request fails' do
- expect(Gitlab::Danger::RequestHelper)
- .to receive(:http_get_json)
- .and_raise(Gitlab::Danger::RequestHelper::HTTPError.new)
+ with_them do
+ it 'returns markdown name with timezone info' do
+ author = described_class.new(options.merge(username: 'mario', tz_offset_hours: author_offset).stringify_keys)
- expect(subject.available?).to be true
+ floored_offset_hours = subject.__send__(:floored_offset_hours)
+ utc_offset = floored_offset_hours >= 0 ? "+#{floored_offset_hours}" : floored_offset_hours
+
+ expect(subject.markdown_name(timezone_experiment: true, author: author)).to eq("#{options[:markdown_name]} (UTC#{utc_offset}, #{diff_text})")
+ end
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/data_builder/alert_spec.rb b/spec/lib/gitlab/data_builder/alert_spec.rb
index b881fb8139b..6c3dc3cd8b4 100644
--- a/spec/lib/gitlab/data_builder/alert_spec.rb
+++ b/spec/lib/gitlab/data_builder/alert_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DataBuilder::Alert do
+RSpec.describe Gitlab::DataBuilder::Alert do
let_it_be(:project) { create(:project) }
let_it_be(:alert) { create(:alert_management_alert, project: project) }
diff --git a/spec/lib/gitlab/data_builder/build_spec.rb b/spec/lib/gitlab/data_builder/build_spec.rb
index da27125c9a6..cfaaf849b09 100644
--- a/spec/lib/gitlab/data_builder/build_spec.rb
+++ b/spec/lib/gitlab/data_builder/build_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DataBuilder::Build do
+RSpec.describe Gitlab::DataBuilder::Build do
let(:runner) { create(:ci_runner, :instance) }
let(:user) { create(:user) }
let(:build) { create(:ci_build, :running, runner: runner, user: user) }
diff --git a/spec/lib/gitlab/data_builder/deployment_spec.rb b/spec/lib/gitlab/data_builder/deployment_spec.rb
index 42d7329494d..57bde6262a9 100644
--- a/spec/lib/gitlab/data_builder/deployment_spec.rb
+++ b/spec/lib/gitlab/data_builder/deployment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DataBuilder::Deployment do
+RSpec.describe Gitlab::DataBuilder::Deployment do
describe '.build' do
it 'returns the object kind for a deployment' do
deployment = build(:deployment)
diff --git a/spec/lib/gitlab/data_builder/note_spec.rb b/spec/lib/gitlab/data_builder/note_spec.rb
index 4b799c23de8..90ca5430526 100644
--- a/spec/lib/gitlab/data_builder/note_spec.rb
+++ b/spec/lib/gitlab/data_builder/note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DataBuilder::Note do
+RSpec.describe Gitlab::DataBuilder::Note do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:data) { described_class.build(note, user) }
diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb
index 519f5873d75..4e0cc8a1fa9 100644
--- a/spec/lib/gitlab/data_builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DataBuilder::Pipeline do
+RSpec.describe Gitlab::DataBuilder::Pipeline do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb
index cbc03fc38eb..7eb81a880bf 100644
--- a/spec/lib/gitlab/data_builder/push_spec.rb
+++ b/spec/lib/gitlab/data_builder/push_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DataBuilder::Push do
+RSpec.describe Gitlab::DataBuilder::Push do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/data_builder/wiki_page_spec.rb b/spec/lib/gitlab/data_builder/wiki_page_spec.rb
index ae338e30a69..ec768cf9719 100644
--- a/spec/lib/gitlab/data_builder/wiki_page_spec.rb
+++ b/spec/lib/gitlab/data_builder/wiki_page_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DataBuilder::WikiPage do
+RSpec.describe Gitlab::DataBuilder::WikiPage do
let_it_be(:project) { create(:project, :repository, :wiki_repo) }
let(:wiki_page) { create(:wiki_page, wiki: project.wiki) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/database/background_migration_job_spec.rb b/spec/lib/gitlab/database/background_migration_job_spec.rb
new file mode 100644
index 00000000000..40f47325be3
--- /dev/null
+++ b/spec/lib/gitlab/database/background_migration_job_spec.rb
@@ -0,0 +1,125 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::BackgroundMigrationJob do
+ it_behaves_like 'having unique enum values'
+
+ describe '.for_migration_execution' do
+ let!(:job1) { create(:background_migration_job) }
+ let!(:job2) { create(:background_migration_job, arguments: ['hi', 2]) }
+ let!(:job3) { create(:background_migration_job, class_name: 'OtherJob', arguments: ['hi', 2]) }
+
+ it 'returns jobs matching class_name and arguments' do
+ relation = described_class.for_migration_execution('TestJob', ['hi', 2])
+
+ expect(relation.count).to eq(1)
+ expect(relation.first).to have_attributes(class_name: 'TestJob', arguments: ['hi', 2])
+ end
+
+ it 'normalizes class names by removing leading ::' do
+ relation = described_class.for_migration_execution('::TestJob', ['hi', 2])
+
+ expect(relation.count).to eq(1)
+ expect(relation.first).to have_attributes(class_name: 'TestJob', arguments: ['hi', 2])
+ end
+ end
+
+ describe '.for_partitioning_migration' do
+ let!(:job1) { create(:background_migration_job, arguments: [1, 100, 'other_table']) }
+ let!(:job2) { create(:background_migration_job, arguments: [1, 100, 'audit_events']) }
+ let!(:job3) { create(:background_migration_job, class_name: 'OtherJob', arguments: [1, 100, 'audit_events']) }
+
+ it 'returns jobs matching class_name and the table_name job argument' do
+ relation = described_class.for_partitioning_migration('TestJob', 'audit_events')
+
+ expect(relation.count).to eq(1)
+ expect(relation.first).to have_attributes(class_name: 'TestJob', arguments: [1, 100, 'audit_events'])
+ end
+
+ it 'normalizes class names by removing leading ::' do
+ relation = described_class.for_partitioning_migration('::TestJob', 'audit_events')
+
+ expect(relation.count).to eq(1)
+ expect(relation.first).to have_attributes(class_name: 'TestJob', arguments: [1, 100, 'audit_events'])
+ end
+ end
+
+ describe '.mark_all_as_succeeded' do
+ let!(:job1) { create(:background_migration_job, arguments: [1, 100]) }
+ let!(:job2) { create(:background_migration_job, arguments: [1, 100]) }
+ let!(:job3) { create(:background_migration_job, arguments: [101, 200]) }
+ let!(:job4) { create(:background_migration_job, class_name: 'OtherJob', arguments: [1, 100]) }
+
+ it 'marks all matching jobs as succeeded' do
+ expect { described_class.mark_all_as_succeeded('TestJob', [1, 100]) }
+ .to change { described_class.succeeded.count }.from(0).to(2)
+
+ expect(job1.reload).to be_succeeded
+ expect(job2.reload).to be_succeeded
+ expect(job3.reload).to be_pending
+ expect(job4.reload).to be_pending
+ end
+
+ it 'normalizes class_names by removing leading ::' do
+ expect { described_class.mark_all_as_succeeded('::TestJob', [1, 100]) }
+ .to change { described_class.succeeded.count }.from(0).to(2)
+
+ expect(job1.reload).to be_succeeded
+ expect(job2.reload).to be_succeeded
+ expect(job3.reload).to be_pending
+ expect(job4.reload).to be_pending
+ end
+
+ context 'when previous matching jobs have already succeeded' do
+ let(:initial_time) { Time.now.round }
+ let!(:job1) { create(:background_migration_job, :succeeded, created_at: initial_time, updated_at: initial_time) }
+
+ it 'does not update non-pending jobs' do
+ Timecop.freeze(initial_time + 1.day) do
+ expect { described_class.mark_all_as_succeeded('TestJob', [1, 100]) }
+ .to change { described_class.succeeded.count }.from(1).to(2)
+ end
+
+ expect(job1.reload.updated_at).to eq(initial_time)
+ expect(job2.reload).to be_succeeded
+ expect(job3.reload).to be_pending
+ expect(job4.reload).to be_pending
+ end
+ end
+ end
+
+ describe '#class_name=' do
+ context 'when the class_name is given without the leading ::' do
+ it 'sets the class_name to the given value' do
+ job = described_class.new(class_name: 'TestJob')
+
+ expect(job.class_name).to eq('TestJob')
+ end
+ end
+
+ context 'when the class_name is given with the leading ::' do
+ it 'removes the leading :: when setting the class_name' do
+ job = described_class.new(class_name: '::TestJob')
+
+ expect(job.class_name).to eq('TestJob')
+ end
+ end
+
+ context 'when the value is nil' do
+ it 'sets the class_name to nil' do
+ job = described_class.new(class_name: nil)
+
+ expect(job.class_name).to be_nil
+ end
+ end
+
+ context 'when the values is blank' do
+ it 'sets the class_name to the given value' do
+ job = described_class.new(class_name: '')
+
+ expect(job.class_name).to eq('')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/batch_count_spec.rb b/spec/lib/gitlab/database/batch_count_spec.rb
index e7cb53f2dbd..656501dbf56 100644
--- a/spec/lib/gitlab/database/batch_count_spec.rb
+++ b/spec/lib/gitlab/database/batch_count_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::BatchCount do
+RSpec.describe Gitlab::Database::BatchCount do
let_it_be(:fallback) { ::Gitlab::Database::BatchCounter::FALLBACK }
let_it_be(:small_batch_size) { ::Gitlab::Database::BatchCounter::MIN_REQUIRED_BATCH_SIZE - 1 }
let(:model) { Issue }
diff --git a/spec/lib/gitlab/database/connection_timer_spec.rb b/spec/lib/gitlab/database/connection_timer_spec.rb
index c9e9d770343..2b6746bae07 100644
--- a/spec/lib/gitlab/database/connection_timer_spec.rb
+++ b/spec/lib/gitlab/database/connection_timer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::ConnectionTimer do
+RSpec.describe Gitlab::Database::ConnectionTimer do
let(:current_clock_value) { 1234.56 }
before do
diff --git a/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb b/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb
index 111833a506a..390620379d6 100644
--- a/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb
+++ b/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::Count::ExactCountStrategy do
+RSpec.describe Gitlab::Database::Count::ExactCountStrategy do
before do
create_list(:project, 3)
create(:identity)
diff --git a/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb b/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
index 08032d19d14..324ed498abc 100644
--- a/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
+++ b/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::Count::ReltuplesCountStrategy do
+RSpec.describe Gitlab::Database::Count::ReltuplesCountStrategy do
before do
create_list(:project, 3)
create(:identity)
diff --git a/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb b/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
index 0c480709c22..e488bf5ee4c 100644
--- a/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
+++ b/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::Count::TablesampleCountStrategy do
+RSpec.describe Gitlab::Database::Count::TablesampleCountStrategy do
before do
create_list(:project, 3)
create(:identity)
diff --git a/spec/lib/gitlab/database/count_spec.rb b/spec/lib/gitlab/database/count_spec.rb
index 2469ce482e7..d65413c2a00 100644
--- a/spec/lib/gitlab/database/count_spec.rb
+++ b/spec/lib/gitlab/database/count_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::Count do
+RSpec.describe Gitlab::Database::Count do
before do
create_list(:project, 3)
create(:identity)
diff --git a/spec/lib/gitlab/database/custom_structure_spec.rb b/spec/lib/gitlab/database/custom_structure_spec.rb
index f03b5ed0a7f..beda9df3684 100644
--- a/spec/lib/gitlab/database/custom_structure_spec.rb
+++ b/spec/lib/gitlab/database/custom_structure_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::CustomStructure do
+RSpec.describe Gitlab::Database::CustomStructure do
let_it_be(:structure) { described_class.new }
let_it_be(:filepath) { Rails.root.join(described_class::CUSTOM_DUMP_FILE) }
let_it_be(:file_header) do
diff --git a/spec/lib/gitlab/database/dynamic_model_helpers_spec.rb b/spec/lib/gitlab/database/dynamic_model_helpers_spec.rb
new file mode 100644
index 00000000000..23ad621d0ee
--- /dev/null
+++ b/spec/lib/gitlab/database/dynamic_model_helpers_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::DynamicModelHelpers do
+ describe '#define_batchable_model' do
+ subject { including_class.new.define_batchable_model(table_name) }
+
+ let(:including_class) { Class.new.include(described_class) }
+ let(:table_name) { 'projects' }
+
+ it 'is an ActiveRecord model' do
+ expect(subject.ancestors).to include(ActiveRecord::Base)
+ end
+
+ it 'includes EachBatch' do
+ expect(subject.included_modules).to include(EachBatch)
+ end
+
+ it 'has the correct table name' do
+ expect(subject.table_name).to eq(table_name)
+ end
+
+ it 'has the inheritance type column disable' do
+ expect(subject.inheritance_column).to eq('_type_disabled')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/grant_spec.rb b/spec/lib/gitlab/database/grant_spec.rb
index 02697eb2a16..23aec85d1d0 100644
--- a/spec/lib/gitlab/database/grant_spec.rb
+++ b/spec/lib/gitlab/database/grant_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::Grant do
+RSpec.describe Gitlab::Database::Grant do
describe '.create_and_execute_trigger' do
it 'returns true when the user can create and execute a trigger' do
# We assume the DB/user is set up correctly so that triggers can be
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index bed444ee7c7..48e1c97e97f 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::MigrationHelpers do
+RSpec.describe Gitlab::Database::MigrationHelpers do
let(:model) do
ActiveRecord::Migration.new.extend(described_class)
end
@@ -178,6 +178,19 @@ describe Gitlab::Database::MigrationHelpers do
model.remove_concurrent_index_by_name(:users, "index_x_by_y")
end
+
+ it 'removes the index with keyword arguments' do
+ expect(model).to receive(:remove_index)
+ .with(:users, { algorithm: :concurrently, name: "index_x_by_y" })
+
+ model.remove_concurrent_index_by_name(:users, name: "index_x_by_y")
+ end
+
+ it 'raises an error if the index is blank' do
+ expect do
+ model.remove_concurrent_index_by_name(:users, wrong_key: "index_x_by_y")
+ end.to raise_error 'remove_concurrent_index_by_name must get an index name as the second argument'
+ end
end
end
end
@@ -690,12 +703,28 @@ describe Gitlab::Database::MigrationHelpers do
model.rename_column_concurrently(:users, :old, :new)
end
+ context 'with existing records and type casting' do
+ let(:trigger_name) { model.rename_trigger_name(:users, :id, :new) }
+ let(:user) { create(:user) }
+
+ it 'copies the value to the new column using the type_cast_function', :aggregate_failures do
+ expect(model).to receive(:copy_indexes).with(:users, :id, :new)
+ expect(model).to receive(:add_not_null_constraint).with(:users, :new)
+ expect(model).to receive(:execute).with("UPDATE \"users\" SET \"new\" = cast_to_jsonb_with_default(\"users\".\"id\") WHERE \"users\".\"id\" >= #{user.id}")
+ expect(model).to receive(:execute).with("DROP TRIGGER IF EXISTS #{trigger_name}\nON \"users\"\n")
+ expect(model).to receive(:execute).with("CREATE TRIGGER #{trigger_name}\nBEFORE INSERT OR UPDATE\nON \"users\"\nFOR EACH ROW\nEXECUTE PROCEDURE #{trigger_name}()\n")
+ expect(model).to receive(:execute).with("CREATE OR REPLACE FUNCTION #{trigger_name}()\nRETURNS trigger AS\n$BODY$\nBEGIN\n NEW.\"new\" := NEW.\"id\";\n RETURN NEW;\nEND;\n$BODY$\nLANGUAGE 'plpgsql'\nVOLATILE\n")
+
+ model.rename_column_concurrently(:users, :id, :new, type_cast_function: 'cast_to_jsonb_with_default')
+ end
+ end
+
it 'passes the batch_column_name' do
expect(model).to receive(:column_exists?).with(:users, :other_batch_column).and_return(true)
expect(model).to receive(:check_trigger_permissions!).and_return(true)
expect(model).to receive(:create_column_from).with(
- :users, :old, :new, type: nil, batch_column_name: :other_batch_column
+ :users, :old, :new, type: nil, batch_column_name: :other_batch_column, type_cast_function: nil
).and_return(true)
expect(model).to receive(:install_rename_triggers).and_return(true)
@@ -703,6 +732,14 @@ describe Gitlab::Database::MigrationHelpers do
model.rename_column_concurrently(:users, :old, :new, batch_column_name: :other_batch_column)
end
+ it 'passes the type_cast_function' do
+ expect(model).to receive(:create_column_from).with(
+ :users, :old, :new, type: nil, batch_column_name: :id, type_cast_function: 'JSON'
+ ).and_return(true)
+
+ model.rename_column_concurrently(:users, :old, :new, type_cast_function: 'JSON')
+ end
+
it 'raises an error with invalid batch_column_name' do
expect do
model.rename_column_concurrently(:users, :old, :new, batch_column_name: :invalid)
@@ -866,10 +903,19 @@ describe Gitlab::Database::MigrationHelpers do
describe '#change_column_type_concurrently' do
it 'changes the column type' do
expect(model).to receive(:rename_column_concurrently)
- .with('users', 'username', 'username_for_type_change', type: :text)
+ .with('users', 'username', 'username_for_type_change', type: :text, type_cast_function: nil)
model.change_column_type_concurrently('users', 'username', :text)
end
+
+ context 'with type cast' do
+ it 'changes the column type with casting the value to the new type' do
+ expect(model).to receive(:rename_column_concurrently)
+ .with('users', 'username', 'username_for_type_change', type: :text, type_cast_function: 'JSON')
+
+ model.change_column_type_concurrently('users', 'username', :text, type_cast_function: 'JSON')
+ end
+ end
end
describe '#cleanup_concurrent_column_type_change' do
@@ -1215,166 +1261,6 @@ describe Gitlab::Database::MigrationHelpers do
end
end
- describe '#bulk_queue_background_migration_jobs_by_range' do
- context 'when the model has an ID column' do
- let!(:id1) { create(:user).id }
- let!(:id2) { create(:user).id }
- let!(:id3) { create(:user).id }
-
- before do
- User.class_eval do
- include EachBatch
- end
- end
-
- context 'with enough rows to bulk queue jobs more than once' do
- before do
- stub_const('Gitlab::Database::MigrationHelpers::BACKGROUND_MIGRATION_JOB_BUFFER_SIZE', 1)
- end
-
- it 'queues jobs correctly' do
- Sidekiq::Testing.fake! do
- model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
-
- expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]])
- expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]])
- end
- end
-
- it 'queues jobs in groups of buffer size 1' do
- expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([['FooJob', [id1, id2]]])
- expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([['FooJob', [id3, id3]]])
-
- model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
- end
- end
-
- context 'with not enough rows to bulk queue jobs more than once' do
- it 'queues jobs correctly' do
- Sidekiq::Testing.fake! do
- model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
-
- expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]])
- expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]])
- end
- end
-
- it 'queues jobs in bulk all at once (big buffer size)' do
- expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([['FooJob', [id1, id2]],
- ['FooJob', [id3, id3]]])
-
- model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
- end
- end
-
- context 'without specifying batch_size' do
- it 'queues jobs correctly' do
- Sidekiq::Testing.fake! do
- model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob')
-
- expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3]])
- end
- end
- end
- end
-
- context "when the model doesn't have an ID column" do
- it 'raises error (for now)' do
- expect do
- model.bulk_queue_background_migration_jobs_by_range(ProjectAuthorization, 'FooJob')
- end.to raise_error(StandardError, /does not have an ID/)
- end
- end
- end
-
- describe '#queue_background_migration_jobs_by_range_at_intervals' do
- context 'when the model has an ID column' do
- let!(:id1) { create(:user).id }
- let!(:id2) { create(:user).id }
- let!(:id3) { create(:user).id }
-
- around do |example|
- Timecop.freeze { example.run }
- end
-
- before do
- User.class_eval do
- include EachBatch
- end
- end
-
- it 'returns the final expected delay' do
- Sidekiq::Testing.fake! do
- final_delay = model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, batch_size: 2)
-
- expect(final_delay.to_f).to eq(20.minutes.to_f)
- end
- end
-
- it 'returns zero when nothing gets queued' do
- Sidekiq::Testing.fake! do
- final_delay = model.queue_background_migration_jobs_by_range_at_intervals(User.none, 'FooJob', 10.minutes)
-
- expect(final_delay).to eq(0)
- end
- end
-
- context 'with batch_size option' do
- it 'queues jobs correctly' do
- Sidekiq::Testing.fake! do
- model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, batch_size: 2)
-
- expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]])
- expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f)
- expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]])
- expect(BackgroundMigrationWorker.jobs[1]['at']).to eq(20.minutes.from_now.to_f)
- end
- end
- end
-
- context 'without batch_size option' do
- it 'queues jobs correctly' do
- Sidekiq::Testing.fake! do
- model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes)
-
- expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3]])
- expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f)
- end
- end
- end
-
- context 'with other_job_arguments option' do
- it 'queues jobs correctly' do
- Sidekiq::Testing.fake! do
- model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, other_job_arguments: [1, 2])
-
- expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3, 1, 2]])
- expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f)
- end
- end
- end
-
- context 'with initial_delay option' do
- it 'queues jobs correctly' do
- Sidekiq::Testing.fake! do
- model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, other_job_arguments: [1, 2], initial_delay: 10.minutes)
-
- expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3, 1, 2]])
- expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(20.minutes.from_now.to_f)
- end
- end
- end
- end
-
- context "when the model doesn't have an ID column" do
- it 'raises error (for now)' do
- expect do
- model.queue_background_migration_jobs_by_range_at_intervals(ProjectAuthorization, 'FooJob', 10.seconds)
- end.to raise_error(StandardError, /does not have an ID/)
- end
- end
- end
-
describe '#change_column_type_using_background_migration' do
let!(:issue) { create(:issue, :closed, closed_at: Time.zone.now) }
@@ -1485,26 +1371,6 @@ describe Gitlab::Database::MigrationHelpers do
end
end
- describe '#perform_background_migration_inline?' do
- it 'returns true in a test environment' do
- stub_rails_env('test')
-
- expect(model.perform_background_migration_inline?).to eq(true)
- end
-
- it 'returns true in a development environment' do
- stub_rails_env('development')
-
- expect(model.perform_background_migration_inline?).to eq(true)
- end
-
- it 'returns false in a production environment' do
- stub_rails_env('production')
-
- expect(model.perform_background_migration_inline?).to eq(false)
- end
- end
-
describe '#index_exists_by_name?' do
it 'returns true if an index exists' do
ActiveRecord::Base.connection.execute(
@@ -1973,62 +1839,6 @@ describe Gitlab::Database::MigrationHelpers do
end
end
- describe '#migrate_async' do
- it 'calls BackgroundMigrationWorker.perform_async' do
- expect(BackgroundMigrationWorker).to receive(:perform_async).with("Class", "hello", "world")
-
- model.migrate_async("Class", "hello", "world")
- end
-
- it 'pushes a context with the current class name as caller_id' do
- expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
-
- model.migrate_async('Class', 'hello', 'world')
- end
- end
-
- describe '#migrate_in' do
- it 'calls BackgroundMigrationWorker.perform_in' do
- expect(BackgroundMigrationWorker).to receive(:perform_in).with(10.minutes, 'Class', 'Hello', 'World')
-
- model.migrate_in(10.minutes, 'Class', 'Hello', 'World')
- end
-
- it 'pushes a context with the current class name as caller_id' do
- expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
-
- model.migrate_in(10.minutes, 'Class', 'Hello', 'World')
- end
- end
-
- describe '#bulk_migrate_async' do
- it 'calls BackgroundMigrationWorker.bulk_perform_async' do
- expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([%w(Class hello world)])
-
- model.bulk_migrate_async([%w(Class hello world)])
- end
-
- it 'pushes a context with the current class name as caller_id' do
- expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
-
- model.bulk_migrate_async([%w(Class hello world)])
- end
- end
-
- describe '#bulk_migrate_in' do
- it 'calls BackgroundMigrationWorker.bulk_perform_in_' do
- expect(BackgroundMigrationWorker).to receive(:bulk_perform_in).with(10.minutes, [%w(Class hello world)])
-
- model.bulk_migrate_in(10.minutes, [%w(Class hello world)])
- end
-
- it 'pushes a context with the current class name as caller_id' do
- expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
-
- model.bulk_migrate_in(10.minutes, [%w(Class hello world)])
- end
- end
-
describe '#check_constraint_name' do
it 'returns a valid constraint name' do
name = model.check_constraint_name(:this_is_a_very_long_table_name,
diff --git a/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb b/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb
new file mode 100644
index 00000000000..042ac498373
--- /dev/null
+++ b/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb
@@ -0,0 +1,276 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::Migrations::BackgroundMigrationHelpers do
+ let(:model) do
+ ActiveRecord::Migration.new.extend(described_class)
+ end
+
+ describe '#bulk_queue_background_migration_jobs_by_range' do
+ context 'when the model has an ID column' do
+ let!(:id1) { create(:user).id }
+ let!(:id2) { create(:user).id }
+ let!(:id3) { create(:user).id }
+
+ before do
+ User.class_eval do
+ include EachBatch
+ end
+ end
+
+ context 'with enough rows to bulk queue jobs more than once' do
+ before do
+ stub_const('Gitlab::Database::Migrations::BackgroundMigrationHelpers::BACKGROUND_MIGRATION_JOB_BUFFER_SIZE', 1)
+ end
+
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]])
+ expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]])
+ end
+ end
+
+ it 'queues jobs in groups of buffer size 1' do
+ expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([['FooJob', [id1, id2]]])
+ expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([['FooJob', [id3, id3]]])
+
+ model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
+ end
+ end
+
+ context 'with not enough rows to bulk queue jobs more than once' do
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]])
+ expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]])
+ end
+ end
+
+ it 'queues jobs in bulk all at once (big buffer size)' do
+ expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([['FooJob', [id1, id2]],
+ ['FooJob', [id3, id3]]])
+
+ model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2)
+ end
+ end
+
+ context 'without specifying batch_size' do
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob')
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3]])
+ end
+ end
+ end
+ end
+
+ context "when the model doesn't have an ID column" do
+ it 'raises error (for now)' do
+ expect do
+ model.bulk_queue_background_migration_jobs_by_range(ProjectAuthorization, 'FooJob')
+ end.to raise_error(StandardError, /does not have an ID/)
+ end
+ end
+ end
+
+ describe '#queue_background_migration_jobs_by_range_at_intervals' do
+ context 'when the model has an ID column' do
+ let!(:id1) { create(:user).id }
+ let!(:id2) { create(:user).id }
+ let!(:id3) { create(:user).id }
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ before do
+ User.class_eval do
+ include EachBatch
+ end
+ end
+
+ it 'returns the final expected delay' do
+ Sidekiq::Testing.fake! do
+ final_delay = model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, batch_size: 2)
+
+ expect(final_delay.to_f).to eq(20.minutes.to_f)
+ end
+ end
+
+ it 'returns zero when nothing gets queued' do
+ Sidekiq::Testing.fake! do
+ final_delay = model.queue_background_migration_jobs_by_range_at_intervals(User.none, 'FooJob', 10.minutes)
+
+ expect(final_delay).to eq(0)
+ end
+ end
+
+ context 'with batch_size option' do
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, batch_size: 2)
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]])
+ expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f)
+ expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]])
+ expect(BackgroundMigrationWorker.jobs[1]['at']).to eq(20.minutes.from_now.to_f)
+ end
+ end
+ end
+
+ context 'without batch_size option' do
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes)
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3]])
+ expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f)
+ end
+ end
+ end
+
+ context 'with other_job_arguments option' do
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, other_job_arguments: [1, 2])
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3, 1, 2]])
+ expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f)
+ end
+ end
+ end
+
+ context 'with initial_delay option' do
+ it 'queues jobs correctly' do
+ Sidekiq::Testing.fake! do
+ model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, other_job_arguments: [1, 2], initial_delay: 10.minutes)
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3, 1, 2]])
+ expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(20.minutes.from_now.to_f)
+ end
+ end
+ end
+
+ context 'with track_jobs option' do
+ it 'creates a record for each job in the database' do
+ Sidekiq::Testing.fake! do
+ expect do
+ model.queue_background_migration_jobs_by_range_at_intervals(User, '::FooJob', 10.minutes,
+ other_job_arguments: [1, 2], track_jobs: true)
+ end.to change { Gitlab::Database::BackgroundMigrationJob.count }.from(0).to(1)
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(1)
+
+ tracked_job = Gitlab::Database::BackgroundMigrationJob.first
+
+ expect(tracked_job.class_name).to eq('FooJob')
+ expect(tracked_job.arguments).to eq([id1, id3, 1, 2])
+ expect(tracked_job).to be_pending
+ end
+ end
+ end
+
+ context 'without track_jobs option' do
+ it 'does not create records in the database' do
+ Sidekiq::Testing.fake! do
+ expect do
+ model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, other_job_arguments: [1, 2])
+ end.not_to change { Gitlab::Database::BackgroundMigrationJob.count }
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(1)
+ end
+ end
+ end
+ end
+
+ context "when the model doesn't have an ID column" do
+ it 'raises error (for now)' do
+ expect do
+ model.queue_background_migration_jobs_by_range_at_intervals(ProjectAuthorization, 'FooJob', 10.seconds)
+ end.to raise_error(StandardError, /does not have an ID/)
+ end
+ end
+ end
+
+ describe '#perform_background_migration_inline?' do
+ it 'returns true in a test environment' do
+ stub_rails_env('test')
+
+ expect(model.perform_background_migration_inline?).to eq(true)
+ end
+
+ it 'returns true in a development environment' do
+ stub_rails_env('development')
+
+ expect(model.perform_background_migration_inline?).to eq(true)
+ end
+
+ it 'returns false in a production environment' do
+ stub_rails_env('production')
+
+ expect(model.perform_background_migration_inline?).to eq(false)
+ end
+ end
+
+ describe '#migrate_async' do
+ it 'calls BackgroundMigrationWorker.perform_async' do
+ expect(BackgroundMigrationWorker).to receive(:perform_async).with("Class", "hello", "world")
+
+ model.migrate_async("Class", "hello", "world")
+ end
+
+ it 'pushes a context with the current class name as caller_id' do
+ expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
+
+ model.migrate_async('Class', 'hello', 'world')
+ end
+ end
+
+ describe '#migrate_in' do
+ it 'calls BackgroundMigrationWorker.perform_in' do
+ expect(BackgroundMigrationWorker).to receive(:perform_in).with(10.minutes, 'Class', 'Hello', 'World')
+
+ model.migrate_in(10.minutes, 'Class', 'Hello', 'World')
+ end
+
+ it 'pushes a context with the current class name as caller_id' do
+ expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
+
+ model.migrate_in(10.minutes, 'Class', 'Hello', 'World')
+ end
+ end
+
+ describe '#bulk_migrate_async' do
+ it 'calls BackgroundMigrationWorker.bulk_perform_async' do
+ expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([%w(Class hello world)])
+
+ model.bulk_migrate_async([%w(Class hello world)])
+ end
+
+ it 'pushes a context with the current class name as caller_id' do
+ expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
+
+ model.bulk_migrate_async([%w(Class hello world)])
+ end
+ end
+
+ describe '#bulk_migrate_in' do
+ it 'calls BackgroundMigrationWorker.bulk_perform_in_' do
+ expect(BackgroundMigrationWorker).to receive(:bulk_perform_in).with(10.minutes, [%w(Class hello world)])
+
+ model.bulk_migrate_in(10.minutes, [%w(Class hello world)])
+ end
+
+ it 'pushes a context with the current class name as caller_id' do
+ expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
+
+ model.bulk_migrate_in(10.minutes, [%w(Class hello world)])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/multi_threaded_migration_spec.rb b/spec/lib/gitlab/database/multi_threaded_migration_spec.rb
index 53c001fbc1b..78dd9e88064 100644
--- a/spec/lib/gitlab/database/multi_threaded_migration_spec.rb
+++ b/spec/lib/gitlab/database/multi_threaded_migration_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::MultiThreadedMigration do
+RSpec.describe Gitlab::Database::MultiThreadedMigration do
let(:migration) do
Class.new { include Gitlab::Database::MultiThreadedMigration }.new
end
diff --git a/spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb b/spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb
index dee1d7df1a9..034bf966db7 100644
--- a/spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb
+++ b/spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::ObsoleteIgnoredColumns do
+RSpec.describe Gitlab::Database::ObsoleteIgnoredColumns do
before do
stub_const('Testing', Module.new)
stub_const('Testing::MyBase', Class.new(ActiveRecord::Base))
diff --git a/spec/lib/gitlab/database/partitioning/monthly_strategy_spec.rb b/spec/lib/gitlab/database/partitioning/monthly_strategy_spec.rb
new file mode 100644
index 00000000000..334cac653cf
--- /dev/null
+++ b/spec/lib/gitlab/database/partitioning/monthly_strategy_spec.rb
@@ -0,0 +1,153 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::Partitioning::MonthlyStrategy do
+ describe '#current_partitions' do
+ subject { described_class.new(model, partitioning_key).current_partitions }
+
+ let(:model) { double('model', table_name: table_name) }
+ let(:partitioning_key) { double }
+ let(:table_name) { :partitioned_test }
+
+ before do
+ ActiveRecord::Base.connection.execute(<<~SQL)
+ CREATE TABLE #{table_name}
+ (id serial not null, created_at timestamptz not null, PRIMARY KEY (id, created_at))
+ PARTITION BY RANGE (created_at);
+
+ CREATE TABLE #{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.partitioned_test_000000
+ PARTITION OF #{table_name}
+ FOR VALUES FROM (MINVALUE) TO ('2020-05-01');
+
+ CREATE TABLE #{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.partitioned_test_202005
+ PARTITION OF #{table_name}
+ FOR VALUES FROM ('2020-05-01') TO ('2020-06-01');
+ SQL
+ end
+
+ it 'detects both partitions' do
+ expect(subject).to eq([
+ Gitlab::Database::Partitioning::TimePartition.new(table_name, nil, '2020-05-01', partition_name: 'partitioned_test_000000'),
+ Gitlab::Database::Partitioning::TimePartition.new(table_name, '2020-05-01', '2020-06-01', partition_name: 'partitioned_test_202005')
+ ])
+ end
+ end
+
+ describe '#missing_partitions' do
+ subject { described_class.new(model, partitioning_key).missing_partitions }
+
+ let(:model) do
+ Class.new(ActiveRecord::Base) do
+ self.table_name = 'partitioned_test'
+ self.primary_key = :id
+ end
+ end
+
+ let(:partitioning_key) { :created_at }
+
+ around do |example|
+ Timecop.freeze(Date.parse('2020-08-22')) { example.run }
+ end
+
+ context 'with existing partitions' do
+ before do
+ ActiveRecord::Base.connection.execute(<<~SQL)
+ CREATE TABLE #{model.table_name}
+ (id serial not null, created_at timestamptz not null, PRIMARY KEY (id, created_at))
+ PARTITION BY RANGE (created_at);
+
+ CREATE TABLE #{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.partitioned_test_000000
+ PARTITION OF #{model.table_name}
+ FOR VALUES FROM (MINVALUE) TO ('2020-05-01');
+
+ CREATE TABLE #{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.partitioned_test_202006
+ PARTITION OF #{model.table_name}
+ FOR VALUES FROM ('2020-06-01') TO ('2020-07-01');
+ SQL
+
+ # Insert some data, it doesn't make a difference
+ model.create!(created_at: Date.parse('2020-04-20'))
+ model.create!(created_at: Date.parse('2020-06-15'))
+ end
+
+ it 'detects the gap and the missing partition in May 2020' do
+ expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-05-01', '2020-06-01'))
+ end
+
+ it 'detects the missing partitions at the end of the range and expects a partition for July 2020' do
+ expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-07-01', '2020-08-01'))
+ end
+
+ it 'detects the missing partitions at the end of the range and expects a partition for August 2020' do
+ expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-08-01', '2020-09-01'))
+ end
+
+ it 'creates partitions 6 months out from now (Sep 2020 through Feb 2021)' do
+ expect(subject).to include(
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-09-01', '2020-10-01'),
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-10-01', '2020-11-01'),
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-11-01', '2020-12-01'),
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-12-01', '2021-01-01'),
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2021-01-01', '2021-02-01'),
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2021-02-01', '2021-03-01')
+ )
+ end
+
+ it 'detects all missing partitions' do
+ expect(subject.size).to eq(9)
+ end
+ end
+
+ context 'without existing partitions' do
+ before do
+ ActiveRecord::Base.connection.execute(<<~SQL)
+ CREATE TABLE #{model.table_name}
+ (id serial not null, created_at timestamptz not null, PRIMARY KEY (id, created_at))
+ PARTITION BY RANGE (created_at);
+ SQL
+ end
+
+ it 'detects the missing catch-all partition at the beginning' do
+ expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, nil, '2020-08-01'))
+ end
+
+ it 'detects the missing partition for today and expects a partition for August 2020' do
+ expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-08-01', '2020-09-01'))
+ end
+
+ it 'creates partitions 6 months out from now (Sep 2020 through Feb 2021' do
+ expect(subject).to include(
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-09-01', '2020-10-01'),
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-10-01', '2020-11-01'),
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-11-01', '2020-12-01'),
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-12-01', '2021-01-01'),
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2021-01-01', '2021-02-01'),
+ Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2021-02-01', '2021-03-01')
+ )
+ end
+
+ it 'detects all missing partitions' do
+ expect(subject.size).to eq(8)
+ end
+ end
+
+ context 'with a regular partition but no catchall (MINVALUE, to) partition' do
+ before do
+ ActiveRecord::Base.connection.execute(<<~SQL)
+ CREATE TABLE #{model.table_name}
+ (id serial not null, created_at timestamptz not null, PRIMARY KEY (id, created_at))
+ PARTITION BY RANGE (created_at);
+
+ CREATE TABLE #{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.partitioned_test_202006
+ PARTITION OF #{model.table_name}
+ FOR VALUES FROM ('2020-06-01') TO ('2020-07-01');
+ SQL
+ end
+
+ it 'detects a missing catch-all partition to add before the existing partition' do
+ expect(subject).to include(Gitlab::Database::Partitioning::TimePartition.new(model.table_name, nil, '2020-06-01'))
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/partitioning/partition_creator_spec.rb b/spec/lib/gitlab/database/partitioning/partition_creator_spec.rb
new file mode 100644
index 00000000000..56399941662
--- /dev/null
+++ b/spec/lib/gitlab/database/partitioning/partition_creator_spec.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::Partitioning::PartitionCreator do
+ include PartitioningHelpers
+ include ExclusiveLeaseHelpers
+
+ describe '.register' do
+ let(:model) { double(partitioning_strategy: nil) }
+
+ it 'remembers registered models' do
+ expect { described_class.register(model) }.to change { described_class.models }.to include(model)
+ end
+ end
+
+ describe '#create_partitions (mocked)' do
+ subject { described_class.new(models).create_partitions }
+
+ let(:models) { [model] }
+ let(:model) { double(partitioning_strategy: partitioning_strategy, table_name: table) }
+ let(:partitioning_strategy) { double(missing_partitions: partitions) }
+ let(:table) { "some_table" }
+
+ before do
+ allow(ActiveRecord::Base.connection).to receive(:table_exists?).and_call_original
+ allow(ActiveRecord::Base.connection).to receive(:table_exists?).with(table).and_return(true)
+ allow(ActiveRecord::Base.connection).to receive(:execute).and_call_original
+
+ stub_exclusive_lease(described_class::LEASE_KEY % table, timeout: described_class::LEASE_TIMEOUT)
+ end
+
+ let(:partitions) do
+ [
+ instance_double(Gitlab::Database::Partitioning::TimePartition, table: 'bar', partition_name: 'foo', to_sql: "SELECT 1"),
+ instance_double(Gitlab::Database::Partitioning::TimePartition, table: 'bar', partition_name: 'foo2', to_sql: "SELECT 2")
+ ]
+ end
+
+ it 'creates the partition' do
+ expect(ActiveRecord::Base.connection).to receive(:execute).with(partitions.first.to_sql)
+ expect(ActiveRecord::Base.connection).to receive(:execute).with(partitions.second.to_sql)
+
+ subject
+ end
+
+ context 'error handling with 2 models' do
+ let(:models) do
+ [
+ double(partitioning_strategy: strategy1, table_name: table),
+ double(partitioning_strategy: strategy2, table_name: table)
+ ]
+ end
+
+ let(:strategy1) { double('strategy1', missing_partitions: nil) }
+ let(:strategy2) { double('strategy2', missing_partitions: partitions) }
+
+ it 'still creates partitions for the second table' do
+ expect(strategy1).to receive(:missing_partitions).and_raise('this should never happen (tm)')
+ expect(ActiveRecord::Base.connection).to receive(:execute).with(partitions.first.to_sql)
+ expect(ActiveRecord::Base.connection).to receive(:execute).with(partitions.second.to_sql)
+
+ subject
+ end
+ end
+ end
+
+ describe '#create_partitions' do
+ subject { described_class.new([my_model]).create_partitions }
+
+ let(:connection) { ActiveRecord::Base.connection }
+ let(:my_model) do
+ Class.new(ApplicationRecord) do
+ include PartitionedTable
+
+ self.table_name = 'my_model_example_table'
+
+ partitioned_by :created_at, strategy: :monthly
+ end
+ end
+
+ before do
+ connection.execute(<<~SQL)
+ CREATE TABLE my_model_example_table
+ (id serial not null, created_at timestamptz not null, primary key (id, created_at))
+ PARTITION BY RANGE (created_at);
+ SQL
+ end
+
+ it 'creates partitions' do
+ expect { subject }.to change { find_partitions(my_model.table_name, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA).size }.from(0)
+
+ subject
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/partitioning/time_partition_spec.rb b/spec/lib/gitlab/database/partitioning/time_partition_spec.rb
new file mode 100644
index 00000000000..700202d81c5
--- /dev/null
+++ b/spec/lib/gitlab/database/partitioning/time_partition_spec.rb
@@ -0,0 +1,174 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::Partitioning::TimePartition do
+ describe '.from_sql' do
+ subject { described_class.from_sql(table, partition_name, definition) }
+
+ let(:table) { 'foo' }
+ let(:partition_name) { 'foo_bar' }
+ let(:definition) { 'FOR VALUES FROM (\'2020-04-01 00:00:00\') TO (\'2020-05-01 00:00:00\')' }
+
+ it 'uses specified table name' do
+ expect(subject.table).to eq(table)
+ end
+
+ it 'uses specified partition name' do
+ expect(subject.partition_name).to eq(partition_name)
+ end
+
+ it 'parses start date' do
+ expect(subject.from).to eq(Date.parse('2020-04-01'))
+ end
+
+ it 'parses end date' do
+ expect(subject.to).to eq(Date.parse('2020-05-01'))
+ end
+
+ context 'with MINVALUE as a start date' do
+ let(:definition) { 'FOR VALUES FROM (MINVALUE) TO (\'2020-05-01\')' }
+
+ it 'sets from to nil' do
+ expect(subject.from).to be_nil
+ end
+ end
+
+ context 'with MAXVALUE as an end date' do
+ let(:definition) { 'FOR VALUES FROM (\'2020-04-01\') TO (MAXVALUE)' }
+
+ it 'raises a NotImplementedError' do
+ expect { subject }.to raise_error(NotImplementedError)
+ end
+ end
+ end
+
+ describe '#partition_name' do
+ subject { described_class.new(table, from, to, partition_name: partition_name).partition_name }
+
+ let(:table) { 'foo' }
+ let(:from) { '2020-04-01 00:00:00' }
+ let(:to) { '2020-05-01 00:00:00' }
+ let(:partition_name) { nil }
+
+ it 'uses table as prefix' do
+ expect(subject).to start_with(table)
+ end
+
+ it 'uses Year-Month (from) as suffix' do
+ expect(subject).to end_with("_202004")
+ end
+
+ context 'without from date' do
+ let(:from) { nil }
+
+ it 'uses 000000 as suffix for first partition' do
+ expect(subject).to end_with("_000000")
+ end
+ end
+
+ context 'with partition name explicitly given' do
+ let(:partition_name) { "foo_bar" }
+
+ it 'uses given partition name' do
+ expect(subject).to eq(partition_name)
+ end
+ end
+ end
+
+ describe '#to_sql' do
+ subject { described_class.new(table, from, to).to_sql }
+
+ let(:table) { 'foo' }
+ let(:from) { '2020-04-01 00:00:00' }
+ let(:to) { '2020-05-01 00:00:00' }
+
+ it 'transforms to a CREATE TABLE statement' do
+ expect(subject).to eq(<<~SQL)
+ CREATE TABLE IF NOT EXISTS "#{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}"."foo_202004"
+ PARTITION OF "foo"
+ FOR VALUES FROM ('2020-04-01') TO ('2020-05-01')
+ SQL
+ end
+
+ context 'without from date' do
+ let(:from) { nil }
+
+ it 'uses MINVALUE instead' do
+ expect(subject).to eq(<<~SQL)
+ CREATE TABLE IF NOT EXISTS "#{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}"."foo_000000"
+ PARTITION OF "foo"
+ FOR VALUES FROM (MINVALUE) TO ('2020-05-01')
+ SQL
+ end
+ end
+ end
+
+ describe 'object equality - #eql' do
+ def expect_inequality(actual, other)
+ expect(actual.eql?(other)).to be_falsey
+ expect(actual).not_to eq(other)
+ end
+
+ def expect_equality(actual, other)
+ expect(actual).to eq(other)
+ expect(actual.eql?(other)).to be_truthy
+ expect(actual.hash).to eq(other.hash)
+ end
+
+ def make_new(table: 'foo', from: '2020-04-01 00:00:00', to: '2020-05-01 00:00:00', partition_name: 'foo_202004')
+ described_class.new(table, from, to, partition_name: partition_name)
+ end
+
+ it 'treats objects identical with identical attributes' do
+ expect_equality(make_new, make_new)
+ end
+
+ it 'different table leads to in-equality' do
+ expect_inequality(make_new, make_new(table: 'bar'))
+ end
+
+ it 'different from leads to in-equality' do
+ expect_inequality(make_new, make_new(from: '2020-05-01 00:00:00'))
+ end
+
+ it 'different to leads to in-equality' do
+ expect_inequality(make_new, make_new(to: '2020-06-01 00:00:00'))
+ end
+
+ it 'different partition_name leads to in-equality' do
+ expect_inequality(make_new, make_new(partition_name: 'different'))
+ end
+
+ it 'nil partition_name is ignored if auto-generated matches' do
+ expect_equality(make_new, make_new(partition_name: nil))
+ end
+ end
+
+ describe 'Comparable, #<=>' do
+ let(:table) { 'foo' }
+
+ it 'sorts by partition name, i.e. by month - MINVALUE partition first' do
+ partitions = [
+ described_class.new(table, '2020-04-01', '2020-05-01'),
+ described_class.new(table, '2020-02-01', '2020-03-01'),
+ described_class.new(table, nil, '2020-02-01'),
+ described_class.new(table, '2020-03-01', '2020-04-01')
+ ]
+
+ expect(partitions.sort).to eq([
+ described_class.new(table, nil, '2020-02-01'),
+ described_class.new(table, '2020-02-01', '2020-03-01'),
+ described_class.new(table, '2020-03-01', '2020-04-01'),
+ described_class.new(table, '2020-04-01', '2020-05-01')
+ ])
+ end
+
+ it 'returns nil for partitions of different tables' do
+ one = described_class.new('foo', '2020-02-01', '2020-03-01')
+ two = described_class.new('bar', '2020-02-01', '2020-03-01')
+
+ expect(one.<=>(two)).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table_spec.rb
new file mode 100644
index 00000000000..49f3f87fe61
--- /dev/null
+++ b/spec/lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table_spec.rb
@@ -0,0 +1,175 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::BackfillPartitionedTable, '#perform' do
+ subject { described_class.new }
+
+ let(:source_table) { '_test_partitioning_backfills' }
+ let(:destination_table) { "#{source_table}_part" }
+ let(:unique_key) { 'id' }
+
+ before do
+ allow(subject).to receive(:transaction_open?).and_return(false)
+ end
+
+ context 'when the destination table exists' do
+ before do
+ connection.execute(<<~SQL)
+ CREATE TABLE #{source_table} (
+ id serial NOT NULL PRIMARY KEY,
+ col1 int NOT NULL,
+ col2 text NOT NULL,
+ created_at timestamptz NOT NULL
+ )
+ SQL
+
+ connection.execute(<<~SQL)
+ CREATE TABLE #{destination_table} (
+ id serial NOT NULL,
+ col1 int NOT NULL,
+ col2 text NOT NULL,
+ created_at timestamptz NOT NULL,
+ PRIMARY KEY (id, created_at)
+ ) PARTITION BY RANGE (created_at)
+ SQL
+
+ connection.execute(<<~SQL)
+ CREATE TABLE #{destination_table}_202001 PARTITION OF #{destination_table}
+ FOR VALUES FROM ('2020-01-01') TO ('2020-02-01')
+ SQL
+
+ connection.execute(<<~SQL)
+ CREATE TABLE #{destination_table}_202002 PARTITION OF #{destination_table}
+ FOR VALUES FROM ('2020-02-01') TO ('2020-03-01')
+ SQL
+
+ source_model.table_name = source_table
+ destination_model.table_name = destination_table
+
+ stub_const("#{described_class}::SUB_BATCH_SIZE", 2)
+ stub_const("#{described_class}::PAUSE_SECONDS", pause_seconds)
+
+ allow(subject).to receive(:sleep)
+ end
+
+ let(:connection) { ActiveRecord::Base.connection }
+ let(:source_model) { Class.new(ActiveRecord::Base) }
+ let(:destination_model) { Class.new(ActiveRecord::Base) }
+ let(:timestamp) { Time.utc(2020, 1, 2).round }
+ let(:pause_seconds) { 1 }
+
+ let!(:source1) { create_source_record(timestamp) }
+ let!(:source2) { create_source_record(timestamp + 1.day) }
+ let!(:source3) { create_source_record(timestamp + 1.month) }
+
+ it 'copies data into the destination table idempotently' do
+ expect(destination_model.count).to eq(0)
+
+ subject.perform(source1.id, source3.id, source_table, destination_table, unique_key)
+
+ expect(destination_model.count).to eq(3)
+
+ source_model.find_each do |source_record|
+ destination_record = destination_model.find_by_id(source_record.id)
+
+ expect(destination_record.attributes).to eq(source_record.attributes)
+ end
+
+ subject.perform(source1.id, source3.id, source_table, destination_table, unique_key)
+
+ expect(destination_model.count).to eq(3)
+ end
+
+ it 'breaks the assigned batch into smaller batches' do
+ expect_next_instance_of(described_class::BulkCopy) do |bulk_copy|
+ expect(bulk_copy).to receive(:copy_between).with(source1.id, source2.id)
+ expect(bulk_copy).to receive(:copy_between).with(source3.id, source3.id)
+ end
+
+ subject.perform(source1.id, source3.id, source_table, destination_table, unique_key)
+ end
+
+ it 'pauses after copying each sub-batch' do
+ expect(subject).to receive(:sleep).with(pause_seconds).twice
+
+ subject.perform(source1.id, source3.id, source_table, destination_table, unique_key)
+ end
+
+ it 'marks each job record as succeeded after processing' do
+ create(:background_migration_job, class_name: "::#{described_class.name}",
+ arguments: [source1.id, source3.id, source_table, destination_table, unique_key])
+
+ expect(::Gitlab::Database::BackgroundMigrationJob).to receive(:mark_all_as_succeeded).and_call_original
+
+ expect do
+ subject.perform(source1.id, source3.id, source_table, destination_table, unique_key)
+ end.to change { ::Gitlab::Database::BackgroundMigrationJob.succeeded.count }.from(0).to(1)
+ end
+
+ context 'when the feature flag is disabled' do
+ let(:mock_connection) { double('connection') }
+
+ before do
+ allow(subject).to receive(:connection).and_return(mock_connection)
+ stub_feature_flags(backfill_partitioned_audit_events: false)
+ end
+
+ it 'exits without attempting to copy data' do
+ expect(mock_connection).not_to receive(:execute)
+
+ subject.perform(1, 100, source_table, destination_table, unique_key)
+
+ expect(destination_model.count).to eq(0)
+ end
+ end
+
+ context 'when the job is run within an explicit transaction block' do
+ let(:mock_connection) { double('connection') }
+
+ before do
+ allow(subject).to receive(:connection).and_return(mock_connection)
+ allow(subject).to receive(:transaction_open?).and_return(true)
+ end
+
+ it 'raises an error before copying data' do
+ expect(mock_connection).not_to receive(:execute)
+
+ expect do
+ subject.perform(1, 100, source_table, destination_table, unique_key)
+ end.to raise_error(/Aborting job to backfill partitioned #{source_table}/)
+
+ expect(destination_model.count).to eq(0)
+ end
+ end
+ end
+
+ context 'when the destination table does not exist' do
+ let(:mock_connection) { double('connection') }
+ let(:mock_logger) { double('logger') }
+
+ before do
+ allow(subject).to receive(:connection).and_return(mock_connection)
+ allow(subject).to receive(:logger).and_return(mock_logger)
+
+ expect(mock_connection).to receive(:table_exists?).with(destination_table).and_return(false)
+ allow(mock_logger).to receive(:warn)
+ end
+
+ it 'exits without attempting to copy data' do
+ expect(mock_connection).not_to receive(:execute)
+
+ subject.perform(1, 100, source_table, destination_table, unique_key)
+ end
+
+ it 'logs a warning message that the job was skipped' do
+ expect(mock_logger).to receive(:warn).with(/#{destination_table} does not exist/)
+
+ subject.perform(1, 100, source_table, destination_table, unique_key)
+ end
+ end
+
+ def create_source_record(timestamp)
+ source_model.create!(col1: 123, col2: 'original value', created_at: timestamp)
+ end
+end
diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers_spec.rb
index 9cec77b434d..efa9c83b2d2 100644
--- a/spec/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers_spec.rb
+++ b/spec/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::PartitioningMigrationHelpers::ForeignKeyHelpers do
+RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::ForeignKeyHelpers do
include TriggerHelpers
let(:model) do
diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/partitioned_foreign_key_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/partitioned_foreign_key_spec.rb
index 77f71676252..a58c37f111d 100644
--- a/spec/lib/gitlab/database/partitioning_migration_helpers/partitioned_foreign_key_spec.rb
+++ b/spec/lib/gitlab/database/partitioning_migration_helpers/partitioned_foreign_key_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::PartitioningMigrationHelpers::PartitionedForeignKey do
+RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::PartitionedForeignKey do
let(:foreign_key) do
described_class.new(
to_table: 'issues',
diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
index 586b57d2002..9b24ab7cad4 100644
--- a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
+++ b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers do
+RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers do
include PartitioningHelpers
include TriggerHelpers
@@ -11,7 +11,7 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
end
let_it_be(:connection) { ActiveRecord::Base.connection }
- let(:template_table) { :audit_events }
+ let(:source_table) { :audit_events }
let(:partitioned_table) { '_test_migration_partitioned_table' }
let(:function_name) { '_test_migration_function_name' }
let(:trigger_name) { '_test_migration_trigger_name' }
@@ -22,10 +22,10 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
before do
allow(migration).to receive(:puts)
allow(migration).to receive(:transaction_open?).and_return(false)
- allow(migration).to receive(:partitioned_table_name).and_return(partitioned_table)
- allow(migration).to receive(:sync_function_name).and_return(function_name)
- allow(migration).to receive(:sync_trigger_name).and_return(trigger_name)
- allow(migration).to receive(:assert_table_is_whitelisted)
+ allow(migration).to receive(:make_partitioned_table_name).and_return(partitioned_table)
+ allow(migration).to receive(:make_sync_function_name).and_return(function_name)
+ allow(migration).to receive(:make_sync_trigger_name).and_return(trigger_name)
+ allow(migration).to receive(:assert_table_is_allowed)
end
describe '#partition_table_by_date' do
@@ -33,15 +33,19 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
let(:old_primary_key) { 'id' }
let(:new_primary_key) { [old_primary_key, partition_column] }
- context 'when the table is not whitelisted' do
- let(:template_table) { :this_table_is_not_whitelisted }
+ before do
+ allow(migration).to receive(:queue_background_migration_jobs_by_range_at_intervals)
+ end
+
+ context 'when the table is not allowed' do
+ let(:source_table) { :this_table_is_not_allowed }
it 'raises an error' do
- expect(migration).to receive(:assert_table_is_whitelisted).with(template_table).and_call_original
+ expect(migration).to receive(:assert_table_is_allowed).with(source_table).and_call_original
expect do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
- end.to raise_error(/#{template_table} is not whitelisted for use/)
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
+ end.to raise_error(/#{source_table} is not allowed for use/)
end
end
@@ -50,7 +54,7 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
expect(migration).to receive(:transaction_open?).and_return(true)
expect do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
end.to raise_error(/can not be run inside a transaction/)
end
end
@@ -60,7 +64,7 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
it 'raises an error' do
expect do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
end.to raise_error(/max_date #{max_date} must be greater than min_date #{min_date}/)
end
end
@@ -70,24 +74,24 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
it 'raises an error' do
expect do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
end.to raise_error(/max_date #{max_date} must be greater than min_date #{min_date}/)
end
end
context 'when the given table does not have a primary key' do
- let(:template_table) { :_partitioning_migration_helper_test_table }
+ let(:source_table) { :_partitioning_migration_helper_test_table }
let(:partition_column) { :some_field }
it 'raises an error' do
- migration.create_table template_table, id: false do |t|
+ migration.create_table source_table, id: false do |t|
t.integer :id
t.datetime partition_column
end
expect do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
- end.to raise_error(/primary key not defined for #{template_table}/)
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
+ end.to raise_error(/primary key not defined for #{source_table}/)
end
end
@@ -96,14 +100,14 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
it 'raises an error' do
expect do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
end.to raise_error(/partition column #{partition_column} does not exist/)
end
end
describe 'constructing the partitioned table' do
it 'creates a table partitioned by the proper column' do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
expect(connection.table_exists?(partitioned_table)).to be(true)
expect(connection.primary_key(partitioned_table)).to eq(new_primary_key)
@@ -112,7 +116,7 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
end
it 'changes the primary key datatype to bigint' do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
pk_column = connection.columns(partitioned_table).find { |c| c.name == old_primary_key }
@@ -127,13 +131,13 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
end
end
- let(:template_table) { :another_example }
+ let(:source_table) { :another_example }
let(:old_primary_key) { 'identifier' }
it 'does not change the primary key datatype' do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
- original_pk_column = connection.columns(template_table).find { |c| c.name == old_primary_key }
+ original_pk_column = connection.columns(source_table).find { |c| c.name == old_primary_key }
pk_column = connection.columns(partitioned_table).find { |c| c.name == old_primary_key }
expect(pk_column).not_to be_nil
@@ -142,7 +146,7 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
end
it 'removes the default from the primary key column' do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
pk_column = connection.columns(partitioned_table).find { |c| c.name == old_primary_key }
@@ -150,26 +154,100 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
end
it 'creates the partitioned table with the same non-key columns' do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
copied_columns = filter_columns_by_name(connection.columns(partitioned_table), new_primary_key)
- original_columns = filter_columns_by_name(connection.columns(template_table), new_primary_key)
+ original_columns = filter_columns_by_name(connection.columns(source_table), new_primary_key)
expect(copied_columns).to match_array(original_columns)
end
it 'creates a partition spanning over each month in the range given' do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
+
+ expect_range_partitions_for(partitioned_table, {
+ '000000' => ['MINVALUE', "'2019-12-01 00:00:00'"],
+ '201912' => ["'2019-12-01 00:00:00'", "'2020-01-01 00:00:00'"],
+ '202001' => ["'2020-01-01 00:00:00'", "'2020-02-01 00:00:00'"],
+ '202002' => ["'2020-02-01 00:00:00'", "'2020-03-01 00:00:00'"],
+ '202003' => ["'2020-03-01 00:00:00'", "'2020-04-01 00:00:00'"]
+ })
+ end
+
+ context 'when min_date is not given' do
+ let(:source_table) { :todos }
+
+ context 'with records present already' do
+ before do
+ create(:todo, created_at: Date.parse('2019-11-05'))
+ end
+
+ it 'creates a partition spanning over each month from the first record' do
+ migration.partition_table_by_date source_table, partition_column, max_date: max_date
+
+ expect_range_partitions_for(partitioned_table, {
+ '000000' => ['MINVALUE', "'2019-11-01 00:00:00'"],
+ '201911' => ["'2019-11-01 00:00:00'", "'2019-12-01 00:00:00'"],
+ '201912' => ["'2019-12-01 00:00:00'", "'2020-01-01 00:00:00'"],
+ '202001' => ["'2020-01-01 00:00:00'", "'2020-02-01 00:00:00'"],
+ '202002' => ["'2020-02-01 00:00:00'", "'2020-03-01 00:00:00'"],
+ '202003' => ["'2020-03-01 00:00:00'", "'2020-04-01 00:00:00'"]
+ })
+ end
+ end
+
+ context 'without data' do
+ it 'creates the catchall partition plus two actual partition' do
+ migration.partition_table_by_date source_table, partition_column, max_date: max_date
+
+ expect_range_partitions_for(partitioned_table, {
+ '000000' => ['MINVALUE', "'2020-02-01 00:00:00'"],
+ '202002' => ["'2020-02-01 00:00:00'", "'2020-03-01 00:00:00'"],
+ '202003' => ["'2020-03-01 00:00:00'", "'2020-04-01 00:00:00'"]
+ })
+ end
+ end
+ end
- expect_range_partition_of("#{partitioned_table}_000000", partitioned_table, 'MINVALUE', "'2019-12-01 00:00:00'")
- expect_range_partition_of("#{partitioned_table}_201912", partitioned_table, "'2019-12-01 00:00:00'", "'2020-01-01 00:00:00'")
- expect_range_partition_of("#{partitioned_table}_202001", partitioned_table, "'2020-01-01 00:00:00'", "'2020-02-01 00:00:00'")
- expect_range_partition_of("#{partitioned_table}_202002", partitioned_table, "'2020-02-01 00:00:00'", "'2020-03-01 00:00:00'")
+ context 'when max_date is not given' do
+ it 'creates partitions including the next month from today' do
+ today = Date.new(2020, 5, 8)
+
+ Timecop.freeze(today) do
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date
+
+ expect_range_partitions_for(partitioned_table, {
+ '000000' => ['MINVALUE', "'2019-12-01 00:00:00'"],
+ '201912' => ["'2019-12-01 00:00:00'", "'2020-01-01 00:00:00'"],
+ '202001' => ["'2020-01-01 00:00:00'", "'2020-02-01 00:00:00'"],
+ '202002' => ["'2020-02-01 00:00:00'", "'2020-03-01 00:00:00'"],
+ '202003' => ["'2020-03-01 00:00:00'", "'2020-04-01 00:00:00'"],
+ '202004' => ["'2020-04-01 00:00:00'", "'2020-05-01 00:00:00'"],
+ '202005' => ["'2020-05-01 00:00:00'", "'2020-06-01 00:00:00'"],
+ '202006' => ["'2020-06-01 00:00:00'", "'2020-07-01 00:00:00'"]
+ })
+ end
+ end
+ end
+
+ context 'without min_date, max_date' do
+ it 'creates partitions for the current and next month' do
+ current_date = Date.new(2020, 05, 22)
+ Timecop.freeze(current_date.to_time) do
+ migration.partition_table_by_date source_table, partition_column
+
+ expect_range_partitions_for(partitioned_table, {
+ '000000' => ['MINVALUE', "'2020-05-01 00:00:00'"],
+ '202005' => ["'2020-05-01 00:00:00'", "'2020-06-01 00:00:00'"],
+ '202006' => ["'2020-06-01 00:00:00'", "'2020-07-01 00:00:00'"]
+ })
+ end
+ end
end
end
describe 'keeping data in sync with the partitioned table' do
- let(:template_table) { :todos }
+ let(:source_table) { :todos }
let(:model) { Class.new(ActiveRecord::Base) }
let(:timestamp) { Time.utc(2019, 12, 1, 12).round }
@@ -180,16 +258,16 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
it 'creates a trigger function on the original table' do
expect_function_not_to_exist(function_name)
- expect_trigger_not_to_exist(template_table, trigger_name)
+ expect_trigger_not_to_exist(source_table, trigger_name)
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
expect_function_to_exist(function_name)
- expect_valid_function_trigger(template_table, trigger_name, function_name, after: %w[delete insert update])
+ expect_valid_function_trigger(source_table, trigger_name, function_name, after: %w[delete insert update])
end
it 'syncs inserts to the partitioned tables' do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
expect(model.count).to eq(0)
@@ -202,7 +280,7 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
end
it 'syncs updates to the partitioned tables' do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
first_todo = create(:todo, :pending, commit_id: nil, created_at: timestamp, updated_at: timestamp)
second_todo = create(:todo, created_at: timestamp, updated_at: timestamp)
@@ -223,7 +301,7 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
end
it 'syncs deletes to the partitioned tables' do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
first_todo = create(:todo, created_at: timestamp, updated_at: timestamp)
second_todo = create(:todo, created_at: timestamp, updated_at: timestamp)
@@ -237,50 +315,129 @@ describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
expect(model.find(second_todo.id).attributes).to eq(second_todo.attributes)
end
end
+
+ describe 'copying historic data to the partitioned table' do
+ let(:source_table) { 'todos' }
+ let(:migration_class) { '::Gitlab::Database::PartitioningMigrationHelpers::BackfillPartitionedTable' }
+ let(:sub_batch_size) { described_class::SUB_BATCH_SIZE }
+ let(:pause_seconds) { described_class::PAUSE_SECONDS }
+ let!(:first_id) { create(:todo).id }
+ let!(:second_id) { create(:todo).id }
+ let!(:third_id) { create(:todo).id }
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 2)
+
+ expect(migration).to receive(:queue_background_migration_jobs_by_range_at_intervals).and_call_original
+ end
+
+ it 'enqueues jobs to copy each batch of data' do
+ Sidekiq::Testing.fake! do
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+
+ first_job_arguments = [first_id, second_id, source_table, partitioned_table, 'id']
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq([migration_class, first_job_arguments])
+
+ second_job_arguments = [third_id, third_id, source_table, partitioned_table, 'id']
+ expect(BackgroundMigrationWorker.jobs[1]['args']).to eq([migration_class, second_job_arguments])
+ end
+ end
+ end
end
describe '#drop_partitioned_table_for' do
let(:expected_tables) do
- %w[000000 201912 202001 202002].map { |suffix| "#{partitioned_table}_#{suffix}" }.unshift(partitioned_table)
+ %w[000000 201912 202001 202002].map { |suffix| "#{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}.#{partitioned_table}_#{suffix}" }.unshift(partitioned_table)
end
+ let(:migration_class) { 'Gitlab::Database::PartitioningMigrationHelpers::BackfillPartitionedTable' }
- context 'when the table is not whitelisted' do
- let(:template_table) { :this_table_is_not_whitelisted }
+ context 'when the table is not allowed' do
+ let(:source_table) { :this_table_is_not_allowed }
it 'raises an error' do
- expect(migration).to receive(:assert_table_is_whitelisted).with(template_table).and_call_original
+ expect(migration).to receive(:assert_table_is_allowed).with(source_table).and_call_original
expect do
- migration.drop_partitioned_table_for template_table
- end.to raise_error(/#{template_table} is not whitelisted for use/)
+ migration.drop_partitioned_table_for source_table
+ end.to raise_error(/#{source_table} is not allowed for use/)
end
end
it 'drops the trigger syncing to the partitioned table' do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
expect_function_to_exist(function_name)
- expect_valid_function_trigger(template_table, trigger_name, function_name, after: %w[delete insert update])
+ expect_valid_function_trigger(source_table, trigger_name, function_name, after: %w[delete insert update])
- migration.drop_partitioned_table_for template_table
+ migration.drop_partitioned_table_for source_table
expect_function_not_to_exist(function_name)
- expect_trigger_not_to_exist(template_table, trigger_name)
+ expect_trigger_not_to_exist(source_table, trigger_name)
end
it 'drops the partitioned copy and all partitions' do
- migration.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
expected_tables.each do |table|
expect(connection.table_exists?(table)).to be(true)
end
- migration.drop_partitioned_table_for template_table
+ migration.drop_partitioned_table_for source_table
expected_tables.each do |table|
expect(connection.table_exists?(table)).to be(false)
end
end
+
+ context 'cleaning up background migration tracking records' do
+ let!(:job1) { create(:background_migration_job, class_name: migration_class, arguments: [1, 10, source_table]) }
+ let!(:job2) { create(:background_migration_job, class_name: migration_class, arguments: [11, 20, source_table]) }
+ let!(:job3) { create(:background_migration_job, class_name: migration_class, arguments: [1, 10, 'other_table']) }
+
+ it 'deletes any tracking records from the background_migration_jobs table' do
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
+
+ expect { migration.drop_partitioned_table_for(source_table) }
+ .to change { ::Gitlab::Database::BackgroundMigrationJob.count }.from(3).to(1)
+
+ remaining_record = ::Gitlab::Database::BackgroundMigrationJob.first
+ expect(remaining_record).to have_attributes(class_name: migration_class, arguments: [1, 10, 'other_table'])
+ end
+ end
+ end
+
+ describe '#create_hash_partitions' do
+ before do
+ connection.execute(<<~SQL)
+ CREATE TABLE #{partitioned_table}
+ (id serial not null, some_id integer not null, PRIMARY KEY (id, some_id))
+ PARTITION BY HASH (some_id);
+ SQL
+ end
+
+ it 'creates partitions for the full hash space (8 partitions)' do
+ partitions = 8
+
+ migration.create_hash_partitions(partitioned_table, partitions)
+
+ (0..partitions - 1).each do |partition|
+ partition_name = "#{partitioned_table}_#{"%01d" % partition}"
+ expect_hash_partition_of(partition_name, partitioned_table, partitions, partition)
+ end
+ end
+
+ it 'creates partitions for the full hash space (16 partitions)' do
+ partitions = 16
+
+ migration.create_hash_partitions(partitioned_table, partitions)
+
+ (0..partitions - 1).each do |partition|
+ partition_name = "#{partitioned_table}_#{"%02d" % partition}"
+ expect_hash_partition_of(partition_name, partitioned_table, partitions, partition)
+ end
+ end
end
def filter_columns_by_name(columns, names)
diff --git a/spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb b/spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb
index 0523066b593..8b3a0ceb804 100644
--- a/spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb
+++ b/spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::PostgresqlAdapter::ForceDisconnectableMixin do
+RSpec.describe Gitlab::Database::PostgresqlAdapter::ForceDisconnectableMixin do
describe 'checking in a connection to the pool' do
let(:model) do
Class.new(ActiveRecord::Base) do
diff --git a/spec/lib/gitlab/database/postgresql_adapter/schema_versions_copy_mixin_spec.rb b/spec/lib/gitlab/database/postgresql_adapter/schema_versions_copy_mixin_spec.rb
index 968dfc1ea43..c6333e4a4dc 100644
--- a/spec/lib/gitlab/database/postgresql_adapter/schema_versions_copy_mixin_spec.rb
+++ b/spec/lib/gitlab/database/postgresql_adapter/schema_versions_copy_mixin_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::PostgresqlAdapter::SchemaVersionsCopyMixin do
+RSpec.describe Gitlab::Database::PostgresqlAdapter::SchemaVersionsCopyMixin do
let(:schema_migration) { double('schem_migration', table_name: table_name, all_versions: versions) }
let(:versions) { %w(5 2 1000 200 4 93 2) }
let(:table_name) { "schema_migrations" }
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
index fae57996fb6..76b1be1e497 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :delete do
+RSpec.describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :delete do
let(:migration) { FakeRenameReservedPathMigrationV1.new }
let(:subject) { described_class.new(['the-path'], migration) }
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 46fc48ab3fc..e222a29c6a1 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
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :delete do
+RSpec.describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :delete do
let(:migration) { FakeRenameReservedPathMigrationV1.new }
let(:subject) { described_class.new(['the-path'], migration) }
let(:namespace) { create(:group, name: 'the-path') }
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
index 1ccdb1d9447..3799fe3c316 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :delete do
+RSpec.describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :delete do
let(:migration) { FakeRenameReservedPathMigrationV1.new }
let(:subject) { described_class.new(['the-path'], migration) }
let(:project) do
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
index 56767c21ab7..3b2d3ab1354 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-shared_examples 'renames child namespaces' do |type|
+RSpec.shared_examples 'renames child namespaces' do |type|
it 'renames namespaces' do
rename_namespaces = double
expect(described_class::RenameNamespaces)
@@ -15,7 +15,7 @@ shared_examples 'renames child namespaces' do |type|
end
end
-describe Gitlab::Database::RenameReservedPathsMigration::V1, :delete do
+RSpec.describe Gitlab::Database::RenameReservedPathsMigration::V1, :delete do
let(:subject) { FakeRenameReservedPathMigrationV1.new }
before do
diff --git a/spec/lib/gitlab/database/schema_cleaner_spec.rb b/spec/lib/gitlab/database/schema_cleaner_spec.rb
index adaeb85d52d..1303ad7a311 100644
--- a/spec/lib/gitlab/database/schema_cleaner_spec.rb
+++ b/spec/lib/gitlab/database/schema_cleaner_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::SchemaCleaner do
+RSpec.describe Gitlab::Database::SchemaCleaner do
let(:example_schema) { fixture_file(File.join('gitlab', 'database', 'structure_example.sql')) }
let(:io) { StringIO.new }
diff --git a/spec/lib/gitlab/database/sha_attribute_spec.rb b/spec/lib/gitlab/database/sha_attribute_spec.rb
index 15695bc8069..1b855625a6c 100644
--- a/spec/lib/gitlab/database/sha_attribute_spec.rb
+++ b/spec/lib/gitlab/database/sha_attribute_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::ShaAttribute do
+RSpec.describe Gitlab::Database::ShaAttribute do
let(:sha) do
'9a573a369a5bfbb9a4a36e98852c21af8a44ea8b'
end
diff --git a/spec/lib/gitlab/database/with_lock_retries_spec.rb b/spec/lib/gitlab/database/with_lock_retries_spec.rb
index d7eee594631..70cbddbb7b7 100644
--- a/spec/lib/gitlab/database/with_lock_retries_spec.rb
+++ b/spec/lib/gitlab/database/with_lock_retries_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Database::WithLockRetries do
+RSpec.describe Gitlab::Database::WithLockRetries do
let(:env) { {} }
let(:logger) { Gitlab::Database::WithLockRetries::NULL_LOGGER }
let(:subject) { described_class.new(env: env, logger: logger, timing_configuration: timing_configuration) }
diff --git a/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb b/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb
index e6321d48e11..fdf16069381 100644
--- a/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb
+++ b/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DatabaseImporters::CommonMetrics::Importer do
+RSpec.describe Gitlab::DatabaseImporters::CommonMetrics::Importer do
subject { described_class.new }
context "does import common_metrics.yml" do
diff --git a/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb b/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb
index e4e8a85e7bc..67da59d6477 100644
--- a/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb
+++ b/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb
@@ -2,14 +2,14 @@
require 'spec_helper'
-describe Gitlab::DatabaseImporters::CommonMetrics::PrometheusMetric do
+RSpec.describe Gitlab::DatabaseImporters::CommonMetrics::PrometheusMetric do
it 'group enum equals ::PrometheusMetric' do
expect(described_class.groups).to eq(::PrometheusMetric.groups)
end
it '.group_titles equals ::PrometheusMetric' do
- existing_group_titles = ::PrometheusMetricEnums.group_details.each_with_object({}) do |(key, value), memo|
- memo[key] = value[:group_title]
+ existing_group_titles = ::PrometheusMetricEnums.group_details.transform_values do |value|
+ value[:group_title]
end
expect(Gitlab::DatabaseImporters::CommonMetrics::PrometheusMetricEnums.group_titles).to eq(existing_group_titles)
end
diff --git a/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb b/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb
index 97f4a7eec75..a3661bbe49a 100644
--- a/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb
+++ b/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DatabaseImporters::InstanceAdministrators::CreateGroup do
+RSpec.describe Gitlab::DatabaseImporters::InstanceAdministrators::CreateGroup do
describe '#execute' do
let(:result) { subject.execute }
diff --git a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
index 923f620a81d..ca9f9ab915f 100644
--- a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
+++ b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do
+RSpec.describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do
describe '#execute' do
let(:result) { subject.execute }
diff --git a/spec/lib/gitlab/database_importers/self_monitoring/project/delete_service_spec.rb b/spec/lib/gitlab/database_importers/self_monitoring/project/delete_service_spec.rb
index 89d816c0cf7..9d514bcc661 100644
--- a/spec/lib/gitlab/database_importers/self_monitoring/project/delete_service_spec.rb
+++ b/spec/lib/gitlab/database_importers/self_monitoring/project/delete_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DatabaseImporters::SelfMonitoring::Project::DeleteService do
+RSpec.describe Gitlab::DatabaseImporters::SelfMonitoring::Project::DeleteService do
describe '#execute' do
let!(:application_setting) { create(:application_setting) }
let(:result) { subject.execute }
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index b99f311de29..cd009f955af 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -2,11 +2,19 @@
require 'spec_helper'
-describe Gitlab::Database do
+RSpec.describe Gitlab::Database do
before do
stub_const('MigrationTest', Class.new { include Gitlab::Database })
end
+ describe 'EXTRA_SCHEMAS' do
+ it 'contains only schemas starting with gitlab_ prefix' do
+ described_class::EXTRA_SCHEMAS.each do |schema|
+ expect(schema.to_s).to start_with('gitlab_')
+ end
+ end
+ end
+
describe '.config' do
it 'returns a Hash' do
expect(described_class.config).to be_an_instance_of(Hash)
@@ -82,36 +90,83 @@ describe Gitlab::Database do
end
describe '.postgresql_minimum_supported_version?' do
- it 'returns false when using PostgreSQL 9.5' do
- allow(described_class).to receive(:version).and_return('9.5')
+ it 'returns false when using PostgreSQL 10' do
+ allow(described_class).to receive(:version).and_return('10')
expect(described_class.postgresql_minimum_supported_version?).to eq(false)
end
- it 'returns true when using PostgreSQL 9.6' do
- allow(described_class).to receive(:version).and_return('9.6')
+ it 'returns true when using PostgreSQL 11' do
+ allow(described_class).to receive(:version).and_return('11')
expect(described_class.postgresql_minimum_supported_version?).to eq(true)
end
- it 'returns true when using PostgreSQL 10 or newer' do
- allow(described_class).to receive(:version).and_return('10')
+ it 'returns true when using PostgreSQL 12' do
+ allow(described_class).to receive(:version).and_return('12')
expect(described_class.postgresql_minimum_supported_version?).to eq(true)
end
end
- describe '.replication_slots_supported?' do
- it 'returns false when using PostgreSQL 9.3' do
- allow(described_class).to receive(:version).and_return('9.3.1')
+ describe '.postgresql_upcoming_deprecation?' do
+ it 'returns true when database version is lower than the upcoming minimum' do
+ allow(described_class).to receive(:version).and_return('11')
+
+ expect(described_class.postgresql_upcoming_deprecation?).to eq(true)
+ end
+
+ it 'returns false when database version equals the upcoming minimum' do
+ allow(described_class).to receive(:version).and_return('12')
+
+ expect(described_class.postgresql_upcoming_deprecation?).to eq(false)
+ end
+
+ it 'returns false when database version is greater the upcoming minimum' do
+ allow(described_class).to receive(:version).and_return('13')
+
+ expect(described_class.postgresql_upcoming_deprecation?).to eq(false)
+ end
+ end
+
+ describe '.check_postgres_version_and_print_warning' do
+ subject { described_class.check_postgres_version_and_print_warning }
+
+ it 'prints a warning if not compliant with minimum postgres version' do
+ allow(described_class).to receive(:postgresql_minimum_supported_version?).and_return(false)
+
+ expect(Kernel).to receive(:warn).with(/You are using PostgreSQL/)
- expect(described_class.replication_slots_supported?).to eq(false)
+ subject
end
- it 'returns true when using PostgreSQL 9.4.0 or newer' do
- allow(described_class).to receive(:version).and_return('9.4.0')
+ it 'doesnt print a warning if compliant with minimum postgres version' do
+ allow(described_class).to receive(:postgresql_minimum_supported_version?).and_return(true)
- expect(described_class.replication_slots_supported?).to eq(true)
+ expect(Kernel).not_to receive(:warn).with(/You are using PostgreSQL/)
+
+ subject
+ end
+
+ it 'doesnt print a warning in Rails runner environment' do
+ allow(described_class).to receive(:postgresql_minimum_supported_version?).and_return(false)
+ allow(Gitlab::Runtime).to receive(:rails_runner?).and_return(true)
+
+ expect(Kernel).not_to receive(:warn).with(/You are using PostgreSQL/)
+
+ subject
+ end
+
+ it 'ignores ActiveRecord errors' do
+ allow(described_class).to receive(:postgresql_minimum_supported_version?).and_raise(ActiveRecord::ActiveRecordError)
+
+ expect { subject }.not_to raise_error
+ end
+
+ it 'ignores Postgres errors' do
+ allow(described_class).to receive(:postgresql_minimum_supported_version?).and_raise(PG::Error)
+
+ expect { subject }.not_to raise_error
end
end
@@ -228,7 +283,6 @@ describe Gitlab::Database do
describe '.bulk_insert' do
before do
allow(described_class).to receive(:connection).and_return(connection)
- allow(described_class).to receive(:version).and_return(version)
allow(connection).to receive(:quote_column_name, &:itself)
allow(connection).to receive(:quote, &:itself)
allow(connection).to receive(:execute)
@@ -243,8 +297,6 @@ describe Gitlab::Database do
]
end
- let_it_be(:version) { 9.6 }
-
it 'does nothing with empty rows' do
expect(connection).not_to receive(:execute)
@@ -311,28 +363,13 @@ describe Gitlab::Database do
expect(ids).to eq([10])
end
- context 'with version >= 9.5' do
- it 'allows setting the upsert to do nothing' do
- expect(connection)
- .to receive(:execute)
- .with(/ON CONFLICT DO NOTHING/)
-
- described_class
- .bulk_insert('test', [{ number: 10 }], on_conflict: :do_nothing)
- end
- end
-
- context 'with version < 9.5' do
- let(:version) { 9.4 }
-
- it 'refuses setting the upsert' do
- expect(connection)
- .not_to receive(:execute)
- .with(/ON CONFLICT/)
+ it 'allows setting the upsert to do nothing' do
+ expect(connection)
+ .to receive(:execute)
+ .with(/ON CONFLICT DO NOTHING/)
- described_class
- .bulk_insert('test', [{ number: 10 }], on_conflict: :do_nothing)
- end
+ described_class
+ .bulk_insert('test', [{ number: 10 }], on_conflict: :do_nothing)
end
end
end
diff --git a/spec/lib/gitlab/dependency_linker/base_linker_spec.rb b/spec/lib/gitlab/dependency_linker/base_linker_spec.rb
index 1466ce2dfcc..678d4a90e8d 100644
--- a/spec/lib/gitlab/dependency_linker/base_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/base_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::BaseLinker do
+RSpec.describe Gitlab::DependencyLinker::BaseLinker do
let(:linker_class) do
Class.new(described_class) do
def link_dependencies
diff --git a/spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb b/spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb
index 86d5bc93bf7..8068fa30367 100644
--- a/spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::CargoTomlLinker do
+RSpec.describe Gitlab::DependencyLinker::CargoTomlLinker do
describe '.support?' do
it 'supports Cargo.toml' do
expect(described_class.support?('Cargo.toml')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
index 6d61edaa870..52ddba24458 100644
--- a/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::CartfileLinker do
+RSpec.describe Gitlab::DependencyLinker::CartfileLinker do
describe '.support?' do
it 'supports Cartfile' do
expect(described_class.support?('Cartfile')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
index cc1f09628ef..c24d6a44d9b 100644
--- a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::ComposerJsonLinker do
+RSpec.describe Gitlab::DependencyLinker::ComposerJsonLinker do
describe '.support?' do
it 'supports composer.json' do
expect(described_class.support?('composer.json')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
index c6b6dfa77cb..00e95dea224 100644
--- a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::GemfileLinker do
+RSpec.describe Gitlab::DependencyLinker::GemfileLinker do
describe '.support?' do
it 'supports Gemfile' do
expect(described_class.support?('Gemfile')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
index c1cbfa31684..ae82dd51c95 100644
--- a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::GemspecLinker do
+RSpec.describe Gitlab::DependencyLinker::GemspecLinker do
describe '.support?' do
it 'supports *.gemspec' do
expect(described_class.support?('gitlab_git.gemspec')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/go_mod_linker_spec.rb b/spec/lib/gitlab/dependency_linker/go_mod_linker_spec.rb
index 769daa0b3a6..605b14bc923 100644
--- a/spec/lib/gitlab/dependency_linker/go_mod_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/go_mod_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::GoModLinker do
+RSpec.describe Gitlab::DependencyLinker::GoModLinker do
let(:file_name) { 'go.mod' }
let(:file_content) do
<<-CONTENT.strip_heredoc
diff --git a/spec/lib/gitlab/dependency_linker/go_sum_linker_spec.rb b/spec/lib/gitlab/dependency_linker/go_sum_linker_spec.rb
index f5cb7809ad3..2836c0e9f29 100644
--- a/spec/lib/gitlab/dependency_linker/go_sum_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/go_sum_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::GoSumLinker do
+RSpec.describe Gitlab::DependencyLinker::GoSumLinker do
let(:file_name) { 'go.sum' }
let(:file_content) do
<<-CONTENT.strip_heredoc
diff --git a/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
index 7128689e362..c1ed030c548 100644
--- a/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::GodepsJsonLinker do
+RSpec.describe Gitlab::DependencyLinker::GodepsJsonLinker do
describe '.support?' do
it 'supports Godeps.json' do
expect(described_class.support?('Godeps.json')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
index eb0c5e0675a..cdfc0e89bc7 100644
--- a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::PackageJsonLinker do
+RSpec.describe Gitlab::DependencyLinker::PackageJsonLinker do
describe '.support?' do
it 'supports package.json' do
expect(described_class.support?('package.json')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb b/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb
index 5b69ef5af24..15f580a3a60 100644
--- a/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::Parser::Gemfile do
+RSpec.describe Gitlab::DependencyLinker::Parser::Gemfile do
describe '#parse' do
let(:file_content) do
<<-CONTENT.strip_heredoc
diff --git a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
index 77326e73505..8e536c00ea6 100644
--- a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::PodfileLinker do
+RSpec.describe Gitlab::DependencyLinker::PodfileLinker do
describe '.support?' do
it 'supports Podfile' do
expect(described_class.support?('Podfile')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
index d522a08cdd9..1f81049a41e 100644
--- a/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::PodspecJsonLinker do
+RSpec.describe Gitlab::DependencyLinker::PodspecJsonLinker do
describe '.support?' do
it 'supports *.podspec.json' do
expect(described_class.support?('Reachability.podspec.json')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
index baabd0c0460..132b5b21d85 100644
--- a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::PodspecLinker do
+RSpec.describe Gitlab::DependencyLinker::PodspecLinker do
describe '.support?' do
it 'supports *.podspec' do
expect(described_class.support?('Reachability.podspec')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
index 04ac5f10479..e59756cb7bc 100644
--- a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker::RequirementsTxtLinker do
+RSpec.describe Gitlab::DependencyLinker::RequirementsTxtLinker do
describe '.support?' do
it 'supports requirements.txt' do
expect(described_class.support?('requirements.txt')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker_spec.rb b/spec/lib/gitlab/dependency_linker_spec.rb
index acd4376615c..2daa8df815d 100644
--- a/spec/lib/gitlab/dependency_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DependencyLinker do
+RSpec.describe Gitlab::DependencyLinker do
describe '.link' do
it 'links using GemfileLinker' do
blob_name = 'Gemfile'
diff --git a/spec/lib/gitlab/devise_failure_spec.rb b/spec/lib/gitlab/devise_failure_spec.rb
index eee05c7befd..a452de59795 100644
--- a/spec/lib/gitlab/devise_failure_spec.rb
+++ b/spec/lib/gitlab/devise_failure_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DeviseFailure do
+RSpec.describe Gitlab::DeviseFailure do
let(:env) do
{
'REQUEST_URI' => 'http://test.host/',
diff --git a/spec/lib/gitlab/diff/diff_refs_spec.rb b/spec/lib/gitlab/diff/diff_refs_spec.rb
index 33a7cf5ae12..c1ee34ba874 100644
--- a/spec/lib/gitlab/diff/diff_refs_spec.rb
+++ b/spec/lib/gitlab/diff/diff_refs_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::DiffRefs do
+RSpec.describe Gitlab::Diff::DiffRefs do
let(:project) { create(:project, :repository) }
describe '#==' do
diff --git a/spec/lib/gitlab/diff/file_collection/commit_spec.rb b/spec/lib/gitlab/diff/file_collection/commit_spec.rb
index 34ed22b8941..6c109e96a53 100644
--- a/spec/lib/gitlab/diff/file_collection/commit_spec.rb
+++ b/spec/lib/gitlab/diff/file_collection/commit_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::FileCollection::Commit do
+RSpec.describe Gitlab::Diff::FileCollection::Commit do
let(:project) { create(:project, :repository) }
it_behaves_like 'diff statistics' do
diff --git a/spec/lib/gitlab/diff/file_collection/compare_spec.rb b/spec/lib/gitlab/diff/file_collection/compare_spec.rb
index f330f299ac1..168d58e584e 100644
--- a/spec/lib/gitlab/diff/file_collection/compare_spec.rb
+++ b/spec/lib/gitlab/diff/file_collection/compare_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::FileCollection::Compare do
+RSpec.describe Gitlab::Diff::FileCollection::Compare do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb
index 7e945d1d140..bd60c24859c 100644
--- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb
+++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::FileCollection::MergeRequestDiffBatch do
+RSpec.describe Gitlab::Diff::FileCollection::MergeRequestDiffBatch do
let(:merge_request) { create(:merge_request) }
let(:batch_page) { 1 }
let(:batch_size) { 10 }
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 c2b6ca4164c..a5e714c90fc 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
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::FileCollection::MergeRequestDiff do
+RSpec.describe Gitlab::Diff::FileCollection::MergeRequestDiff do
let(:merge_request) { create(:merge_request) }
let(:diffable) { merge_request.merge_request_diff }
let(:subject) { described_class.new(diffable, diff_options: nil) }
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index 8dbedcf26b9..94abfcf079a 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::File do
+RSpec.describe Gitlab::Diff::File do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb
index 3f88f39ba92..579776d44aa 100644
--- a/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb
+++ b/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::Formatters::ImageFormatter do
+RSpec.describe Gitlab::Diff::Formatters::ImageFormatter do
let(:base_attrs) do
{
base_sha: 123,
diff --git a/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb
index 50dd597c5a7..41877a16ebf 100644
--- a/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb
+++ b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::Formatters::TextFormatter do
+RSpec.describe Gitlab::Diff::Formatters::TextFormatter do
let!(:base) do
{
base_sha: 123,
diff --git a/spec/lib/gitlab/diff/highlight_cache_spec.rb b/spec/lib/gitlab/diff/highlight_cache_spec.rb
index 3c128aad976..80cc10051c4 100644
--- a/spec/lib/gitlab/diff/highlight_cache_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
let(:merge_request) { create(:merge_request_with_diffs) }
let(:diff_hash) do
{ ".gitignore-false-false-false" =>
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index ff4ec75358e..283437e7fbd 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::Highlight do
+RSpec.describe Gitlab::Diff::Highlight do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb
index a668bb464a4..60f7f3a103f 100644
--- a/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::InlineDiffMarkdownMarker do
+RSpec.describe Gitlab::Diff::InlineDiffMarkdownMarker do
describe '#mark' do
let(:raw) { "abc 'def'" }
let(:inline_diffs) { [2..5] }
diff --git a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
index 26b99870b31..6820a7df95e 100644
--- a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::InlineDiffMarker do
+RSpec.describe Gitlab::Diff::InlineDiffMarker do
describe '#mark' do
let(:inline_diffs) { [2..5] }
let(:raw) { "abc 'def'" }
diff --git a/spec/lib/gitlab/diff/inline_diff_spec.rb b/spec/lib/gitlab/diff/inline_diff_spec.rb
index fdbee3b4230..35284e952f7 100644
--- a/spec/lib/gitlab/diff/inline_diff_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::InlineDiff do
+RSpec.describe Gitlab::Diff::InlineDiff do
describe '.for_lines' do
let(:diff) do
<<-EOF.strip_heredoc
diff --git a/spec/lib/gitlab/diff/line_mapper_spec.rb b/spec/lib/gitlab/diff/line_mapper_spec.rb
index 6a86f885c3b..1c1f8201a81 100644
--- a/spec/lib/gitlab/diff/line_mapper_spec.rb
+++ b/spec/lib/gitlab/diff/line_mapper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::LineMapper do
+RSpec.describe Gitlab::Diff::LineMapper do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/diff/line_spec.rb b/spec/lib/gitlab/diff/line_spec.rb
index 7961bec9d57..e10a50afde9 100644
--- a/spec/lib/gitlab/diff/line_spec.rb
+++ b/spec/lib/gitlab/diff/line_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::Line do
+RSpec.describe Gitlab::Diff::Line do
shared_examples 'line object initialized by hash' do
it 'round-trips correctly with to_hash' do
expect(described_class.safe_init_from_hash(line.to_hash).to_hash)
diff --git a/spec/lib/gitlab/diff/lines_unfolder_spec.rb b/spec/lib/gitlab/diff/lines_unfolder_spec.rb
index ebcbe1fb1a0..b891f9e8285 100644
--- a/spec/lib/gitlab/diff/lines_unfolder_spec.rb
+++ b/spec/lib/gitlab/diff/lines_unfolder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::LinesUnfolder do
+RSpec.describe Gitlab::Diff::LinesUnfolder do
let(:raw_diff) do
<<-DIFF.strip_heredoc
@@ -7,9 +7,6 @@
diff --git a/spec/lib/gitlab/diff/parallel_diff_spec.rb b/spec/lib/gitlab/diff/parallel_diff_spec.rb
index d275bf2c223..f574d7ec707 100644
--- a/spec/lib/gitlab/diff/parallel_diff_spec.rb
+++ b/spec/lib/gitlab/diff/parallel_diff_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::ParallelDiff do
+RSpec.describe Gitlab::Diff::ParallelDiff do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb
index 00a446c4e20..7448ae0b2ea 100644
--- a/spec/lib/gitlab/diff/parser_spec.rb
+++ b/spec/lib/gitlab/diff/parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::Parser do
+RSpec.describe Gitlab::Diff::Parser do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/diff/position_collection_spec.rb b/spec/lib/gitlab/diff/position_collection_spec.rb
index dd8434ab10b..b1478c774f1 100644
--- a/spec/lib/gitlab/diff/position_collection_spec.rb
+++ b/spec/lib/gitlab/diff/position_collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::PositionCollection do
+RSpec.describe Gitlab::Diff::PositionCollection do
let(:merge_request) { build(:merge_request) }
let(:text_position) do
diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb
index b32a2c59bb9..a7f6ea0cbfb 100644
--- a/spec/lib/gitlab/diff/position_spec.rb
+++ b/spec/lib/gitlab/diff/position_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::Position do
+RSpec.describe Gitlab::Diff::Position do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb b/spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb
index 900816af53a..7dceb64b776 100644
--- a/spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb
+++ b/spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::PositionTracer::ImageStrategy do
+RSpec.describe Gitlab::Diff::PositionTracer::ImageStrategy do
include PositionTracerHelpers
let(:project) { create(:project, :repository) }
@@ -234,5 +234,118 @@ describe Gitlab::Diff::PositionTracer::ImageStrategy do
end
end
end
+
+ describe 'symlink scenarios' do
+ let(:new_file) { old_file_status == :new }
+ let(:deleted_file) { old_file_status == :deleted }
+ let(:renamed_file) { old_file_status == :renamed }
+
+ let(:file_identifier) { "#{file_name}-#{new_file}-#{deleted_file}-#{renamed_file}" }
+ let(:file_identifier_hash) { Digest::SHA1.hexdigest(file_identifier) }
+ let(:old_position) { position(old_path: file_name, new_path: file_name, position_type: 'image', file_identifier_hash: file_identifier_hash) }
+
+ let(:update_file_commit) do
+ initial_commit
+
+ update_file(
+ branch_name,
+ file_name,
+ Base64.encode64('morecontent')
+ )
+ end
+
+ let(:delete_file_commit) do
+ initial_commit
+
+ delete_file(branch_name, file_name)
+ end
+
+ let(:create_second_file_commit) do
+ initial_commit
+
+ create_file(
+ branch_name,
+ second_file_name,
+ Base64.encode64('morecontent')
+ )
+ end
+
+ before do
+ stub_feature_flags(file_identifier_hash: true)
+ end
+
+ describe 'from symlink to image' do
+ let(:initial_commit) { project.commit('a19c7f9a147e35e535c797cf148d29c24dac5544') }
+ let(:symlink_to_image_commit) { project.commit('8cfca8420812e5bd7479aa32cf33e0c95a3ca576') }
+ let(:branch_name) { 'diff-files-symlink-to-image' }
+ let(:file_name) { 'symlink-to-image.png' }
+
+ context "when the old position is on the new image file" do
+ let(:old_file_status) { :new }
+
+ context "when the image file's content was unchanged between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_image_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, create_second_file_commit) }
+
+ it "returns the new position" do
+ expect_new_position(
+ old_path: file_name,
+ new_path: file_name
+ )
+ end
+ end
+
+ context "when the image file's content was changed between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_image_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, update_file_commit) }
+ let(:change_diff_refs) { diff_refs(symlink_to_image_commit, update_file_commit) }
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name
+ )
+ end
+ end
+
+ context "when the image file was removed between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_image_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, delete_file_commit) }
+ let(:change_diff_refs) { diff_refs(symlink_to_image_commit, delete_file_commit) }
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name
+ )
+ end
+ end
+ end
+ end
+
+ describe 'from image to symlink' do
+ let(:initial_commit) { project.commit('d10dcdfbbb2b59a959a5f5d66a4adf28f0ea4008') }
+ let(:image_to_symlink_commit) { project.commit('3e94fdaa60da8aed38401b91bc56be70d54ca424') }
+ let(:branch_name) { 'diff-files-image-to-symlink' }
+ let(:file_name) { 'image-to-symlink.png' }
+
+ context "when the old position is on the added image file" do
+ let(:old_file_status) { :new }
+
+ context "when the image file gets changed to a symlink between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit.parent, initial_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit.parent, image_to_symlink_commit) }
+ let(:change_diff_refs) { diff_refs(initial_commit, image_to_symlink_commit) }
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name
+ )
+ end
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb b/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb
index 7f4902c5b86..d9f384fb47f 100644
--- a/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb
+++ b/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::PositionTracer::LineStrategy do
+RSpec.describe Gitlab::Diff::PositionTracer::LineStrategy do
# Douwe's diary New York City, 2016-06-28
# --------------------------------------------------------------------------
#
@@ -1801,5 +1801,143 @@ describe Gitlab::Diff::PositionTracer::LineStrategy do
end
end
end
+
+ describe 'symlink scenarios' do
+ let(:new_file) { old_file_status == :new }
+ let(:deleted_file) { old_file_status == :deleted }
+ let(:renamed_file) { old_file_status == :renamed }
+
+ let(:file_identifier) { "#{file_name}-#{new_file}-#{deleted_file}-#{renamed_file}" }
+ let(:file_identifier_hash) { Digest::SHA1.hexdigest(file_identifier) }
+
+ let(:update_line_commit) do
+ update_file(
+ branch_name,
+ file_name,
+ <<-CONTENT.strip_heredoc
+ A
+ BB
+ C
+ CONTENT
+ )
+ end
+
+ let(:delete_file_commit) do
+ delete_file(branch_name, file_name)
+ end
+
+ let(:create_second_file_commit) do
+ create_file(
+ branch_name,
+ second_file_name,
+ <<-CONTENT.strip_heredoc
+ D
+ E
+ CONTENT
+ )
+ end
+
+ before do
+ stub_feature_flags(file_identifier_hash: true)
+ end
+
+ describe 'from symlink to text' do
+ let(:initial_commit) { project.commit('0e5b363105e9176a77bac94d7ff6d8c4fb35c3eb') }
+ let(:symlink_to_text_commit) { project.commit('689815e617abc6889f1fded4834d2dd7d942a58e') }
+ let(:branch_name) { 'diff-files-symlink-to-text' }
+ let(:file_name) { 'symlink-to-text.txt' }
+ let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 3, file_identifier_hash: file_identifier_hash) }
+
+ before do
+ create_branch('diff-files-symlink-to-text-test', branch_name)
+ end
+
+ context "when the old position is on the new text file" do
+ let(:old_file_status) { :new }
+
+ context "when the text file's content was unchanged between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_text_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, create_second_file_commit) }
+
+ it "returns the new position" do
+ expect_new_position(
+ new_path: old_position.new_path,
+ new_line: old_position.new_line
+ )
+ end
+ end
+
+ context "when the text file's content has change, but the line was unchanged between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_text_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
+
+ it "returns the new position" do
+ expect_new_position(
+ new_path: old_position.new_path,
+ new_line: old_position.new_line
+ )
+ end
+ end
+
+ context "when the text file's line was changed between the old and the new diff" do
+ let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2, file_identifier_hash: file_identifier_hash) }
+
+ let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_text_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
+ let(:change_diff_refs) { diff_refs(symlink_to_text_commit, update_line_commit) }
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
+ end
+ end
+
+ context "when the text file was removed between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_text_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, delete_file_commit) }
+ let(:change_diff_refs) { diff_refs(symlink_to_text_commit, delete_file_commit) }
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 3,
+ new_line: nil
+ )
+ end
+ end
+ end
+
+ describe 'from text to symlink' do
+ let(:initial_commit) { project.commit('3db7bd90bab8ce8f02c9818590b84739a2e97230') }
+ let(:text_to_symlink_commit) { project.commit('5e2c2708c2e403dece5dd25759369150aac51644') }
+ let(:branch_name) { 'diff-files-text-to-symlink' }
+ let(:file_name) { 'text-to-symlink.txt' }
+
+ context "when the position is on the added text file" do
+ let(:old_file_status) { :new }
+
+ context "when the text file gets changed to a symlink between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit.parent, initial_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit.parent, text_to_symlink_commit) }
+ let(:change_diff_refs) { diff_refs(initial_commit, text_to_symlink_commit) }
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 3,
+ new_line: nil
+ )
+ end
+ end
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb
index 47d78e0b18c..fc649812b0a 100644
--- a/spec/lib/gitlab/diff/position_tracer_spec.rb
+++ b/spec/lib/gitlab/diff/position_tracer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::PositionTracer do
+RSpec.describe Gitlab::Diff::PositionTracer do
include PositionTracerHelpers
subject do
diff --git a/spec/lib/gitlab/diff/stats_cache_spec.rb b/spec/lib/gitlab/diff/stats_cache_spec.rb
new file mode 100644
index 00000000000..8bf510c0bdd
--- /dev/null
+++ b/spec/lib/gitlab/diff/stats_cache_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Diff::StatsCache, :use_clean_rails_memory_store_caching do
+ subject(:stats_cache) { described_class.new(cachable_key: cachable_key) }
+
+ let(:key) { ['diff_stats', cachable_key, described_class::VERSION].join(":") }
+ let(:cachable_key) { 'cachecachecache' }
+ let(:stat) { Gitaly::DiffStats.new(path: 'temp', additions: 10, deletions: 15) }
+ let(:stats) { Gitlab::Git::DiffStatsCollection.new([stat]) }
+ let(:cache) { Rails.cache }
+
+ describe '#read' do
+ before do
+ stats_cache.write_if_empty(stats)
+ end
+
+ it 'returns the expected stats' do
+ expect(stats_cache.read.to_json).to eq(stats.to_json)
+ end
+ end
+
+ describe '#write_if_empty' do
+ context 'when the cache already exists' do
+ before do
+ Rails.cache.write(key, true)
+ end
+
+ it 'does not write the stats' do
+ expect(cache).not_to receive(:write)
+
+ stats_cache.write_if_empty(stats)
+ end
+ end
+
+ context 'when the cache does not exist' do
+ it 'writes the stats' do
+ expect(cache)
+ .to receive(:write)
+ .with(key, stats.as_json, expires_in: described_class::EXPIRATION)
+ .and_call_original
+
+ stats_cache.write_if_empty(stats)
+
+ expect(stats_cache.read.to_a).to eq(stats.to_a)
+ end
+
+ context 'when given non utf-8 characters' do
+ let(:non_utf8_path) { '你好'.b }
+ let(:stat) { Gitaly::DiffStats.new(path: non_utf8_path, additions: 10, deletions: 15) }
+
+ it 'writes the stats' do
+ expect(cache)
+ .to receive(:write)
+ .with(key, stats.as_json, expires_in: described_class::EXPIRATION)
+ .and_call_original
+
+ stats_cache.write_if_empty(stats)
+
+ expect(stats_cache.read.to_a).to eq(stats.to_a)
+ end
+ end
+
+ context 'when given empty stats' do
+ let(:stats) { nil }
+
+ it 'does not write the stats' do
+ expect(cache).not_to receive(:write)
+
+ stats_cache.write_if_empty(stats)
+ end
+ end
+ end
+ end
+
+ describe '#clear' do
+ it 'clears cache' do
+ expect(cache).to receive(:delete).with(key)
+
+ stats_cache.clear
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/suggestion_diff_spec.rb b/spec/lib/gitlab/diff/suggestion_diff_spec.rb
index 0d4fe33bc47..9546c581112 100644
--- a/spec/lib/gitlab/diff/suggestion_diff_spec.rb
+++ b/spec/lib/gitlab/diff/suggestion_diff_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::SuggestionDiff do
+RSpec.describe Gitlab::Diff::SuggestionDiff do
describe '#diff_lines' do
let(:from_content) do
<<-BLOB.strip_heredoc
diff --git a/spec/lib/gitlab/diff/suggestion_spec.rb b/spec/lib/gitlab/diff/suggestion_spec.rb
index d7ca0e0a522..5a5c5555818 100644
--- a/spec/lib/gitlab/diff/suggestion_spec.rb
+++ b/spec/lib/gitlab/diff/suggestion_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::Suggestion do
+RSpec.describe Gitlab::Diff::Suggestion do
shared_examples 'correct suggestion raw content' do
it 'returns correct raw data' do
expect(suggestion.to_hash).to include(from_content: expected_lines.join,
diff --git a/spec/lib/gitlab/diff/suggestions_parser_spec.rb b/spec/lib/gitlab/diff/suggestions_parser_spec.rb
index 1f2af42f6e7..5efce414dc8 100644
--- a/spec/lib/gitlab/diff/suggestions_parser_spec.rb
+++ b/spec/lib/gitlab/diff/suggestions_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Diff::SuggestionsParser do
+RSpec.describe Gitlab::Diff::SuggestionsParser do
describe '.parse' do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.project }
diff --git a/spec/lib/gitlab/discussions_diff/file_collection_spec.rb b/spec/lib/gitlab/discussions_diff/file_collection_spec.rb
index a13727b62ea..f85a68ada15 100644
--- a/spec/lib/gitlab/discussions_diff/file_collection_spec.rb
+++ b/spec/lib/gitlab/discussions_diff/file_collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DiscussionsDiff::FileCollection do
+RSpec.describe Gitlab::DiscussionsDiff::FileCollection do
let(:merge_request) { create(:merge_request) }
let!(:diff_note_a) { create(:diff_note_on_merge_request, project: merge_request.project, noteable: merge_request) }
let!(:diff_note_b) { create(:diff_note_on_merge_request, project: merge_request.project, noteable: merge_request) }
diff --git a/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb b/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb
index 97d3a49ea90..9f10811d765 100644
--- a/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb
+++ b/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DiscussionsDiff::HighlightCache, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::DiscussionsDiff::HighlightCache, :clean_gitlab_redis_cache do
def fake_file(offset)
{
text: 'foo',
diff --git a/spec/lib/gitlab/doctor/secrets_spec.rb b/spec/lib/gitlab/doctor/secrets_spec.rb
index f118519fd9f..b9e054ce14f 100644
--- a/spec/lib/gitlab/doctor/secrets_spec.rb
+++ b/spec/lib/gitlab/doctor/secrets_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Doctor::Secrets do
+RSpec.describe Gitlab::Doctor::Secrets do
let!(:user) { create(:user, otp_secret: "test") }
let!(:group) { create(:group, runners_token: "test") }
let(:logger) { double(:logger).as_null_object }
diff --git a/spec/lib/gitlab/downtime_check/message_spec.rb b/spec/lib/gitlab/downtime_check/message_spec.rb
index 2beb5a19a32..2d82836db33 100644
--- a/spec/lib/gitlab/downtime_check/message_spec.rb
+++ b/spec/lib/gitlab/downtime_check/message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DowntimeCheck::Message do
+RSpec.describe Gitlab::DowntimeCheck::Message do
describe '#to_s' do
it 'returns an ANSI formatted String for an offline migration' do
message = described_class.new('foo.rb', true, 'hello')
diff --git a/spec/lib/gitlab/downtime_check_spec.rb b/spec/lib/gitlab/downtime_check_spec.rb
index 5a5e34961a4..761519425f6 100644
--- a/spec/lib/gitlab/downtime_check_spec.rb
+++ b/spec/lib/gitlab/downtime_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::DowntimeCheck do
+RSpec.describe Gitlab::DowntimeCheck do
subject { described_class.new }
let(:path) { 'foo.rb' }
diff --git a/spec/lib/gitlab/elasticsearch/logs/lines_spec.rb b/spec/lib/gitlab/elasticsearch/logs/lines_spec.rb
index 45a262c0e77..f93c1aa1974 100644
--- a/spec/lib/gitlab/elasticsearch/logs/lines_spec.rb
+++ b/spec/lib/gitlab/elasticsearch/logs/lines_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Elasticsearch::Logs::Lines do
+RSpec.describe Gitlab::Elasticsearch::Logs::Lines do
let(:client) { Elasticsearch::Transport::Client }
let(:es_message_1) { { timestamp: "2019-12-13T14:35:34.034Z", pod: "production-6866bc8974-m4sk4", message: "10.8.2.1 - - [25/Oct/2019:08:03:22 UTC] \"GET / HTTP/1.1\" 200 13" } }
diff --git a/spec/lib/gitlab/elasticsearch/logs/pods_spec.rb b/spec/lib/gitlab/elasticsearch/logs/pods_spec.rb
index c2c3074e965..07fa0980d36 100644
--- a/spec/lib/gitlab/elasticsearch/logs/pods_spec.rb
+++ b/spec/lib/gitlab/elasticsearch/logs/pods_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Elasticsearch::Logs::Pods do
+RSpec.describe Gitlab::Elasticsearch::Logs::Pods do
let(:client) { Elasticsearch::Transport::Client }
let(:es_query) { Gitlab::Json.parse(fixture_file('lib/elasticsearch/pods_query.json'), symbolize_names: true) }
diff --git a/spec/lib/gitlab/email/attachment_uploader_spec.rb b/spec/lib/gitlab/email/attachment_uploader_spec.rb
index 5104e0a3f8f..6beadb6e1a5 100644
--- a/spec/lib/gitlab/email/attachment_uploader_spec.rb
+++ b/spec/lib/gitlab/email/attachment_uploader_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::Email::AttachmentUploader do
+RSpec.describe Gitlab::Email::AttachmentUploader do
describe "#execute" do
let(:project) { create(:project) }
let(:message_raw) { fixture_file("emails/attachment.eml") }
diff --git a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
index 7833b9f387d..ee2173a9c8d 100644
--- a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Handler::CreateIssueHandler do
+RSpec.describe Gitlab::Email::Handler::CreateIssueHandler do
include_context :email_shared_context
it_behaves_like :reply_processing_shared_examples
diff --git a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
index 9f5413f9607..75d5fc040cb 100644
--- a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Handler::CreateMergeRequestHandler do
+RSpec.describe Gitlab::Email::Handler::CreateMergeRequestHandler do
include_context :email_shared_context
it_behaves_like :reply_processing_shared_examples
diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
index af963e1b695..e5598bbd10f 100644
--- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Handler::CreateNoteHandler do
+RSpec.describe Gitlab::Email::Handler::CreateNoteHandler do
include_context :email_shared_context
it_behaves_like :reply_processing_shared_examples
@@ -242,4 +242,70 @@ describe Gitlab::Email::Handler::CreateNoteHandler do
it_behaves_like 'a reply to existing comment'
end
+
+ context 'when the service desk' do
+ let(:project) { create(:project, :public, service_desk_enabled: true) }
+ let(:support_bot) { User.support_bot }
+ let(:noteable) { create(:issue, project: project, author: support_bot, title: 'service desk issue') }
+ let(:note) { create(:note, project: project, noteable: noteable) }
+ let(:email_raw) { fixture_file('emails/valid_reply_with_quick_actions.eml') }
+
+ let!(:sent_notification) do
+ SentNotification.record_note(note, support_bot.id, mail_key)
+ end
+
+ context 'is enabled' do
+ before do
+ allow(Gitlab::ServiceDesk).to receive(:enabled?).with(project: project).and_return(true)
+ project.project_feature.update!(issues_access_level: issues_access_level)
+ end
+
+ context 'when issues are enabled for everyone' do
+ let(:issues_access_level) { ProjectFeature::ENABLED }
+
+ it 'creates a comment' do
+ expect { receiver.execute }.to change { noteable.notes.count }.by(1)
+ end
+
+ context 'when quick actions are present' do
+ it 'encloses quick actions with code span markdown' do
+ receiver.execute
+ noteable.reload
+
+ note = Note.last
+ expect(note.note).to include("Jake out\n\n`/close`\n`/title test`")
+ expect(noteable.title).to eq('service desk issue')
+ expect(noteable).to be_opened
+ end
+ end
+ end
+
+ context 'when issues are protected members only' do
+ let(:issues_access_level) { ProjectFeature::PRIVATE }
+
+ it 'creates a comment' do
+ expect { receiver.execute }.to change { noteable.notes.count }.by(1)
+ end
+ end
+
+ context 'when issues are disabled' do
+ let(:issues_access_level) { ProjectFeature::DISABLED }
+
+ it 'does not create a comment' do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotAuthorizedError)
+ end
+ end
+ end
+
+ context 'is disabled' do
+ before do
+ allow(Gitlab::ServiceDesk).to receive(:enabled?).and_return(false)
+ allow(Gitlab::ServiceDesk).to receive(:enabled?).with(project: project).and_return(false)
+ end
+
+ it 'does not create a comment' do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb b/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
new file mode 100644
index 00000000000..a38fe2c51ca
--- /dev/null
+++ b/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
@@ -0,0 +1,311 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler do
+ include_context :email_shared_context
+
+ before do
+ stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.adventuretime.ooo")
+ stub_config_setting(host: 'localhost')
+ end
+
+ let(:email_raw) { email_fixture('emails/service_desk.eml') }
+ let_it_be(:namespace) { create(:namespace, name: "email") }
+ let(:expected_description) do
+ "Service desk stuff!\n\n```\na = b\n```\n\n`/label ~label1`\n`/assign @user1`\n`/close`\n![image](uploads/image.png)"
+ end
+
+ context 'service desk is enabled for the project' do
+ let_it_be(:project) { create(:project, :repository, :public, namespace: namespace, path: 'test', service_desk_enabled: true) }
+
+ before do
+ allow(Gitlab::ServiceDesk).to receive(:supported?).and_return(true)
+ end
+
+ shared_examples 'a new issue request' do
+ before do
+ setup_attachment
+ end
+
+ it 'creates a new issue' do
+ expect { receiver.execute }.to change { Issue.count }.by(1)
+
+ new_issue = Issue.last
+
+ expect(new_issue.author).to eql(User.support_bot)
+ expect(new_issue.confidential?).to be true
+ expect(new_issue.all_references.all).to be_empty
+ expect(new_issue.title).to eq("Service Desk (from jake@adventuretime.ooo): The message subject! @all")
+ expect(new_issue.description).to eq(expected_description.strip)
+ end
+
+ it 'sends thank you email' do
+ expect { receiver.execute }.to have_enqueued_job.on_queue('mailers')
+ end
+ end
+
+ context 'when everything is fine' do
+ it_behaves_like 'a new issue request'
+
+ context 'with legacy incoming email address' do
+ let(:email_raw) { fixture_file('emails/service_desk_legacy.eml') }
+
+ it_behaves_like 'a new issue request'
+ end
+
+ context 'when using issue templates' do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ setup_attachment
+ end
+
+ context 'and template is present' do
+ let_it_be(:settings) { create(:service_desk_setting, project: project) }
+
+ def set_template_file(file_name, content)
+ file_path = ".gitlab/issue_templates/#{file_name}.md"
+ project.repository.create_file(user, file_path, content, message: 'message', branch_name: 'master')
+ settings.update!(issue_template_key: file_name)
+ end
+
+ it 'appends template text to issue description' do
+ set_template_file('service_desk', 'text from template')
+
+ receiver.execute
+
+ issue_description = Issue.last.description
+ expect(issue_description).to include(expected_description)
+ expect(issue_description.lines.last).to eq('text from template')
+ end
+
+ context 'when quick actions are present' do
+ let(:label) { create(:label, project: project, title: 'label1') }
+ let(:milestone) { create(:milestone, project: project) }
+ let!(:user) { create(:user, username: 'user1') }
+
+ before do
+ project.add_developer(user)
+ end
+
+ it 'applies quick action commands present on templates' do
+ file_content = %(Text from template \n/label ~#{label.title} \n/milestone %"#{milestone.name}"")
+ set_template_file('with_slash_commands', file_content)
+
+ receiver.execute
+
+ issue = Issue.last
+ expect(issue.description).to include('Text from template')
+ expect(issue.label_ids).to include(label.id)
+ expect(issue.milestone).to eq(milestone)
+ end
+
+ it 'redacts quick actions present on user email body' do
+ set_template_file('service_desk1', 'text from template')
+
+ receiver.execute
+
+ issue = Issue.last
+ expect(issue).to be_opened
+ expect(issue.description).to include('`/label ~label1`')
+ expect(issue.description).to include('`/assign @user1`')
+ expect(issue.description).to include('`/close`')
+ expect(issue.assignees).to be_empty
+ expect(issue.milestone).to be_nil
+ end
+ end
+ end
+
+ context 'and template cannot be found' do
+ before do
+ service = ServiceDeskSetting.new(project_id: project.id, issue_template_key: 'unknown')
+ service.save!(validate: false)
+ end
+
+ it 'does not append template text to issue description' do
+ receiver.execute
+
+ new_issue = Issue.last
+
+ expect(new_issue.description).to eq(expected_description.strip)
+ end
+
+ it 'creates support bot note on issue' do
+ receiver.execute
+
+ note = Note.last
+
+ expect(note.note).to include("WARNING: The template file unknown.md used for service desk issues is empty or could not be found.")
+ expect(note.author).to eq(User.support_bot)
+ end
+
+ it 'does not send warning note email' do
+ ActionMailer::Base.deliveries = []
+
+ perform_enqueued_jobs do
+ expect { receiver.execute }.to change { ActionMailer::Base.deliveries.size }.by(1)
+ end
+
+ # Only sends created issue email
+ expect(ActionMailer::Base.deliveries.last.text_part.body).to include("Thank you for your support request!")
+ end
+ end
+ end
+
+ context 'when using service desk key' do
+ let_it_be(:service_desk_settings) { create(:service_desk_setting, project: project, project_key: 'mykey') }
+ let(:email_raw) { service_desk_fixture('emails/service_desk_custom_address.eml') }
+ let(:receiver) { Gitlab::Email::ServiceDeskReceiver.new(email_raw) }
+
+ before do
+ stub_service_desk_email_setting(enabled: true, address: 'support+%{key}@example.com')
+ end
+
+ it_behaves_like 'a new issue request'
+
+ context 'when there is no project with the key' do
+ let(:email_raw) { service_desk_fixture('emails/service_desk_custom_address.eml', key: 'some_key') }
+
+ it 'bounces the email' do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound)
+ end
+ end
+
+ context 'when the project slug does not match' do
+ let(:email_raw) { service_desk_fixture('emails/service_desk_custom_address.eml', slug: 'some-slug') }
+
+ it 'bounces the email' do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound)
+ end
+ end
+
+ context 'when service_desk_custom_address feature is disabled' do
+ before do
+ stub_feature_flags(service_desk_custom_address: false)
+ end
+
+ it 'bounces the email' do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound)
+ end
+ end
+ end
+ end
+
+ describe '#can_handle?' do
+ let(:mail) { Mail::Message.new(email_raw) }
+
+ it 'handles the new email key format' do
+ handler = described_class.new(mail, "h5bp-html5-boilerplate-#{project.project_id}-issue-")
+
+ expect(handler.instance_variable_get(:@project_id).to_i).to eq project.project_id
+ expect(handler.can_handle?).to be_truthy
+ end
+
+ it 'handles the legacy email key format' do
+ handler = described_class.new(mail, "h5bp/html5-boilerplate")
+
+ expect(handler.instance_variable_get(:@project_path)).to eq 'h5bp/html5-boilerplate'
+ expect(handler.can_handle?).to be_truthy
+ end
+
+ it "doesn't handle invalid email key" do
+ handler = described_class.new(mail, "h5bp-html5-boilerplate-invalid")
+
+ expect(handler.can_handle?).to be_falsey
+ end
+ end
+
+ context 'when there is no from address' do
+ before do
+ allow_next_instance_of(described_class) do |instance|
+ allow(instance).to receive(:from_address).and_return(nil)
+ end
+ end
+
+ it "creates a new issue" do
+ expect { receiver.execute }.to change { Issue.count }.by(1)
+ end
+
+ it 'does not send thank you email' do
+ expect { receiver.execute }.not_to have_enqueued_job.on_queue('mailers')
+ end
+ end
+
+ context 'when there is a sender address and a from address' do
+ let(:email_raw) { email_fixture('emails/service_desk_sender_and_from.eml') }
+
+ it 'prefers the from address' do
+ setup_attachment
+
+ expect { receiver.execute }.to change { Issue.count }.by(1)
+
+ new_issue = Issue.last
+
+ expect(new_issue.service_desk_reply_to).to eq('finn@adventuretime.ooo')
+ end
+ end
+
+ context 'when service desk is not enabled for project' do
+ before do
+ allow(Gitlab::ServiceDesk).to receive(:enabled?).and_return(false)
+ end
+
+ it 'does not create an issue' do
+ expect { receiver.execute rescue nil }.not_to change { Issue.count }
+ end
+
+ it 'does not send thank you email' do
+ expect { receiver.execute rescue nil }.not_to have_enqueued_job.on_queue('mailers')
+ end
+ end
+
+ context 'when the email is forwarded through an alias' do
+ let(:email_raw) { email_fixture('emails/service_desk_forwarded.eml') }
+
+ it_behaves_like 'a new issue request'
+ end
+
+ context 'when the email is forwarded' do
+ let(:email_raw) { email_fixture('emails/service_desk_forwarded_new_issue.eml') }
+
+ it_behaves_like 'a new issue request' do
+ let(:expected_description) do
+ <<~EOF
+ Service desk stuff!
+
+ ---------- Forwarded message ---------
+ From: Jake the Dog <jake@adventuretime.ooo>
+ To: <jake@adventuretime.ooo>
+
+
+ forwarded content
+
+ ![image](uploads/image.png)
+ EOF
+ end
+ end
+ end
+ end
+
+ context 'service desk is disabled for the project' do
+ let(:project) { create(:project, :public, namespace: namespace, path: 'test', service_desk_enabled: false) }
+
+ it 'bounces the email' do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::ProcessingError)
+ end
+
+ it "doesn't create an issue" do
+ expect { receiver.execute rescue nil }.not_to change { Issue.count }
+ end
+ end
+
+ def email_fixture(path)
+ fixture_file(path).gsub('project_id', project.project_id.to_s)
+ end
+
+ def service_desk_fixture(path, slug: nil, key: 'mykey')
+ slug ||= project.full_path_slug.to_s
+ fixture_file(path).gsub('project_slug', slug).gsub('project_key', key)
+ end
+end
diff --git a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
index dcddd00df59..13ad9ddd8ef 100644
--- a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Handler::UnsubscribeHandler do
+RSpec.describe Gitlab::Email::Handler::UnsubscribeHandler do
include_context :email_shared_context
before do
diff --git a/spec/lib/gitlab/email/handler_spec.rb b/spec/lib/gitlab/email/handler_spec.rb
index 6dbf069f07c..2cd8c31e6b2 100644
--- a/spec/lib/gitlab/email/handler_spec.rb
+++ b/spec/lib/gitlab/email/handler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Handler do
+RSpec.describe Gitlab::Email::Handler do
let(:email) { Mail.new { body 'email' } }
describe '.for' do
@@ -33,12 +33,40 @@ describe Gitlab::Email::Handler do
it 'returns nil if provided email is nil' do
expect(described_class.for(nil, '')).to be_nil
end
+
+ context 'new issue email' do
+ def handler_for(fixture, mail_key)
+ described_class.for(fixture_file(fixture), mail_key)
+ end
+
+ before do
+ stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.adventuretime.ooo")
+ stub_config_setting(host: 'localhost')
+ end
+
+ let!(:user) { create(:user, email: 'jake@adventuretime.ooo', incoming_email_token: 'auth_token') }
+
+ context 'a Service Desk email' do
+ it 'uses the Service Desk handler' do
+ expect(handler_for('emails/service_desk.eml', 'some/project')).to be_instance_of(Gitlab::Email::Handler::ServiceDeskHandler)
+ end
+ end
+
+ it 'return new issue handler' do
+ expect(handler_for('emails/valid_new_issue.eml', 'some/project+auth_token')).to be_instance_of(Gitlab::Email::Handler::CreateIssueHandler)
+ end
+ end
end
describe 'regexps are set properly' do
let(:addresses) do
- %W(sent_notification_key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX} sent_notification_key path-to-project-123-user_email_token-merge-request path-to-project-123-user_email_token-issue) +
- %W(sent_notification_key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX_LEGACY} sent_notification_key path/to/project+merge-request+user_email_token path/to/project+user_email_token)
+ %W(sent_notification_key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX} sent_notification_key path-to-project-123-user_email_token-merge-request) +
+ %W(sent_notification_key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX_LEGACY} sent_notification_key path-to-project-123-user_email_token-issue) +
+ %w(path/to/project+user_email_token path/to/project+merge-request+user_email_token some/project)
+ end
+
+ before do
+ allow(Gitlab::ServiceDesk).to receive(:supported?).and_return(true)
end
it 'picks each handler at least once' do
@@ -46,12 +74,12 @@ describe Gitlab::Email::Handler do
described_class.for(email, address).class
end
- expect(matched_handlers.uniq).to match_array(ce_handlers)
+ expect(matched_handlers.uniq).to match_array(Gitlab::Email::Handler.handlers)
end
it 'can pick exactly one handler for each address' do
addresses.each do |address|
- matched_handlers = ce_handlers.select do |handler|
+ matched_handlers = Gitlab::Email::Handler.handlers.select do |handler|
handler.new(email, address).can_handle?
end
@@ -59,10 +87,4 @@ describe Gitlab::Email::Handler do
end
end
end
-
- def ce_handlers
- @ce_handlers ||= Gitlab::Email::Handler.handlers.reject do |handler|
- handler.name.start_with?('Gitlab::Email::Handler::EE::')
- end
- end
end
diff --git a/spec/lib/gitlab/email/hook/additional_headers_interceptor_spec.rb b/spec/lib/gitlab/email/hook/additional_headers_interceptor_spec.rb
index 65e4e27d56f..deaa086d689 100644
--- a/spec/lib/gitlab/email/hook/additional_headers_interceptor_spec.rb
+++ b/spec/lib/gitlab/email/hook/additional_headers_interceptor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Hook::AdditionalHeadersInterceptor do
+RSpec.describe Gitlab::Email::Hook::AdditionalHeadersInterceptor do
let(:mail) do
ActionMailer::Base.mail(to: 'test@mail.com', from: 'info@mail.com', body: 'hello')
end
diff --git a/spec/lib/gitlab/email/hook/delivery_metrics_observer_spec.rb b/spec/lib/gitlab/email/hook/delivery_metrics_observer_spec.rb
index 24da47c42ac..73a0573be26 100644
--- a/spec/lib/gitlab/email/hook/delivery_metrics_observer_spec.rb
+++ b/spec/lib/gitlab/email/hook/delivery_metrics_observer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Hook::DeliveryMetricsObserver do
+RSpec.describe Gitlab::Email::Hook::DeliveryMetricsObserver do
let(:email) do
ActionMailer::Base.mail(to: 'test@example.com',
from: 'info@example.com',
diff --git a/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb b/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb
index c8ed12523d0..47f6015c6f8 100644
--- a/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb
+++ b/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Hook::DisableEmailInterceptor do
+RSpec.describe Gitlab::Email::Hook::DisableEmailInterceptor do
before do
Mail.register_interceptor(described_class)
end
diff --git a/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb b/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb
index 31ba48e9df1..56cf58dcf92 100644
--- a/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb
+++ b/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Hook::SmimeSignatureInterceptor do
+RSpec.describe Gitlab::Email::Hook::SmimeSignatureInterceptor do
include SmimeHelper
# certs generation is an expensive operation and they are used read-only,
diff --git a/spec/lib/gitlab/email/message/repository_push_spec.rb b/spec/lib/gitlab/email/message/repository_push_spec.rb
index 9e95d31f41c..10586527239 100644
--- a/spec/lib/gitlab/email/message/repository_push_spec.rb
+++ b/spec/lib/gitlab/email/message/repository_push_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Message::RepositoryPush do
+RSpec.describe Gitlab::Email::Message::RepositoryPush do
include RepoHelpers
let!(:group) { create(:group, name: 'my_group') }
diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb
index d860968ab98..592d3f3f0e4 100644
--- a/spec/lib/gitlab/email/receiver_spec.rb
+++ b/spec/lib/gitlab/email/receiver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Receiver do
+RSpec.describe Gitlab::Email::Receiver do
include_context :email_shared_context
shared_examples 'correctly finds the mail key' do
diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb
index 646575b2edd..575ff7f357b 100644
--- a/spec/lib/gitlab/email/reply_parser_spec.rb
+++ b/spec/lib/gitlab/email/reply_parser_spec.rb
@@ -3,7 +3,7 @@
require "spec_helper"
# Inspired in great part by Discourse's Email::Receiver
-describe Gitlab::Email::ReplyParser do
+RSpec.describe Gitlab::Email::ReplyParser do
describe '#execute' do
def test_parse_body(mail_string, params = {})
described_class.new(Mail::Message.new(mail_string), params).execute
diff --git a/spec/lib/gitlab/email/service_desk_receiver_spec.rb b/spec/lib/gitlab/email/service_desk_receiver_spec.rb
new file mode 100644
index 00000000000..6ba58ad5e93
--- /dev/null
+++ b/spec/lib/gitlab/email/service_desk_receiver_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Email::ServiceDeskReceiver do
+ let(:email) { fixture_file('emails/service_desk_custom_address.eml') }
+ let(:receiver) { described_class.new(email) }
+
+ context 'when the email contains a valid email address' do
+ before do
+ stub_service_desk_email_setting(enabled: true, address: 'support+%{key}@example.com')
+ end
+
+ it 'finds the service desk key' do
+ handler = double(execute: true, metrics_event: true, metrics_params: true)
+ expected_params = [
+ an_instance_of(Mail::Message), nil,
+ { service_desk_key: 'project_slug-project_key' }
+ ]
+
+ expect(Gitlab::Email::Handler::ServiceDeskHandler)
+ .to receive(:new).with(*expected_params).and_return(handler)
+
+ receiver.execute
+ end
+ end
+
+ context 'when the email does not contain a valid email address' do
+ before do
+ stub_service_desk_email_setting(enabled: true, address: 'other_support+%{key}@example.com')
+ end
+
+ it 'raises an error' do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::UnknownIncomingEmail)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/email/smime/certificate_spec.rb b/spec/lib/gitlab/email/smime/certificate_spec.rb
index 07b8c1e4de1..e4a085d971b 100644
--- a/spec/lib/gitlab/email/smime/certificate_spec.rb
+++ b/spec/lib/gitlab/email/smime/certificate_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Smime::Certificate do
+RSpec.describe Gitlab::Email::Smime::Certificate do
include SmimeHelper
# cert generation is an expensive operation and they are used read-only,
diff --git a/spec/lib/gitlab/email/smime/signer_spec.rb b/spec/lib/gitlab/email/smime/signer_spec.rb
index d891b86da08..217c0d62950 100644
--- a/spec/lib/gitlab/email/smime/signer_spec.rb
+++ b/spec/lib/gitlab/email/smime/signer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Email::Smime::Signer do
+RSpec.describe Gitlab::Email::Smime::Signer do
include SmimeHelper
let_it_be(:root_ca) { generate_root }
diff --git a/spec/lib/gitlab/emoji_spec.rb b/spec/lib/gitlab/emoji_spec.rb
new file mode 100644
index 00000000000..ada37f25d1e
--- /dev/null
+++ b/spec/lib/gitlab/emoji_spec.rb
@@ -0,0 +1,125 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Emoji do
+ let_it_be(:emojis) { Gemojione.index.instance_variable_get(:@emoji_by_name) }
+ let_it_be(:emojis_by_moji) { Gemojione.index.instance_variable_get(:@emoji_by_moji) }
+ let_it_be(:emoji_unicode_versions_by_name) { Gitlab::Json.parse(File.read(Rails.root.join('fixtures', 'emojis', 'emoji-unicode-version-map.json'))) }
+ let_it_be(:emojis_aliases) { Gitlab::Json.parse(File.read(Rails.root.join('fixtures', 'emojis', 'aliases.json'))) }
+
+ describe '.emojis' do
+ it 'returns emojis' do
+ current_emojis = described_class.emojis
+
+ expect(current_emojis).to eq(emojis)
+ end
+ end
+
+ describe '.emojis_by_moji' do
+ it 'return emojis by moji' do
+ current_emojis_by_moji = described_class.emojis_by_moji
+
+ expect(current_emojis_by_moji).to eq(emojis_by_moji)
+ end
+ end
+
+ describe '.emojis_unicodes' do
+ it 'returns emoji unicodes' do
+ emoji_keys = described_class.emojis_unicodes
+
+ expect(emoji_keys).to eq(emojis_by_moji.keys)
+ end
+ end
+
+ describe '.emojis_names' do
+ it 'returns emoji names' do
+ emoji_names = described_class.emojis_names
+
+ expect(emoji_names).to eq(emojis.keys)
+ end
+ end
+
+ describe '.emojis_aliases' do
+ it 'returns emoji aliases' do
+ emoji_aliases = described_class.emojis_aliases
+
+ expect(emoji_aliases).to eq(emojis_aliases)
+ end
+ end
+
+ describe '.emoji_filename' do
+ it 'returns emoji filename' do
+ # "100" => {"unicode"=>"1F4AF"...}
+ emoji_filename = described_class.emoji_filename('100')
+
+ expect(emoji_filename).to eq(emojis['100']['unicode'])
+ end
+ end
+
+ describe '.emoji_unicode_filename' do
+ it 'returns emoji unicode filename' do
+ emoji_unicode_filename = described_class.emoji_unicode_filename('💯')
+
+ expect(emoji_unicode_filename).to eq(emojis_by_moji['💯']['unicode'])
+ end
+ end
+
+ describe '.emoji_unicode_version' do
+ it 'returns emoji unicode version by name' do
+ emoji_unicode_version = described_class.emoji_unicode_version('100')
+
+ expect(emoji_unicode_version).to eq(emoji_unicode_versions_by_name['100'])
+ end
+ end
+
+ describe '.normalize_emoji_name' do
+ it 'returns same name if not found in aliases' do
+ emoji_name = described_class.normalize_emoji_name('random')
+
+ expect(emoji_name).to eq('random')
+ end
+
+ it 'returns name if name found in aliases' do
+ emoji_name = described_class.normalize_emoji_name('small_airplane')
+
+ expect(emoji_name).to eq(emojis_aliases['small_airplane'])
+ end
+ end
+
+ describe '.emoji_image_tag' do
+ it 'returns emoji image tag' do
+ emoji_image = described_class.emoji_image_tag('emoji_one', 'src_url')
+
+ expect(emoji_image).to eq( "<img class='emoji' title=':emoji_one:' alt=':emoji_one:' src='src_url' height='20' width='20' align='absmiddle' />")
+ end
+ end
+
+ describe '.emoji_exists?' do
+ it 'returns true if the name exists' do
+ emoji_exists = described_class.emoji_exists?('100')
+
+ expect(emoji_exists).to be_truthy
+ end
+
+ it 'returns false if the name does not exist' do
+ emoji_exists = described_class.emoji_exists?('random')
+
+ expect(emoji_exists).to be_falsey
+ end
+ end
+
+ describe '.gl_emoji_tag' do
+ it 'returns gl emoji tag if emoji is found' do
+ gl_tag = described_class.gl_emoji_tag('small_airplane')
+
+ expect(gl_tag).to eq('<gl-emoji title="small airplane" data-name="airplane_small" data-unicode-version="7.0">🛩</gl-emoji>')
+ end
+
+ it 'returns nil if emoji name is not found' do
+ gl_tag = described_class.gl_emoji_tag('random')
+
+ expect(gl_tag).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb
index e6dfd8728aa..5394c04c6ba 100644
--- a/spec/lib/gitlab/encoding_helper_spec.rb
+++ b/spec/lib/gitlab/encoding_helper_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::EncodingHelper do
+RSpec.describe Gitlab::EncodingHelper do
let(:ext_class) { Class.new { extend Gitlab::EncodingHelper } }
let(:binary_string) { File.read(Rails.root + "spec/fixtures/dk.png") }
diff --git a/spec/lib/gitlab/error_tracking/stack_trace_highlight_decorator_spec.rb b/spec/lib/gitlab/error_tracking/stack_trace_highlight_decorator_spec.rb
index d553fb4848b..5c496d653b2 100644
--- a/spec/lib/gitlab/error_tracking/stack_trace_highlight_decorator_spec.rb
+++ b/spec/lib/gitlab/error_tracking/stack_trace_highlight_decorator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ErrorTracking::StackTraceHighlightDecorator do
+RSpec.describe Gitlab::ErrorTracking::StackTraceHighlightDecorator do
let(:error_event) { build(:error_tracking_error_event) }
describe '.decorate' do
diff --git a/spec/lib/gitlab/error_tracking_spec.rb b/spec/lib/gitlab/error_tracking_spec.rb
index c40369f5965..2cc9ff36c99 100644
--- a/spec/lib/gitlab/error_tracking_spec.rb
+++ b/spec/lib/gitlab/error_tracking_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require 'raven/transports/dummy'
-describe Gitlab::ErrorTracking do
+RSpec.describe Gitlab::ErrorTracking do
let(:exception) { RuntimeError.new('boom') }
let(:issue_url) { 'http://gitlab.com/gitlab-org/gitlab-foss/issues/1' }
@@ -18,6 +18,8 @@ describe Gitlab::ErrorTracking do
]
end
+ let(:sentry_event) { Gitlab::Json.parse(Raven.client.transport.events.last[1]) }
+
before do
stub_sentry_settings
@@ -29,6 +31,86 @@ describe Gitlab::ErrorTracking do
end
end
+ describe '.configure' do
+ context 'default tags from GITLAB_SENTRY_EXTRA_TAGS' do
+ context 'when the value is a JSON hash' do
+ it 'includes those tags in all events' do
+ stub_env('GITLAB_SENTRY_EXTRA_TAGS', { foo: 'bar', baz: 'quux' }.to_json)
+
+ described_class.configure do |config|
+ config.encoding = 'json'
+ end
+
+ described_class.track_exception(StandardError.new)
+
+ expect(sentry_event['tags'].except('correlation_id', 'locale', 'program'))
+ .to eq('foo' => 'bar', 'baz' => 'quux')
+ end
+ end
+
+ context 'when the value is not set' do
+ before do
+ stub_env('GITLAB_SENTRY_EXTRA_TAGS', nil)
+ end
+
+ it 'does not log an error' do
+ expect(Gitlab::AppLogger).not_to receive(:debug)
+
+ described_class.configure do |config|
+ config.encoding = 'json'
+ end
+ end
+
+ it 'does not send any extra tags' do
+ described_class.configure do |config|
+ config.encoding = 'json'
+ end
+
+ described_class.track_exception(StandardError.new)
+
+ expect(sentry_event['tags'].keys).to contain_exactly('correlation_id', 'locale', 'program')
+ end
+ end
+
+ context 'when the value is not a JSON hash' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:env_var, :error) do
+ { foo: 'bar', baz: 'quux' }.inspect | 'JSON::ParserError'
+ [].to_json | 'NoMethodError'
+ [%w[foo bar]].to_json | 'NoMethodError'
+ %w[foo bar].to_json | 'NoMethodError'
+ '"string"' | 'NoMethodError'
+ end
+
+ with_them do
+ before do
+ stub_env('GITLAB_SENTRY_EXTRA_TAGS', env_var)
+ end
+
+ it 'does not include any extra tags' do
+ described_class.configure do |config|
+ config.encoding = 'json'
+ end
+
+ described_class.track_exception(StandardError.new)
+
+ expect(sentry_event['tags'].except('correlation_id', 'locale', 'program'))
+ .to be_empty
+ end
+
+ it 'logs the error class' do
+ expect(Gitlab::AppLogger).to receive(:debug).with(a_string_matching(error))
+
+ described_class.configure do |config|
+ config.encoding = 'json'
+ end
+ end
+ end
+ end
+ end
+ end
+
describe '.with_context' do
it 'adds the expected tags' do
described_class.with_context {}
@@ -202,8 +284,6 @@ describe Gitlab::ErrorTracking do
described_class.track_exception(exception, extra)
- sentry_event = Gitlab::Json.parse(Raven.client.transport.events.last[1])
-
expect(sentry_event.dig('extra', 'sidekiq', 'args')).to eq(['[FILTERED]', 1, 2])
end
end
diff --git a/spec/lib/gitlab/etag_caching/middleware_spec.rb b/spec/lib/gitlab/etag_caching/middleware_spec.rb
index 5e9df555241..361b2329e15 100644
--- a/spec/lib/gitlab/etag_caching/middleware_spec.rb
+++ b/spec/lib/gitlab/etag_caching/middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::EtagCaching::Middleware do
+RSpec.describe Gitlab::EtagCaching::Middleware do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
let(:app_status_code) { 200 }
diff --git a/spec/lib/gitlab/etag_caching/router_spec.rb b/spec/lib/gitlab/etag_caching/router_spec.rb
index d9eeb5b9a2b..3e939e588ad 100644
--- a/spec/lib/gitlab/etag_caching/router_spec.rb
+++ b/spec/lib/gitlab/etag_caching/router_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::EtagCaching::Router do
+RSpec.describe Gitlab::EtagCaching::Router do
it 'matches issue notes endpoint' do
result = described_class.match(
'/my-group/and-subgroup/here-comes-the-project/noteable/issue/1/notes'
diff --git a/spec/lib/gitlab/exclusive_lease_helpers/sleeping_lock_spec.rb b/spec/lib/gitlab/exclusive_lease_helpers/sleeping_lock_spec.rb
index 8917eeec56f..f74fbf1206f 100644
--- a/spec/lib/gitlab/exclusive_lease_helpers/sleeping_lock_spec.rb
+++ b/spec/lib/gitlab/exclusive_lease_helpers/sleeping_lock_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ExclusiveLeaseHelpers::SleepingLock, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::ExclusiveLeaseHelpers::SleepingLock, :clean_gitlab_redis_shared_state do
include ::ExclusiveLeaseHelpers
let(:timeout) { 1.second }
diff --git a/spec/lib/gitlab/exclusive_lease_helpers_spec.rb b/spec/lib/gitlab/exclusive_lease_helpers_spec.rb
index 9914518cda5..01e2fe8ce17 100644
--- a/spec/lib/gitlab/exclusive_lease_helpers_spec.rb
+++ b/spec/lib/gitlab/exclusive_lease_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ExclusiveLeaseHelpers, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::ExclusiveLeaseHelpers, :clean_gitlab_redis_shared_state do
include ::ExclusiveLeaseHelpers
let(:class_instance) { (Class.new { include ::Gitlab::ExclusiveLeaseHelpers }).new }
diff --git a/spec/lib/gitlab/exclusive_lease_spec.rb b/spec/lib/gitlab/exclusive_lease_spec.rb
index 2c0bb23a0b6..e730ddd6577 100644
--- a/spec/lib/gitlab/exclusive_lease_spec.rb
+++ b/spec/lib/gitlab/exclusive_lease_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ExclusiveLease, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::ExclusiveLease, :clean_gitlab_redis_shared_state do
let(:unique_key) { SecureRandom.hex(10) }
describe '#try_obtain' do
diff --git a/spec/lib/gitlab/experimentation_spec.rb b/spec/lib/gitlab/experimentation_spec.rb
index f6e6c031624..a6408aeae8b 100644
--- a/spec/lib/gitlab/experimentation_spec.rb
+++ b/spec/lib/gitlab/experimentation_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Experimentation do
+RSpec.describe Gitlab::Experimentation do
before do
stub_const('Gitlab::Experimentation::EXPERIMENTS', {
test_experiment: {
diff --git a/spec/lib/gitlab/external_authorization/access_spec.rb b/spec/lib/gitlab/external_authorization/access_spec.rb
index 8a08b2a6275..4bb81230ac0 100644
--- a/spec/lib/gitlab/external_authorization/access_spec.rb
+++ b/spec/lib/gitlab/external_authorization/access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ExternalAuthorization::Access, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::ExternalAuthorization::Access, :clean_gitlab_redis_cache do
subject(:access) { described_class.new(build(:user), 'dummy_label') }
describe '#loaded?' do
diff --git a/spec/lib/gitlab/external_authorization/cache_spec.rb b/spec/lib/gitlab/external_authorization/cache_spec.rb
index 1f217249f97..9037c04cf2b 100644
--- a/spec/lib/gitlab/external_authorization/cache_spec.rb
+++ b/spec/lib/gitlab/external_authorization/cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ExternalAuthorization::Cache, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::ExternalAuthorization::Cache, :clean_gitlab_redis_cache do
let(:user) { build_stubbed(:user) }
let(:cache_key) { "external_authorization:user-#{user.id}:label-dummy_label" }
diff --git a/spec/lib/gitlab/external_authorization/client_spec.rb b/spec/lib/gitlab/external_authorization/client_spec.rb
index e28a155a47f..473b57441fa 100644
--- a/spec/lib/gitlab/external_authorization/client_spec.rb
+++ b/spec/lib/gitlab/external_authorization/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ExternalAuthorization::Client do
+RSpec.describe Gitlab::ExternalAuthorization::Client do
let(:user) { build(:user, email: 'dummy_user@example.com') }
let(:dummy_url) { 'https://dummy.net/' }
diff --git a/spec/lib/gitlab/external_authorization/logger_spec.rb b/spec/lib/gitlab/external_authorization/logger_spec.rb
index 380e765309c..167ce500911 100644
--- a/spec/lib/gitlab/external_authorization/logger_spec.rb
+++ b/spec/lib/gitlab/external_authorization/logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ExternalAuthorization::Logger do
+RSpec.describe Gitlab::ExternalAuthorization::Logger do
let(:request_time) { Time.parse('2018-03-26 20:22:15') }
def fake_access(has_access, user, load_type = :request)
diff --git a/spec/lib/gitlab/external_authorization/response_spec.rb b/spec/lib/gitlab/external_authorization/response_spec.rb
index 5ce3325ef77..11f83feb76f 100644
--- a/spec/lib/gitlab/external_authorization/response_spec.rb
+++ b/spec/lib/gitlab/external_authorization/response_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ExternalAuthorization::Response do
+RSpec.describe Gitlab::ExternalAuthorization::Response do
let(:excon_response) { double }
subject(:response) { described_class.new(excon_response) }
diff --git a/spec/lib/gitlab/external_authorization_spec.rb b/spec/lib/gitlab/external_authorization_spec.rb
index 97055e7b3f9..76025d70e9a 100644
--- a/spec/lib/gitlab/external_authorization_spec.rb
+++ b/spec/lib/gitlab/external_authorization_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ExternalAuthorization, :request_store do
+RSpec.describe Gitlab::ExternalAuthorization, :request_store do
include ExternalAuthorizationServiceHelpers
let(:user) { build(:user) }
diff --git a/spec/lib/gitlab/fake_application_settings_spec.rb b/spec/lib/gitlab/fake_application_settings_spec.rb
index 6a872185713..ec32afcfb7b 100644
--- a/spec/lib/gitlab/fake_application_settings_spec.rb
+++ b/spec/lib/gitlab/fake_application_settings_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::FakeApplicationSettings do
+RSpec.describe Gitlab::FakeApplicationSettings do
let(:defaults) do
described_class.defaults.merge(
foobar: 'asdf',
diff --git a/spec/lib/gitlab/file_detector_spec.rb b/spec/lib/gitlab/file_detector_spec.rb
index 5bf70ef898a..8c0c56ea2c3 100644
--- a/spec/lib/gitlab/file_detector_spec.rb
+++ b/spec/lib/gitlab/file_detector_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::FileDetector do
+RSpec.describe Gitlab::FileDetector do
describe '.types_in_paths' do
it 'returns the file types for the given paths' do
expect(described_class.types_in_paths(%w(README.md CHANGELOG VERSION VERSION)))
diff --git a/spec/lib/gitlab/file_finder_spec.rb b/spec/lib/gitlab/file_finder_spec.rb
index 90aa759671a..36fb4c48fb2 100644
--- a/spec/lib/gitlab/file_finder_spec.rb
+++ b/spec/lib/gitlab/file_finder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::FileFinder do
+RSpec.describe Gitlab::FileFinder do
describe '#find' do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/lib/gitlab/file_hook_spec.rb b/spec/lib/gitlab/file_hook_spec.rb
index fda3583289b..7f40d9ae772 100644
--- a/spec/lib/gitlab/file_hook_spec.rb
+++ b/spec/lib/gitlab/file_hook_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::FileHook do
+RSpec.describe Gitlab::FileHook do
let(:file_hook) { Rails.root.join('file_hooks', 'test.rb') }
let(:tmp_file) { Tempfile.new('file_hook-dump') }
diff --git a/spec/lib/gitlab/file_markdown_link_builder_spec.rb b/spec/lib/gitlab/file_markdown_link_builder_spec.rb
index de0ac9733e6..ea21bda12d3 100644
--- a/spec/lib/gitlab/file_markdown_link_builder_spec.rb
+++ b/spec/lib/gitlab/file_markdown_link_builder_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::FileMarkdownLinkBuilder do
+RSpec.describe Gitlab::FileMarkdownLinkBuilder do
let(:custom_class) do
Class.new do
include Gitlab::FileMarkdownLinkBuilder
diff --git a/spec/lib/gitlab/file_type_detection_spec.rb b/spec/lib/gitlab/file_type_detection_spec.rb
index 2f1fc57c559..ba5e7cfabf2 100644
--- a/spec/lib/gitlab/file_type_detection_spec.rb
+++ b/spec/lib/gitlab/file_type_detection_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::FileTypeDetection do
+RSpec.describe Gitlab::FileTypeDetection do
describe '.extension_match?' do
let(:extensions) { %w[foo bar] }
diff --git a/spec/lib/gitlab/fogbugz_import/client_spec.rb b/spec/lib/gitlab/fogbugz_import/client_spec.rb
index ca6f374476c..560cb98ccfd 100644
--- a/spec/lib/gitlab/fogbugz_import/client_spec.rb
+++ b/spec/lib/gitlab/fogbugz_import/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::FogbugzImport::Client do
+RSpec.describe Gitlab::FogbugzImport::Client do
let(:client) { described_class.new(uri: '', token: '') }
let(:one_user) { { 'people' => { 'person' => { "ixPerson" => "2", "sFullName" => "James" } } } }
let(:two_users) { { 'people' => { 'person' => [one_user, { "ixPerson" => "3" }] } } }
diff --git a/spec/lib/gitlab/fogbugz_import/importer_spec.rb b/spec/lib/gitlab/fogbugz_import/importer_spec.rb
index 9e67047eeda..d2be3e3f6b1 100644
--- a/spec/lib/gitlab/fogbugz_import/importer_spec.rb
+++ b/spec/lib/gitlab/fogbugz_import/importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::FogbugzImport::Importer do
+RSpec.describe Gitlab::FogbugzImport::Importer do
let(:project) { create(:project_empty_repo) }
let(:importer) { described_class.new(project) }
let(:repo) do
diff --git a/spec/lib/gitlab/fogbugz_import/project_creator_spec.rb b/spec/lib/gitlab/fogbugz_import/project_creator_spec.rb
index 503fe897e29..6b8bb2229a9 100644
--- a/spec/lib/gitlab/fogbugz_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/fogbugz_import/project_creator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::FogbugzImport::ProjectCreator do
+RSpec.describe Gitlab::FogbugzImport::ProjectCreator do
let(:user) { create(:user) }
let(:repo) do
diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
index 335135696ef..1c9004262c5 100644
--- a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
+++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Gfm::ReferenceRewriter do
+RSpec.describe Gitlab::Gfm::ReferenceRewriter do
let_it_be(:group) { create(:group) }
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
index 7279399d1b8..5b78acc3b1d 100644
--- a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
+++ b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Gfm::UploadsRewriter do
+RSpec.describe Gitlab::Gfm::UploadsRewriter do
let(:user) { create(:user) }
let(:old_project) { create(:project) }
let(:new_project) { create(:project) }
diff --git a/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb b/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb
index 6c4f650fa83..96cd70b4ff1 100644
--- a/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb
+++ b/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::AttributesAtRefParser, :seed_helper do
+RSpec.describe Gitlab::Git::AttributesAtRefParser, :seed_helper do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
diff --git a/spec/lib/gitlab/git/attributes_parser_spec.rb b/spec/lib/gitlab/git/attributes_parser_spec.rb
index 45db4acd3ac..4bc39921e85 100644
--- a/spec/lib/gitlab/git/attributes_parser_spec.rb
+++ b/spec/lib/gitlab/git/attributes_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::AttributesParser, :seed_helper do
+RSpec.describe Gitlab::Git::AttributesParser, :seed_helper do
let(:attributes_path) { File.join(SEED_STORAGE_PATH, 'with-git-attributes.git', 'info', 'attributes') }
let(:data) { File.read(attributes_path) }
diff --git a/spec/lib/gitlab/git/blame_spec.rb b/spec/lib/gitlab/git/blame_spec.rb
index 9b2d6fa3bcb..67d7b37dd45 100644
--- a/spec/lib/gitlab/git/blame_spec.rb
+++ b/spec/lib/gitlab/git/blame_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::Git::Blame, :seed_helper do
+RSpec.describe Gitlab::Git::Blame, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
let(:blame) do
Gitlab::Git::Blame.new(repository, SeedRepo::Commit::ID, "CONTRIBUTING.md")
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index 46d9b78c14b..fb4510a78de 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::Git::Blob, :seed_helper do
+RSpec.describe Gitlab::Git::Blob, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
let(:rugged) do
Rugged::Repository.new(File.join(TestEnv.repos_path, TEST_REPO_PATH))
diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb
index cb3f4df2dbd..bac1b4c57f9 100644
--- a/spec/lib/gitlab/git/branch_spec.rb
+++ b/spec/lib/gitlab/git/branch_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::Git::Branch, :seed_helper do
+RSpec.describe Gitlab::Git::Branch, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
let(:rugged) do
Rugged::Repository.new(File.join(TestEnv.repos_path, repository.relative_path))
diff --git a/spec/lib/gitlab/git/bundle_file_spec.rb b/spec/lib/gitlab/git/bundle_file_spec.rb
index e88e163a03f..701febadc1b 100644
--- a/spec/lib/gitlab/git/bundle_file_spec.rb
+++ b/spec/lib/gitlab/git/bundle_file_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::BundleFile do
+RSpec.describe Gitlab::Git::BundleFile do
describe '.check!' do
let(:valid_bundle) { Tempfile.new }
let(:valid_bundle_path) { valid_bundle.path }
diff --git a/spec/lib/gitlab/git/changes_spec.rb b/spec/lib/gitlab/git/changes_spec.rb
index 7f56d30cb48..310be7a3731 100644
--- a/spec/lib/gitlab/git/changes_spec.rb
+++ b/spec/lib/gitlab/git/changes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::Changes do
+RSpec.describe Gitlab::Git::Changes do
let(:changes) { described_class.new }
describe '#includes_branches?' do
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index edd367673fb..666b49f27f7 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::Git::Commit, :seed_helper do
+RSpec.describe Gitlab::Git::Commit, :seed_helper do
include GitHelpers
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
@@ -227,6 +227,34 @@ describe Gitlab::Git::Commit, :seed_helper do
end
end
+ context 'pathspec' do
+ let(:pathspec) { 'files/ruby/*' }
+
+ context 'with default literal_pathspec value' do
+ it 'finds the seed commit' do
+ commit = described_class.last_for_path(repository, 'master', pathspec)
+
+ expect(commit.id).to eq(SeedRepo::Commit::ID)
+ end
+ end
+
+ context 'with literal_pathspec set to false' do
+ it 'finds the seed commit' do
+ commit = described_class.last_for_path(repository, 'master', pathspec, literal_pathspec: false)
+
+ expect(commit.id).to eq(SeedRepo::Commit::ID)
+ end
+ end
+
+ context 'with literal_pathspec set to true' do
+ it 'does not find the seed commit' do
+ commit = described_class.last_for_path(repository, 'master', pathspec, literal_pathspec: true)
+
+ expect(commit).to be_nil
+ end
+ end
+ end
+
context 'ref + path' do
subject { described_class.last_for_path(repository, SeedRepo::Commit::ID, 'encoding') }
@@ -560,7 +588,7 @@ describe Gitlab::Git::Commit, :seed_helper do
end
skip 'move this test to gitaly-ruby' do
- describe '#init_from_rugged' do
+ RSpec.describe '#init_from_rugged' do
let(:gitlab_commit) { described_class.new(repository, rugged_commit) }
subject { gitlab_commit }
diff --git a/spec/lib/gitlab/git/compare_spec.rb b/spec/lib/gitlab/git/compare_spec.rb
index 6136df57acb..51043355ede 100644
--- a/spec/lib/gitlab/git/compare_spec.rb
+++ b/spec/lib/gitlab/git/compare_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::Git::Compare, :seed_helper do
+RSpec.describe Gitlab::Git::Compare, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
let(:compare) { Gitlab::Git::Compare.new(repository, SeedRepo::BigCommit::ID, SeedRepo::Commit::ID, straight: false) }
let(:compare_straight) { Gitlab::Git::Compare.new(repository, SeedRepo::BigCommit::ID, SeedRepo::Commit::ID, straight: true) }
diff --git a/spec/lib/gitlab/git/conflict/file_spec.rb b/spec/lib/gitlab/git/conflict/file_spec.rb
index 0ee9ff93e87..454a48a1d3a 100644
--- a/spec/lib/gitlab/git/conflict/file_spec.rb
+++ b/spec/lib/gitlab/git/conflict/file_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::Conflict::File do
+RSpec.describe Gitlab::Git::Conflict::File do
let(:conflict) { { theirs: { path: 'foo', mode: 33188 }, ours: { path: 'foo', mode: 33188 } } }
let(:invalid_content) { described_class.new(nil, nil, conflict, (+"a\xC4\xFC").force_encoding(Encoding::ASCII_8BIT)) }
let(:valid_content) { described_class.new(nil, nil, conflict, (+"Espa\xC3\xB1a").force_encoding(Encoding::ASCII_8BIT)) }
diff --git a/spec/lib/gitlab/git/conflict/parser_spec.rb b/spec/lib/gitlab/git/conflict/parser_spec.rb
index 92ea44394b1..67bd48256ce 100644
--- a/spec/lib/gitlab/git/conflict/parser_spec.rb
+++ b/spec/lib/gitlab/git/conflict/parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::Conflict::Parser do
+RSpec.describe Gitlab::Git::Conflict::Parser do
describe '.parse' do
def parse_text(text)
described_class.parse(text, our_path: 'README.md', their_path: 'README.md')
diff --git a/spec/lib/gitlab/git/cross_repo_comparer_spec.rb b/spec/lib/gitlab/git/cross_repo_comparer_spec.rb
index 8b37b6d1667..1c49486b7b1 100644
--- a/spec/lib/gitlab/git/cross_repo_comparer_spec.rb
+++ b/spec/lib/gitlab/git/cross_repo_comparer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::CrossRepoComparer do
+RSpec.describe Gitlab::Git::CrossRepoComparer do
let(:source_project) { create(:project, :repository) }
let(:target_project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb
index 6aa4f884d20..6da07ce84a1 100644
--- a/spec/lib/gitlab/git/diff_collection_spec.rb
+++ b/spec/lib/gitlab/git/diff_collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::DiffCollection, :seed_helper do
+RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
before do
stub_const('MutatingConstantIterator', Class.new)
diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb
index ff54d7fbcd3..91688c31f5e 100644
--- a/spec/lib/gitlab/git/diff_spec.rb
+++ b/spec/lib/gitlab/git/diff_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::Git::Diff, :seed_helper do
+RSpec.describe Gitlab::Git::Diff, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
let(:gitaly_diff) do
Gitlab::GitalyClient::Diff.new(
diff --git a/spec/lib/gitlab/git/diff_stats_collection_spec.rb b/spec/lib/gitlab/git/diff_stats_collection_spec.rb
index 82d15a49062..f2fe03829be 100644
--- a/spec/lib/gitlab/git/diff_stats_collection_spec.rb
+++ b/spec/lib/gitlab/git/diff_stats_collection_spec.rb
@@ -2,13 +2,13 @@
require "spec_helper"
-describe Gitlab::Git::DiffStatsCollection do
+RSpec.describe Gitlab::Git::DiffStatsCollection do
let(:stats_a) do
- double(Gitaly::DiffStats, additions: 10, deletions: 15, path: 'foo')
+ Gitaly::DiffStats.new(additions: 10, deletions: 15, path: 'foo')
end
let(:stats_b) do
- double(Gitaly::DiffStats, additions: 5, deletions: 1, path: 'bar')
+ Gitaly::DiffStats.new(additions: 5, deletions: 1, path: 'bar')
end
let(:diff_stats) { [stats_a, stats_b] }
diff --git a/spec/lib/gitlab/git/gitmodules_parser_spec.rb b/spec/lib/gitlab/git/gitmodules_parser_spec.rb
index 58d1d2c71da..0e386c7f3d1 100644
--- a/spec/lib/gitlab/git/gitmodules_parser_spec.rb
+++ b/spec/lib/gitlab/git/gitmodules_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::GitmodulesParser do
+RSpec.describe Gitlab::Git::GitmodulesParser do
it 'parses a .gitmodules file correctly' do
data = <<~GITMODULES
[submodule "vendor/libgit2"]
diff --git a/spec/lib/gitlab/git/hook_env_spec.rb b/spec/lib/gitlab/git/hook_env_spec.rb
index 22b016cee3e..c8f9218916e 100644
--- a/spec/lib/gitlab/git/hook_env_spec.rb
+++ b/spec/lib/gitlab/git/hook_env_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::HookEnv do
+RSpec.describe Gitlab::Git::HookEnv do
let(:gl_repository) { 'project-123' }
describe ".set" do
diff --git a/spec/lib/gitlab/git/keep_around_spec.rb b/spec/lib/gitlab/git/keep_around_spec.rb
index 04ccf86cd28..44c3caf3f8d 100644
--- a/spec/lib/gitlab/git/keep_around_spec.rb
+++ b/spec/lib/gitlab/git/keep_around_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::KeepAround do
+RSpec.describe Gitlab::Git::KeepAround do
include RepoHelpers
let(:repository) { create(:project, :repository).repository }
diff --git a/spec/lib/gitlab/git/lfs_changes_spec.rb b/spec/lib/gitlab/git/lfs_changes_spec.rb
index adc63401b89..286c5b98771 100644
--- a/spec/lib/gitlab/git/lfs_changes_spec.rb
+++ b/spec/lib/gitlab/git/lfs_changes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::LfsChanges do
+RSpec.describe Gitlab::Git::LfsChanges do
let_it_be(:project) { create(:project, :repository) }
let(:newrev) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' }
let(:blob_object_id) { '0c304a93cb8430108629bbbcaa27db3343299bc0' }
diff --git a/spec/lib/gitlab/git/lfs_pointer_file_spec.rb b/spec/lib/gitlab/git/lfs_pointer_file_spec.rb
index 8bb26ed4854..f45c7cccca0 100644
--- a/spec/lib/gitlab/git/lfs_pointer_file_spec.rb
+++ b/spec/lib/gitlab/git/lfs_pointer_file_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::LfsPointerFile do
+RSpec.describe Gitlab::Git::LfsPointerFile do
let(:data) { "1234\n" }
subject { described_class.new(data) }
diff --git a/spec/lib/gitlab/git/merge_base_spec.rb b/spec/lib/gitlab/git/merge_base_spec.rb
index d92b13c5023..1410e44a220 100644
--- a/spec/lib/gitlab/git/merge_base_spec.rb
+++ b/spec/lib/gitlab/git/merge_base_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::MergeBase do
+RSpec.describe Gitlab::Git::MergeBase do
let_it_be(:project) { create(:project, :repository) }
let(:repository) { project.repository }
diff --git a/spec/lib/gitlab/git/object_pool_spec.rb b/spec/lib/gitlab/git/object_pool_spec.rb
index ebeb7b7b633..c8fbc674c73 100644
--- a/spec/lib/gitlab/git/object_pool_spec.rb
+++ b/spec/lib/gitlab/git/object_pool_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::ObjectPool do
+RSpec.describe Gitlab::Git::ObjectPool do
include RepoHelpers
let(:pool_repository) { create(:pool_repository) }
diff --git a/spec/lib/gitlab/git/patches/collection_spec.rb b/spec/lib/gitlab/git/patches/collection_spec.rb
index 080be141c59..eb92f4663c8 100644
--- a/spec/lib/gitlab/git/patches/collection_spec.rb
+++ b/spec/lib/gitlab/git/patches/collection_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::Git::Patches::Collection do
+RSpec.describe Gitlab::Git::Patches::Collection do
let(:patches_folder) { Rails.root.join('spec/fixtures/patchfiles') }
let(:patch_content1) do
File.read(File.join(patches_folder, "0001-This-does-not-apply-to-the-feature-branch.patch"))
diff --git a/spec/lib/gitlab/git/patches/commit_patches_spec.rb b/spec/lib/gitlab/git/patches/commit_patches_spec.rb
index 760112155ce..cd1e03a6de0 100644
--- a/spec/lib/gitlab/git/patches/commit_patches_spec.rb
+++ b/spec/lib/gitlab/git/patches/commit_patches_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::Git::Patches::CommitPatches do
+RSpec.describe Gitlab::Git::Patches::CommitPatches do
describe '#commit' do
let(:patches) do
patches_folder = Rails.root.join('spec/fixtures/patchfiles')
diff --git a/spec/lib/gitlab/git/patches/patch_spec.rb b/spec/lib/gitlab/git/patches/patch_spec.rb
index 7466e853b65..629f43d3636 100644
--- a/spec/lib/gitlab/git/patches/patch_spec.rb
+++ b/spec/lib/gitlab/git/patches/patch_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::Git::Patches::Patch do
+RSpec.describe Gitlab::Git::Patches::Patch do
let(:patches_folder) { Rails.root.join('spec/fixtures/patchfiles') }
let(:patch_content) do
File.read(File.join(patches_folder, "0001-This-does-not-apply-to-the-feature-branch.patch"))
diff --git a/spec/lib/gitlab/git/pre_receive_error_spec.rb b/spec/lib/gitlab/git/pre_receive_error_spec.rb
index cb539261671..bf4530c8945 100644
--- a/spec/lib/gitlab/git/pre_receive_error_spec.rb
+++ b/spec/lib/gitlab/git/pre_receive_error_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::PreReceiveError do
+RSpec.describe Gitlab::Git::PreReceiveError do
Gitlab::Git::PreReceiveError::SAFE_MESSAGE_PREFIXES.each do |prefix|
context "error messages prefixed with #{prefix}" do
it 'accepts only errors lines with the prefix' do
diff --git a/spec/lib/gitlab/git/push_spec.rb b/spec/lib/gitlab/git/push_spec.rb
index c09e8a085df..0f52f10c0a6 100644
--- a/spec/lib/gitlab/git/push_spec.rb
+++ b/spec/lib/gitlab/git/push_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::Push do
+RSpec.describe Gitlab::Git::Push do
let_it_be(:project) { create(:project, :repository) }
let(:oldrev) { project.commit('HEAD~2').id }
let(:newrev) { project.commit.id }
diff --git a/spec/lib/gitlab/git/raw_diff_change_spec.rb b/spec/lib/gitlab/git/raw_diff_change_spec.rb
index 79b2fc21011..f894ae1d98b 100644
--- a/spec/lib/gitlab/git/raw_diff_change_spec.rb
+++ b/spec/lib/gitlab/git/raw_diff_change_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::RawDiffChange do
+RSpec.describe Gitlab::Git::RawDiffChange do
let(:raw_change) { }
let(:change) { described_class.new(raw_change) }
diff --git a/spec/lib/gitlab/git/remote_mirror_spec.rb b/spec/lib/gitlab/git/remote_mirror_spec.rb
index edef91b8bc6..423c4aa9620 100644
--- a/spec/lib/gitlab/git/remote_mirror_spec.rb
+++ b/spec/lib/gitlab/git/remote_mirror_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::RemoteMirror do
+RSpec.describe Gitlab::Git::RemoteMirror do
describe '#update' do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
diff --git a/spec/lib/gitlab/git/remote_repository_spec.rb b/spec/lib/gitlab/git/remote_repository_spec.rb
index b53eee293f0..84c17234ae4 100644
--- a/spec/lib/gitlab/git/remote_repository_spec.rb
+++ b/spec/lib/gitlab/git/remote_repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::RemoteRepository, :seed_helper do
+RSpec.describe Gitlab::Git::RemoteRepository, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
subject { described_class.new(repository) }
diff --git a/spec/lib/gitlab/git/repository_cleaner_spec.rb b/spec/lib/gitlab/git/repository_cleaner_spec.rb
index b387d1033d3..9f1bf9e48ee 100644
--- a/spec/lib/gitlab/git/repository_cleaner_spec.rb
+++ b/spec/lib/gitlab/git/repository_cleaner_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::RepositoryCleaner do
+RSpec.describe Gitlab::Git::RepositoryCleaner do
include HttpIOHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 183e6e8d044..e7f4573c95f 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::Git::Repository, :seed_helper do
+RSpec.describe Gitlab::Git::Repository, :seed_helper do
include Gitlab::EncodingHelper
include RepoHelpers
using RSpec::Parameterized::TableSyntax
@@ -2187,34 +2187,47 @@ describe Gitlab::Git::Repository, :seed_helper do
'gitaly_address' => Gitlab.config.repositories.storages.default.gitaly_address,
'path' => TestEnv::SECOND_STORAGE_PATH
})
- new_repository.create_repository
end
after do
new_repository.remove
end
- it 'mirrors the source repository' do
- subject
+ context 'destination does not exist' do
+ it 'mirrors the source repository' do
+ subject
- expect(refs(new_repository_path)).to eq(refs(repository_path))
+ expect(refs(new_repository_path)).to eq(refs(repository_path))
+ end
end
- context 'with keep-around refs' do
- let(:sha) { SeedRepo::Commit::ID }
- let(:keep_around_ref) { "refs/keep-around/#{sha}" }
- let(:tmp_ref) { "refs/tmp/#{SecureRandom.hex}" }
-
+ context 'destination exists' do
before do
- repository.write_ref(keep_around_ref, sha)
- repository.write_ref(tmp_ref, sha)
+ new_repository.create_repository
end
- it 'includes the temporary and keep-around refs' do
+ it 'mirrors the source repository' do
subject
- expect(refs(new_repository_path)).to include(keep_around_ref)
- expect(refs(new_repository_path)).to include(tmp_ref)
+ expect(refs(new_repository_path)).to eq(refs(repository_path))
+ end
+
+ context 'with keep-around refs' do
+ let(:sha) { SeedRepo::Commit::ID }
+ let(:keep_around_ref) { "refs/keep-around/#{sha}" }
+ let(:tmp_ref) { "refs/tmp/#{SecureRandom.hex}" }
+
+ before do
+ repository.write_ref(keep_around_ref, sha)
+ repository.write_ref(tmp_ref, sha)
+ end
+
+ it 'includes the temporary and keep-around refs' do
+ subject
+
+ expect(refs(new_repository_path)).to include(keep_around_ref)
+ expect(refs(new_repository_path)).to include(tmp_ref)
+ end
end
end
end
diff --git a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
index 8339006fe9f..4f6a3fb823e 100644
--- a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
+++ b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require 'json'
require 'tempfile'
-describe Gitlab::Git::RuggedImpl::UseRugged, :seed_helper do
+RSpec.describe Gitlab::Git::RuggedImpl::UseRugged, :seed_helper do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:feature_flag_name) { 'feature-flag-name' }
diff --git a/spec/lib/gitlab/git/tag_spec.rb b/spec/lib/gitlab/git/tag_spec.rb
index 6d3b239c38f..f83ccc6cae0 100644
--- a/spec/lib/gitlab/git/tag_spec.rb
+++ b/spec/lib/gitlab/git/tag_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::Git::Tag, :seed_helper do
+RSpec.describe Gitlab::Git::Tag, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
describe '#tags' do
diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb
index b254dd3f036..5ef964ac3c1 100644
--- a/spec/lib/gitlab/git/tree_spec.rb
+++ b/spec/lib/gitlab/git/tree_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::Git::Tree, :seed_helper do
+RSpec.describe Gitlab::Git::Tree, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
shared_examples :repo do
diff --git a/spec/lib/gitlab/git/user_spec.rb b/spec/lib/gitlab/git/user_spec.rb
index 6761413320a..4414195ebf4 100644
--- a/spec/lib/gitlab/git/user_spec.rb
+++ b/spec/lib/gitlab/git/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::User do
+RSpec.describe Gitlab::Git::User do
let(:username) { 'janedoe' }
let(:name) { 'Jane Doé' }
let(:email) { 'janedoé@example.com' }
diff --git a/spec/lib/gitlab/git/util_spec.rb b/spec/lib/gitlab/git/util_spec.rb
index 81918f036f9..a0237c821b5 100644
--- a/spec/lib/gitlab/git/util_spec.rb
+++ b/spec/lib/gitlab/git/util_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::Util do
+RSpec.describe Gitlab::Git::Util do
describe '#count_lines' do
[
["", 0],
diff --git a/spec/lib/gitlab/git/wiki_spec.rb b/spec/lib/gitlab/git/wiki_spec.rb
index 8bae2e8125e..a88097705f6 100644
--- a/spec/lib/gitlab/git/wiki_spec.rb
+++ b/spec/lib/gitlab/git/wiki_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::Wiki do
+RSpec.describe Gitlab::Git::Wiki do
using RSpec::Parameterized::TableSyntax
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb b/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb
index a4489cca443..e448277b307 100644
--- a/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb
+++ b/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git::WrapsGitalyErrors do
+RSpec.describe Gitlab::Git::WrapsGitalyErrors do
subject(:wrapper) do
klazz = Class.new { include Gitlab::Git::WrapsGitalyErrors }
klazz.new
diff --git a/spec/lib/gitlab/git_access_design_spec.rb b/spec/lib/gitlab/git_access_design_spec.rb
index d816608f7e5..ee25f6c2979 100644
--- a/spec/lib/gitlab/git_access_design_spec.rb
+++ b/spec/lib/gitlab/git_access_design_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::GitAccessDesign do
+RSpec.describe Gitlab::GitAccessDesign do
include DesignManagementTestHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/lib/gitlab/git_access_project_spec.rb b/spec/lib/gitlab/git_access_project_spec.rb
index f7f7976ccb8..520300363c9 100644
--- a/spec/lib/gitlab/git_access_project_spec.rb
+++ b/spec/lib/gitlab/git_access_project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitAccessProject do
+RSpec.describe Gitlab::GitAccessProject do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
let(:actor) { user }
diff --git a/spec/lib/gitlab/git_access_snippet_spec.rb b/spec/lib/gitlab/git_access_snippet_spec.rb
index 48b425a8ec5..3b8b5fd82c6 100644
--- a/spec/lib/gitlab/git_access_snippet_spec.rb
+++ b/spec/lib/gitlab/git_access_snippet_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitAccessSnippet do
+RSpec.describe Gitlab::GitAccessSnippet do
include ProjectHelpers
include TermsHelper
include_context 'ProjectPolicyTable context'
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 7c09fc5cc79..01691f87092 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitAccess do
+RSpec.describe Gitlab::GitAccess do
include TermsHelper
include GitHelpers
diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb
index e42570804a8..738269e4a14 100644
--- a/spec/lib/gitlab/git_access_wiki_spec.rb
+++ b/spec/lib/gitlab/git_access_wiki_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitAccessWiki do
+RSpec.describe Gitlab::GitAccessWiki do
let(:access) { described_class.new(user, project, 'web', authentication_abilities: authentication_abilities, redirected_path: redirected_path) }
let(:project) { create(:project, :wiki_repo) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/git_post_receive_spec.rb b/spec/lib/gitlab/git_post_receive_spec.rb
index 0e25a616810..45d536a437f 100644
--- a/spec/lib/gitlab/git_post_receive_spec.rb
+++ b/spec/lib/gitlab/git_post_receive_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::GitPostReceive do
+RSpec.describe ::Gitlab::GitPostReceive do
let_it_be(:project) { create(:project, :repository) }
subject { described_class.new(project, "project-#{project.id}", changes.dup, {}) }
diff --git a/spec/lib/gitlab/git_ref_validator_spec.rb b/spec/lib/gitlab/git_ref_validator_spec.rb
index 28cc13f02de..6938ad51189 100644
--- a/spec/lib/gitlab/git_ref_validator_spec.rb
+++ b/spec/lib/gitlab/git_ref_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitRefValidator do
+RSpec.describe Gitlab::GitRefValidator do
using RSpec::Parameterized::TableSyntax
describe '.validate' do
diff --git a/spec/lib/gitlab/git_spec.rb b/spec/lib/gitlab/git_spec.rb
index d6d12b84724..784d25f55c1 100644
--- a/spec/lib/gitlab/git_spec.rb
+++ b/spec/lib/gitlab/git_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Git do
+RSpec.describe Gitlab::Git do
let(:committer_email) { 'user@example.org' }
let(:committer_name) { 'John Doe' }
diff --git a/spec/lib/gitlab/gitaly_client/blob_service_spec.rb b/spec/lib/gitlab/gitaly_client/blob_service_spec.rb
index e609acc8fb0..037734f1b13 100644
--- a/spec/lib/gitlab/gitaly_client/blob_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/blob_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::BlobService do
+RSpec.describe Gitlab::GitalyClient::BlobService do
let(:project) { create(:project, :repository) }
let(:storage_name) { project.repository_storage }
let(:relative_path) { project.disk_path + '.git' }
diff --git a/spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb b/spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb
index e88b86c71f2..e0c3e8d4b40 100644
--- a/spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::BlobsStitcher do
+RSpec.describe Gitlab::GitalyClient::BlobsStitcher do
describe 'enumeration' do
it 'combines segregated blob messages together' do
messages = [
diff --git a/spec/lib/gitlab/gitaly_client/call_spec.rb b/spec/lib/gitlab/gitaly_client/call_spec.rb
new file mode 100644
index 00000000000..5c33ac40460
--- /dev/null
+++ b/spec/lib/gitlab/gitaly_client/call_spec.rb
@@ -0,0 +1,122 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GitalyClient::Call do
+ describe '#call', :request_store do
+ let(:client) { Gitlab::GitalyClient }
+ let(:storage) { 'default' }
+ let(:remote_storage) { nil }
+ let(:request) { Gitaly::FindLocalBranchesRequest.new }
+ let(:rpc) { :find_local_branches }
+ let(:service) { :ref_service }
+ let(:timeout) { client.long_timeout }
+
+ subject do
+ described_class.new(storage, service, rpc, request, remote_storage, timeout).call
+ end
+
+ before do
+ allow(client).to receive(:execute) { response }
+ allow(Gitlab::PerformanceBar).to receive(:enabled_for_request?) { true }
+ end
+
+ def expect_call_details_to_match(duration_higher_than: 0)
+ expect(client.list_call_details.size).to eq(1)
+ expect(client.list_call_details.first)
+ .to match a_hash_including(feature: "#{service}##{rpc}",
+ duration: a_value > duration_higher_than,
+ request: an_instance_of(Hash),
+ rpc: rpc,
+ backtrace: an_instance_of(Array))
+ end
+
+ context 'when the response is not an enumerator' do
+ let(:response) do
+ Gitaly::FindLocalBranchesResponse.new
+ end
+
+ it 'returns the response' do
+ expect(subject).to eq(response)
+ end
+
+ it 'stores timings and call details' do
+ subject
+
+ expect(client.query_time).to be > 0
+ expect_call_details_to_match
+ end
+
+ context 'when err' do
+ before do
+ allow(client).to receive(:execute).and_raise(StandardError)
+ end
+
+ it 'stores timings and call details' do
+ expect { subject }.to raise_error(StandardError)
+
+ expect(client.query_time).to be > 0
+ expect_call_details_to_match
+ end
+ end
+ end
+
+ context 'when the response is an enumerator' do
+ let(:response) do
+ Enumerator.new do |yielder|
+ yielder << 1
+ yielder << 2
+ end
+ end
+
+ it 'returns a consumable enumerator' do
+ instrumented_response = subject
+
+ expect(instrumented_response).to be_a(Enumerator)
+ expect(instrumented_response.to_a).to eq([1, 2])
+ end
+
+ context 'time measurements' do
+ let(:response) do
+ Enumerator.new do |yielder|
+ sleep 0.1
+ yielder << 1
+ sleep 0.2
+ yielder << 2
+ end
+ end
+
+ it 'records full rpc stream consumption' do
+ subject.to_a
+
+ expect(client.query_time).to be > 0.3
+ expect_call_details_to_match(duration_higher_than: 0.3)
+ end
+
+ it 'records partial rpc stream consumption' do
+ subject.first
+
+ expect(client.query_time).to be > 0.1
+ expect_call_details_to_match(duration_higher_than: 0.1)
+ end
+
+ context 'when err' do
+ let(:response) do
+ Enumerator.new do |yielder|
+ sleep 0.2
+ yielder << 1
+ raise StandardError
+ end
+ end
+
+ it 'records partial rpc stream consumption' do
+ expect { subject.to_a }.to raise_error(StandardError)
+
+ expect(client.query_time).to be > 0.2
+ expect_call_details_to_match(duration_higher_than: 0.2)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb b/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb
index 07f53797b2a..3381c69ea0d 100644
--- a/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::CleanupService do
+RSpec.describe Gitlab::GitalyClient::CleanupService do
let(:project) { create(:project) }
let(:storage_name) { project.repository_storage }
let(:relative_path) { project.disk_path + '.git' }
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
index 29a5ef0d2fc..45a25ccfc88 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::CommitService do
+RSpec.describe Gitlab::GitalyClient::CommitService do
let(:project) { create(:project, :repository) }
let(:storage_name) { project.repository_storage }
let(:relative_path) { project.disk_path + '.git' }
@@ -290,7 +290,8 @@ describe Gitlab::GitalyClient::CommitService do
request = Gitaly::FindCommitsRequest.new(
repository: repository_message,
disable_walk: true,
- order: 'NONE'
+ order: 'NONE',
+ global_options: Gitaly::GlobalOptions.new(literal_pathspecs: false)
)
expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commits)
@@ -303,7 +304,8 @@ describe Gitlab::GitalyClient::CommitService do
request = Gitaly::FindCommitsRequest.new(
repository: repository_message,
disable_walk: true,
- order: 'TOPO'
+ order: 'TOPO',
+ global_options: Gitaly::GlobalOptions.new(literal_pathspecs: false)
)
expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commits)
@@ -317,7 +319,8 @@ describe Gitlab::GitalyClient::CommitService do
repository: repository_message,
disable_walk: true,
order: 'NONE',
- author: "Billy Baggins <bilbo@shire.com>"
+ author: "Billy Baggins <bilbo@shire.com>",
+ global_options: Gitaly::GlobalOptions.new(literal_pathspecs: false)
)
expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commits)
@@ -338,7 +341,8 @@ describe Gitlab::GitalyClient::CommitService do
revision: (options[:revision] || '').dup.force_encoding(Encoding::ASCII_8BIT),
path: (options[:path] || '').dup.force_encoding(Encoding::ASCII_8BIT),
limit: (options[:limit] || 1000).to_i,
- offset: (options[:offset] || 0).to_i
+ offset: (options[:offset] || 0).to_i,
+ global_options: Gitaly::GlobalOptions.new(literal_pathspecs: true)
)
allow_any_instance_of(Gitaly::CommitService::Stub)
@@ -381,4 +385,19 @@ describe Gitlab::GitalyClient::CommitService do
commits.map { |commit| Gitlab::Git::Commit.new(repository, commit) }
end
end
+
+ describe '#list_commits_by_ref_name' do
+ let(:project) { create(:project, :repository, create_branch: 'ü/unicode/multi-byte') }
+
+ it 'lists latest commits grouped by a ref name' do
+ response = client.list_commits_by_ref_name(%w[master feature v1.0.0 nonexistent ü/unicode/multi-byte])
+
+ expect(response.keys.count).to eq 4
+ expect(response.fetch('master').id).to eq 'b83d6e391c22777fca1ed3012fce84f633d7fed0'
+ expect(response.fetch('feature').id).to eq '0b4bc9a49b562e85de7cc9e834518ea6828729b9'
+ expect(response.fetch('v1.0.0').id).to eq '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9'
+ expect(response.fetch('ü/unicode/multi-byte')).to be_present
+ expect(response).not_to have_key 'nonexistent'
+ end
+ end
end
diff --git a/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb b/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb
index db734b1c129..0bb8628af6c 100644
--- a/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::ConflictFilesStitcher do
+RSpec.describe Gitlab::GitalyClient::ConflictFilesStitcher do
describe 'enumeration' do
it 'combines segregated ConflictFile messages together' do
target_project = create(:project, :repository)
diff --git a/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb b/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb
index f19bcae2470..b016e8bdf5a 100644
--- a/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::ConflictsService do
+RSpec.describe Gitlab::GitalyClient::ConflictsService do
let(:project) { create(:project, :repository) }
let(:target_project) { create(:project, :repository) }
let(:source_repository) { project.repository.raw }
diff --git a/spec/lib/gitlab/gitaly_client/diff_spec.rb b/spec/lib/gitlab/gitaly_client/diff_spec.rb
index d86497da7f5..230322faecd 100644
--- a/spec/lib/gitlab/gitaly_client/diff_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/diff_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::Diff do
+RSpec.describe Gitlab::GitalyClient::Diff do
let(:diff_fields) do
{
to_path: ".gitmodules",
diff --git a/spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb b/spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb
index c9d42ad32cf..113c47b4f2c 100644
--- a/spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::DiffStitcher do
+RSpec.describe Gitlab::GitalyClient::DiffStitcher do
describe 'enumeration' do
it 'combines segregated diff messages together' do
diff_1 = OpenStruct.new(
diff --git a/spec/lib/gitlab/gitaly_client/health_check_service_spec.rb b/spec/lib/gitlab/gitaly_client/health_check_service_spec.rb
index 615bc80fff2..0855544d11e 100644
--- a/spec/lib/gitlab/gitaly_client/health_check_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/health_check_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::HealthCheckService do
+RSpec.describe Gitlab::GitalyClient::HealthCheckService do
let(:project) { create(:project) }
let(:storage_name) { project.repository_storage }
diff --git a/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb b/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb
index 0e0c3d329b5..15eebf62a39 100644
--- a/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::ObjectPoolService do
+RSpec.describe Gitlab::GitalyClient::ObjectPoolService do
let(:pool_repository) { create(:pool_repository) }
let(:project) { create(:project, :repository) }
let(:raw_repository) { project.repository.raw }
diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
index 45701b501bb..4e16f760235 100644
--- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::OperationService do
+RSpec.describe Gitlab::GitalyClient::OperationService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
let(:repository) { project.repository.raw }
@@ -191,6 +191,20 @@ describe Gitlab::GitalyClient::OperationService do
it { expect(subject).to be_nil }
end
+
+ context "when the pre-receive hook fails" do
+ let(:response) do
+ Gitaly::UserFFBranchResponse.new(
+ branch_update: nil,
+ pre_receive_error: "pre-receive hook error message\n"
+ )
+ end
+
+ it "raises the error" do
+ # the PreReceiveError class strips the GL-HOOK-ERR prefix from this error
+ expect { subject }.to raise_error(Gitlab::Git::PreReceiveError, "pre-receive hook failed.")
+ end
+ end
end
shared_examples 'cherry pick and revert errors' do
diff --git a/spec/lib/gitlab/gitaly_client/praefect_info_service_spec.rb b/spec/lib/gitlab/gitaly_client/praefect_info_service_spec.rb
index 9b5c751e0ec..5f90745f4e0 100644
--- a/spec/lib/gitlab/gitaly_client/praefect_info_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/praefect_info_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::PraefectInfoService do
+RSpec.describe Gitlab::GitalyClient::PraefectInfoService do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:gitaly_repository) { repository.gitaly_repository }
diff --git a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
index d4a7f6e6df9..a4c6e30bba8 100644
--- a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::RefService do
+RSpec.describe Gitlab::GitalyClient::RefService do
let(:project) { create(:project, :repository) }
let(:storage_name) { project.repository_storage }
let(:relative_path) { project.disk_path + '.git' }
@@ -34,7 +34,7 @@ describe Gitlab::GitalyClient::RefService do
subject
end
- it 'concantes and returns the response branches as Gitlab::Git::Branch objects' do
+ it 'concatenates and returns the response branches as Gitlab::Git::Branch objects' do
target_commits = create_list(:gitaly_commit, 4)
response_branches = target_commits.each_with_index.map do |gitaly_commit, i|
Gitaly::Branch.new(name: "#{remote_name}/#{i}", target_commit: gitaly_commit)
@@ -59,6 +59,17 @@ describe Gitlab::GitalyClient::RefService do
end
end
+ describe '#merged_branches' do
+ it 'sends a find_all_branches message' do
+ expect_any_instance_of(Gitaly::RefService::Stub)
+ .to receive(:find_all_branches)
+ .with(gitaly_request_with_params(merged_only: true, merged_branches: ['test']), kind_of(Hash))
+ .and_return([])
+
+ client.merged_branches(%w(test))
+ end
+ end
+
describe '#branch_names' do
it 'sends a find_all_branch_names message' do
expect_any_instance_of(Gitaly::RefService::Stub)
@@ -135,6 +146,38 @@ describe Gitlab::GitalyClient::RefService do
end
end
+ describe '#tags' do
+ it 'sends a find_all_tags message' do
+ expect_any_instance_of(Gitaly::RefService::Stub)
+ .to receive(:find_all_tags)
+ .and_return([])
+
+ client.tags
+ end
+ end
+
+ describe '#branch_names_contains_sha' do
+ it 'sends a list_branch_names_containing_commit message' do
+ expect_any_instance_of(Gitaly::RefService::Stub)
+ .to receive(:list_branch_names_containing_commit)
+ .with(gitaly_request_with_params(commit_id: '123', limit: 0), kind_of(Hash))
+ .and_return([])
+
+ client.branch_names_contains_sha('123')
+ end
+ end
+
+ describe '#get_tag_messages' do
+ it 'sends a get_tag_messages message' do
+ expect_any_instance_of(Gitaly::RefService::Stub)
+ .to receive(:get_tag_messages)
+ .with(gitaly_request_with_params(tag_ids: ['some_tag_id']), kind_of(Hash))
+ .and_return([])
+
+ client.get_tag_messages(['some_tag_id'])
+ end
+ end
+
describe '#find_ref_name', :seed_helper do
subject { client.find_ref_name(SeedRepo::Commit::ID, 'refs/heads/master') }
diff --git a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
index 2bddec739fc..b9ef76e1f41 100644
--- a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::RemoteService do
+RSpec.describe Gitlab::GitalyClient::RemoteService do
let(:project) { create(:project) }
let(:storage_name) { project.repository_storage }
let(:relative_path) { project.disk_path + '.git' }
@@ -73,8 +73,11 @@ describe Gitlab::GitalyClient::RemoteService do
describe '.exists?' do
context "when the remote doesn't exist" do
let(:url) { 'https://gitlab.com/gitlab-org/ik-besta-niet-of-ik-word-geplaagd.git' }
+ let(:storage_name) { 'default' }
it 'returns false' do
+ expect(Gitaly::FindRemoteRepositoryRequest).to receive(:new).with(remote: url, storage_name: storage_name).and_call_original
+
expect(described_class.exists?(url)).to be(false)
end
end
diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
index 5f4147f6ff6..c7ea0a95596 100644
--- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::RepositoryService do
+RSpec.describe Gitlab::GitalyClient::RepositoryService do
using RSpec::Parameterized::TableSyntax
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb b/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb
index a6b29489df3..0c4c8de52ae 100644
--- a/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::StorageSettings do
+RSpec.describe Gitlab::GitalyClient::StorageSettings do
describe "#initialize" do
context 'when the storage contains no path' do
it 'raises an error' do
diff --git a/spec/lib/gitlab/gitaly_client/util_spec.rb b/spec/lib/gitlab/gitaly_client/util_spec.rb
index f31b7c349ff..b6589a08f7d 100644
--- a/spec/lib/gitlab/gitaly_client/util_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/util_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::Util do
+RSpec.describe Gitlab::GitalyClient::Util do
describe '.repository' do
let(:repository_storage) { 'default' }
let(:relative_path) { 'my/repo.git' }
diff --git a/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb b/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb
index cb04f9a1637..a06f8459963 100644
--- a/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::WikiService do
+RSpec.describe Gitlab::GitalyClient::WikiService do
let(:project) { create(:project) }
let(:storage_name) { project.repository_storage }
let(:relative_path) { project.disk_path + '.git' }
diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
index c2b989c2fdc..16dd2bbee6d 100644
--- a/spec/lib/gitlab/gitaly_client_spec.rb
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -4,7 +4,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 do
+RSpec.describe Gitlab::GitalyClient do
let(:sample_cert) { Rails.root.join('spec/fixtures/clusters/sample_cert.pem').to_s }
before do
@@ -521,8 +521,6 @@ describe Gitlab::GitalyClient do
context 'when the request store is active', :request_store do
it 'records call details if a RPC is called' do
- expect(described_class).to receive(:measure_timings).and_call_original
-
gitaly_server.server_version
expect(described_class.list_call_details).not_to be_empty
diff --git a/spec/lib/gitlab/github_import/bulk_importing_spec.rb b/spec/lib/gitlab/github_import/bulk_importing_spec.rb
index 3266ec4ab50..63dce51c5da 100644
--- a/spec/lib/gitlab/github_import/bulk_importing_spec.rb
+++ b/spec/lib/gitlab/github_import/bulk_importing_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::BulkImporting do
+RSpec.describe Gitlab::GithubImport::BulkImporting do
let(:importer) do
Class.new { include(Gitlab::GithubImport::BulkImporting) }.new
end
diff --git a/spec/lib/gitlab/github_import/client_spec.rb b/spec/lib/gitlab/github_import/client_spec.rb
index 3b269d64b07..7cff6ed1388 100644
--- a/spec/lib/gitlab/github_import/client_spec.rb
+++ b/spec/lib/gitlab/github_import/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Client do
+RSpec.describe Gitlab::GithubImport::Client do
describe '#parallel?' do
it 'returns true when the client is running in parallel mode' do
client = described_class.new('foo', parallel: true)
diff --git a/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb b/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb
index 66909976b43..9eea85526f5 100644
--- a/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::DiffNoteImporter do
+RSpec.describe Gitlab::GithubImport::Importer::DiffNoteImporter do
let(:project) { create(:project) }
let(:client) { double(:client) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/github_import/importer/diff_notes_importer_spec.rb b/spec/lib/gitlab/github_import/importer/diff_notes_importer_spec.rb
index 23ed21294e3..7750e508713 100644
--- a/spec/lib/gitlab/github_import/importer/diff_notes_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/diff_notes_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::DiffNotesImporter do
+RSpec.describe Gitlab::GithubImport::Importer::DiffNotesImporter do
let(:project) { double(:project, id: 4, import_source: 'foo/bar') }
let(:client) { double(:client) }
diff --git a/spec/lib/gitlab/github_import/importer/issue_and_label_links_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_and_label_links_importer_spec.rb
index 399e2d9a563..49a76fb5e6b 100644
--- a/spec/lib/gitlab/github_import/importer/issue_and_label_links_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/issue_and_label_links_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::IssueAndLabelLinksImporter do
+RSpec.describe Gitlab::GithubImport::Importer::IssueAndLabelLinksImporter do
describe '#execute' do
it 'imports an issue and its labels' do
issue = double(:issue)
diff --git a/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
index 0f2ba99f816..fb826c987e1 100644
--- a/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redis_cache do
let(:project) { create(:project) }
let(:client) { double(:client) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/github_import/importer/issues_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issues_importer_spec.rb
index 8920ef9fedb..2c2b6a2aff0 100644
--- a/spec/lib/gitlab/github_import/importer/issues_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/issues_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::IssuesImporter do
+RSpec.describe Gitlab::GithubImport::Importer::IssuesImporter do
let(:project) { double(:project, id: 4, import_source: 'foo/bar') }
let(:client) { double(:client) }
let(:created_at) { Time.new(2017, 1, 1, 12, 00) }
diff --git a/spec/lib/gitlab/github_import/importer/label_links_importer_spec.rb b/spec/lib/gitlab/github_import/importer/label_links_importer_spec.rb
index 9c02b0e280f..4d3245fc988 100644
--- a/spec/lib/gitlab/github_import/importer/label_links_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/label_links_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::LabelLinksImporter do
+RSpec.describe Gitlab::GithubImport::Importer::LabelLinksImporter do
let(:project) { create(:project) }
let(:client) { double(:client) }
let(:issue) do
diff --git a/spec/lib/gitlab/github_import/importer/labels_importer_spec.rb b/spec/lib/gitlab/github_import/importer/labels_importer_spec.rb
index 16326da9ca4..0010b959a49 100644
--- a/spec/lib/gitlab/github_import/importer/labels_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/labels_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::LabelsImporter, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::GithubImport::Importer::LabelsImporter, :clean_gitlab_redis_cache do
let(:project) { create(:project, import_source: 'foo/bar') }
let(:client) { double(:client) }
let(:importer) { described_class.new(project, client) }
diff --git a/spec/lib/gitlab/github_import/importer/lfs_object_importer_spec.rb b/spec/lib/gitlab/github_import/importer/lfs_object_importer_spec.rb
index a02b620f131..add554992f1 100644
--- a/spec/lib/gitlab/github_import/importer/lfs_object_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/lfs_object_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::LfsObjectImporter do
+RSpec.describe Gitlab::GithubImport::Importer::LfsObjectImporter do
let(:project) { create(:project) }
let(:lfs_attributes) do
{
diff --git a/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb b/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb
index bec039a48eb..1f7b14661c2 100644
--- a/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::LfsObjectsImporter do
+RSpec.describe Gitlab::GithubImport::Importer::LfsObjectsImporter do
let(:project) { double(:project, id: 4, import_source: 'foo/bar') }
let(:client) { double(:client) }
let(:download_link) { "http://www.gitlab.com/lfs_objects/oid" }
diff --git a/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb b/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb
index 294599c02f4..dad1efc5a8d 100644
--- a/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::MilestonesImporter, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::GithubImport::Importer::MilestonesImporter, :clean_gitlab_redis_cache do
let(:project) { create(:project, import_source: 'foo/bar') }
let(:client) { double(:client) }
let(:importer) { described_class.new(project, client) }
diff --git a/spec/lib/gitlab/github_import/importer/note_importer_spec.rb b/spec/lib/gitlab/github_import/importer/note_importer_spec.rb
index 816041b771b..3bb57e152fe 100644
--- a/spec/lib/gitlab/github_import/importer/note_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/note_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::NoteImporter do
+RSpec.describe Gitlab::GithubImport::Importer::NoteImporter do
let(:client) { double(:client) }
let(:project) { create(:project) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/github_import/importer/notes_importer_spec.rb b/spec/lib/gitlab/github_import/importer/notes_importer_spec.rb
index 128f8f95fa0..3782dab5ee3 100644
--- a/spec/lib/gitlab/github_import/importer/notes_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/notes_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::NotesImporter do
+RSpec.describe Gitlab::GithubImport::Importer::NotesImporter do
let(:project) { double(:project, id: 4, import_source: 'foo/bar') }
let(:client) { double(:client) }
diff --git a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
index bffae9e2ba0..46850618945 100644
--- a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redis_cache do
let(:project) { create(:project, :repository) }
let(:client) { double(:client) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb
index 526a5589743..05ac0248ec9 100644
--- a/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::PullRequestsImporter do
+RSpec.describe Gitlab::GithubImport::Importer::PullRequestsImporter do
let(:project) { create(:project, import_source: 'foo/bar') }
let(:client) { double(:client) }
diff --git a/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb b/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb
index f8d53208619..1a25824bc8a 100644
--- a/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::ReleasesImporter do
+RSpec.describe Gitlab::GithubImport::Importer::ReleasesImporter do
let(:project) { create(:project) }
let(:client) { double(:client) }
let(:importer) { described_class.new(project, client) }
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 e26ac7bf81e..65dba2711b9 100644
--- a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer::RepositoryImporter do
+RSpec.describe Gitlab::GithubImport::Importer::RepositoryImporter do
let(:repository) { double(:repository) }
let(:import_state) { double(:import_state) }
let(:client) { double(:client) }
diff --git a/spec/lib/gitlab/github_import/issuable_finder_spec.rb b/spec/lib/gitlab/github_import/issuable_finder_spec.rb
index 55add863d43..f009b61ad89 100644
--- a/spec/lib/gitlab/github_import/issuable_finder_spec.rb
+++ b/spec/lib/gitlab/github_import/issuable_finder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::IssuableFinder, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::GithubImport::IssuableFinder, :clean_gitlab_redis_cache do
let(:project) { double(:project, id: 4) }
let(:issue) do
double(:issue, issuable_type: MergeRequest, iid: 1)
diff --git a/spec/lib/gitlab/github_import/label_finder_spec.rb b/spec/lib/gitlab/github_import/label_finder_spec.rb
index bb946a15a2d..452f3c896a4 100644
--- a/spec/lib/gitlab/github_import/label_finder_spec.rb
+++ b/spec/lib/gitlab/github_import/label_finder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::LabelFinder, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::GithubImport::LabelFinder, :clean_gitlab_redis_cache do
let(:project) { create(:project) }
let(:finder) { described_class.new(project) }
let!(:bug) { create(:label, project: project, name: 'Bug') }
diff --git a/spec/lib/gitlab/github_import/markdown_text_spec.rb b/spec/lib/gitlab/github_import/markdown_text_spec.rb
index a1216db7aac..4995caa0733 100644
--- a/spec/lib/gitlab/github_import/markdown_text_spec.rb
+++ b/spec/lib/gitlab/github_import/markdown_text_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::MarkdownText do
+RSpec.describe Gitlab::GithubImport::MarkdownText do
describe '.format' do
it 'formats the text' do
author = double(:author, login: 'Alice')
diff --git a/spec/lib/gitlab/github_import/milestone_finder_spec.rb b/spec/lib/gitlab/github_import/milestone_finder_spec.rb
index ecb533b7e39..419184d6115 100644
--- a/spec/lib/gitlab/github_import/milestone_finder_spec.rb
+++ b/spec/lib/gitlab/github_import/milestone_finder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::MilestoneFinder, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::GithubImport::MilestoneFinder, :clean_gitlab_redis_cache do
let!(:project) { create(:project) }
let!(:milestone) { create(:milestone, project: project) }
let(:finder) { described_class.new(project) }
diff --git a/spec/lib/gitlab/github_import/page_counter_spec.rb b/spec/lib/gitlab/github_import/page_counter_spec.rb
index 95125c9c22f..a1305b714b5 100644
--- a/spec/lib/gitlab/github_import/page_counter_spec.rb
+++ b/spec/lib/gitlab/github_import/page_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::PageCounter, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::GithubImport::PageCounter, :clean_gitlab_redis_cache do
let(:project) { double(:project, id: 1) }
let(:counter) { described_class.new(project, :issues) }
diff --git a/spec/lib/gitlab/github_import/parallel_importer_spec.rb b/spec/lib/gitlab/github_import/parallel_importer_spec.rb
index a8d5cc58bd5..06304bf84ca 100644
--- a/spec/lib/gitlab/github_import/parallel_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/parallel_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::ParallelImporter do
+RSpec.describe Gitlab::GithubImport::ParallelImporter do
describe '.async?' do
it 'returns true' do
expect(described_class).to be_async
diff --git a/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb b/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb
index a6ae99b395c..578743be96b 100644
--- a/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb
+++ b/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::ParallelScheduling do
+RSpec.describe Gitlab::GithubImport::ParallelScheduling do
let(:importer_class) do
Class.new do
include(Gitlab::GithubImport::ParallelScheduling)
diff --git a/spec/lib/gitlab/github_import/representation/diff_note_spec.rb b/spec/lib/gitlab/github_import/representation/diff_note_spec.rb
index e743a87cdd1..7e540674258 100644
--- a/spec/lib/gitlab/github_import/representation/diff_note_spec.rb
+++ b/spec/lib/gitlab/github_import/representation/diff_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Representation::DiffNote do
+RSpec.describe Gitlab::GithubImport::Representation::DiffNote do
let(:hunk) do
'@@ -1 +1 @@
-Hello
diff --git a/spec/lib/gitlab/github_import/representation/expose_attribute_spec.rb b/spec/lib/gitlab/github_import/representation/expose_attribute_spec.rb
index e3b48df4ae9..d40be0e841c 100644
--- a/spec/lib/gitlab/github_import/representation/expose_attribute_spec.rb
+++ b/spec/lib/gitlab/github_import/representation/expose_attribute_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Representation::ExposeAttribute do
+RSpec.describe Gitlab::GithubImport::Representation::ExposeAttribute do
it 'defines a getter method that returns an attribute value' do
klass = Class.new do
include Gitlab::GithubImport::Representation::ExposeAttribute
diff --git a/spec/lib/gitlab/github_import/representation/issue_spec.rb b/spec/lib/gitlab/github_import/representation/issue_spec.rb
index 741a912e53b..3d306a4a3a3 100644
--- a/spec/lib/gitlab/github_import/representation/issue_spec.rb
+++ b/spec/lib/gitlab/github_import/representation/issue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Representation::Issue do
+RSpec.describe Gitlab::GithubImport::Representation::Issue do
let(:created_at) { Time.new(2017, 1, 1, 12, 00) }
let(:updated_at) { Time.new(2017, 1, 1, 12, 15) }
diff --git a/spec/lib/gitlab/github_import/representation/note_spec.rb b/spec/lib/gitlab/github_import/representation/note_spec.rb
index a171a38bc9e..112bb7eb908 100644
--- a/spec/lib/gitlab/github_import/representation/note_spec.rb
+++ b/spec/lib/gitlab/github_import/representation/note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Representation::Note do
+RSpec.describe Gitlab::GithubImport::Representation::Note do
let(:created_at) { Time.new(2017, 1, 1, 12, 00) }
let(:updated_at) { Time.new(2017, 1, 1, 12, 15) }
diff --git a/spec/lib/gitlab/github_import/representation/pull_request_spec.rb b/spec/lib/gitlab/github_import/representation/pull_request_spec.rb
index b6dcd098c9c..370eac1d993 100644
--- a/spec/lib/gitlab/github_import/representation/pull_request_spec.rb
+++ b/spec/lib/gitlab/github_import/representation/pull_request_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Representation::PullRequest do
+RSpec.describe Gitlab::GithubImport::Representation::PullRequest do
let(:created_at) { Time.new(2017, 1, 1, 12, 00) }
let(:updated_at) { Time.new(2017, 1, 1, 12, 15) }
let(:merged_at) { Time.new(2017, 1, 1, 12, 17) }
diff --git a/spec/lib/gitlab/github_import/representation/to_hash_spec.rb b/spec/lib/gitlab/github_import/representation/to_hash_spec.rb
index 9c47349b376..2770e5c5397 100644
--- a/spec/lib/gitlab/github_import/representation/to_hash_spec.rb
+++ b/spec/lib/gitlab/github_import/representation/to_hash_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Representation::ToHash do
+RSpec.describe Gitlab::GithubImport::Representation::ToHash do
describe '#to_hash' do
let(:user) { double(:user, attributes: { login: 'alice' }) }
diff --git a/spec/lib/gitlab/github_import/representation/user_spec.rb b/spec/lib/gitlab/github_import/representation/user_spec.rb
index a7ad6bda3ad..14204886e9b 100644
--- a/spec/lib/gitlab/github_import/representation/user_spec.rb
+++ b/spec/lib/gitlab/github_import/representation/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Representation::User do
+RSpec.describe Gitlab::GithubImport::Representation::User do
shared_examples 'a User' do
it 'returns an instance of User' do
expect(user).to be_an_instance_of(described_class)
diff --git a/spec/lib/gitlab/github_import/representation_spec.rb b/spec/lib/gitlab/github_import/representation_spec.rb
index 76753a0ff21..58c10c4a775 100644
--- a/spec/lib/gitlab/github_import/representation_spec.rb
+++ b/spec/lib/gitlab/github_import/representation_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Representation do
+RSpec.describe Gitlab::GithubImport::Representation do
describe '.symbolize_hash' do
it 'returns a Hash with the keys as Symbols' do
hash = described_class.symbolize_hash('number' => 10)
diff --git a/spec/lib/gitlab/github_import/sequential_importer_spec.rb b/spec/lib/gitlab/github_import/sequential_importer_spec.rb
index 256155dea03..fe13fcd2568 100644
--- a/spec/lib/gitlab/github_import/sequential_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/sequential_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::SequentialImporter do
+RSpec.describe Gitlab::GithubImport::SequentialImporter do
describe '#execute' do
it 'imports a project in sequence' do
repository = double(:repository)
diff --git a/spec/lib/gitlab/github_import/user_finder_spec.rb b/spec/lib/gitlab/github_import/user_finder_spec.rb
index 8764ebef32b..0dd2bd4df45 100644
--- a/spec/lib/gitlab/github_import/user_finder_spec.rb
+++ b/spec/lib/gitlab/github_import/user_finder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
let(:project) { create(:project) }
let(:client) { double(:client) }
let(:finder) { described_class.new(project, client) }
diff --git a/spec/lib/gitlab/github_import_spec.rb b/spec/lib/gitlab/github_import_spec.rb
index 290d66243aa..1a690b81d2b 100644
--- a/spec/lib/gitlab/github_import_spec.rb
+++ b/spec/lib/gitlab/github_import_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport do
+RSpec.describe Gitlab::GithubImport do
let(:project) { double(:project) }
describe '.new_client_for' do
diff --git a/spec/lib/gitlab/gitlab_import/client_spec.rb b/spec/lib/gitlab/gitlab_import/client_spec.rb
index 6e4e88093bb..7f57d5fbf1b 100644
--- a/spec/lib/gitlab/gitlab_import/client_spec.rb
+++ b/spec/lib/gitlab/gitlab_import/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitlabImport::Client do
+RSpec.describe Gitlab::GitlabImport::Client do
include ImportSpecHelper
let(:token) { '123456' }
diff --git a/spec/lib/gitlab/gitlab_import/importer_spec.rb b/spec/lib/gitlab/gitlab_import/importer_spec.rb
index 2db1ddcfd0a..eb4c404e454 100644
--- a/spec/lib/gitlab/gitlab_import/importer_spec.rb
+++ b/spec/lib/gitlab/gitlab_import/importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitlabImport::Importer do
+RSpec.describe Gitlab::GitlabImport::Importer do
include ImportSpecHelper
describe '#execute' do
diff --git a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb
index c7ef978df37..44bcfb93c51 100644
--- a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GitlabImport::ProjectCreator do
+RSpec.describe Gitlab::GitlabImport::ProjectCreator do
let(:user) { create(:user) }
let(:repo) do
{
diff --git a/spec/lib/gitlab/gl_repository/identifier_spec.rb b/spec/lib/gitlab/gl_repository/identifier_spec.rb
index c36f296702e..e95aaaa6690 100644
--- a/spec/lib/gitlab/gl_repository/identifier_spec.rb
+++ b/spec/lib/gitlab/gl_repository/identifier_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GlRepository::Identifier do
+RSpec.describe Gitlab::GlRepository::Identifier do
let_it_be(:project) { create(:project) }
let_it_be(:personal_snippet) { create(:personal_snippet, author: project.owner) }
let_it_be(:project_snippet) { create(:project_snippet, project: project, author: project.owner) }
@@ -14,6 +14,21 @@ describe Gitlab::GlRepository::Identifier do
let(:expected_container) { project }
let(:expected_type) { Gitlab::GlRepository::PROJECT }
end
+
+ pending 'https://gitlab.com/gitlab-org/gitlab/-/issues/219192' do
+ it_behaves_like 'parsing gl_repository identifier' do
+ let(:record_id) { project.id }
+ let(:identifier) { "project-#{record_id}-code" }
+ let(:expected_container) { project }
+ let(:expected_type) { Gitlab::GlRepository::PROJECT }
+ end
+ end
+
+ it_behaves_like 'parsing gl_repository identifier' do
+ let(:identifier) { "project-1000000" }
+ let(:expected_container) { nil }
+ let(:expected_type) { Gitlab::GlRepository::PROJECT }
+ end
end
describe 'wiki' do
@@ -23,6 +38,13 @@ describe Gitlab::GlRepository::Identifier do
let(:expected_container) { project }
let(:expected_type) { Gitlab::GlRepository::WIKI }
end
+
+ it_behaves_like 'parsing gl_repository identifier' do
+ let(:record_id) { project.id }
+ let(:identifier) { "project-#{record_id}-wiki" }
+ let(:expected_container) { project }
+ let(:expected_type) { Gitlab::GlRepository::WIKI }
+ end
end
describe 'snippet' do
@@ -54,29 +76,30 @@ describe Gitlab::GlRepository::Identifier do
end
end
- describe 'incorrect format' do
- def expect_error_raised_for(identifier)
- expect { described_class.new(identifier) }.to raise_error(ArgumentError)
- end
-
- it 'raises error for incorrect id' do
- expect_error_raised_for('wiki-noid')
+ context 'when the format is incorrect' do
+ where(:identifier) do
+ [
+ 'wiki-noid',
+ 'foo-2',
+ 'project-0',
+ '2-project',
+ 'snippet-2-wiki',
+ 'project-wibble-wiki',
+ 'wiki-1-project',
+ 'snippet',
+ 'project-1-wiki-bar'
+ ]
end
- it 'raises error for incorrect type' do
- expect_error_raised_for('foo-2')
- end
-
- it 'raises error for incorrect three-segment container' do
- expect_error_raised_for('snippet-2-wiki')
- end
-
- it 'raises error for one segment' do
- expect_error_raised_for('snippet')
+ with_them do
+ it 'raises InvalidIdentifier' do
+ expect { described_class.parse(identifier) }.to raise_error(described_class::InvalidIdentifier)
+ end
end
- it 'raises error for more than three segments' do
- expect_error_raised_for('project-1-wiki-bar')
+ it 'raises InvalidIdentifier on project-1-project' do
+ pending 'https://gitlab.com/gitlab-org/gitlab/-/issues/219192'
+ expect { described_class.parse('project-1-project') }.to raise_error(described_class::InvalidIdentifier)
end
end
end
diff --git a/spec/lib/gitlab/gl_repository/repo_type_spec.rb b/spec/lib/gitlab/gl_repository/repo_type_spec.rb
index f5270104d2f..e920fc7cd3b 100644
--- a/spec/lib/gitlab/gl_repository/repo_type_spec.rb
+++ b/spec/lib/gitlab/gl_repository/repo_type_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::GlRepository::RepoType do
+RSpec.describe Gitlab::GlRepository::RepoType do
let_it_be(:project) { create(:project) }
let_it_be(:personal_snippet) { create(:personal_snippet, author: project.owner) }
let_it_be(:project_snippet) { create(:project_snippet, project: project, author: project.owner) }
diff --git a/spec/lib/gitlab/gl_repository_spec.rb b/spec/lib/gitlab/gl_repository_spec.rb
index 413540b4db8..f90103ee6f7 100644
--- a/spec/lib/gitlab/gl_repository_spec.rb
+++ b/spec/lib/gitlab/gl_repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::GlRepository do
+RSpec.describe ::Gitlab::GlRepository do
describe '.parse' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:snippet) { create(:personal_snippet) }
diff --git a/spec/lib/gitlab/global_id_spec.rb b/spec/lib/gitlab/global_id_spec.rb
index 719743ed5dc..3fc18858eee 100644
--- a/spec/lib/gitlab/global_id_spec.rb
+++ b/spec/lib/gitlab/global_id_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GlobalId do
+RSpec.describe Gitlab::GlobalId do
describe '.build' do
let_it_be(:object) { create(:issue) }
@@ -34,4 +34,37 @@ describe Gitlab::GlobalId do
expect { described_class.build }.to raise_error(URI::InvalidComponentError)
end
end
+
+ describe '.as_global_id' do
+ let(:project) { build_stubbed(:project) }
+
+ it 'is the identify function on GlobalID instances' do
+ gid = project.to_global_id
+
+ expect(described_class.as_global_id(gid)).to eq(gid)
+ end
+
+ it 'wraps URI::GID in GlobalID' do
+ uri = described_class.build(model_name: 'Foo', id: 1)
+
+ expect(described_class.as_global_id(uri)).to eq(GlobalID.new(uri))
+ end
+
+ it 'cannot coerce Integers without a model name' do
+ expect { described_class.as_global_id(1) }
+ .to raise_error(described_class::CoerceError, 'Cannot coerce Integer')
+ end
+
+ it 'can coerce Integers with a model name' do
+ uri = described_class.build(model_name: 'Foo', id: 1)
+
+ expect(described_class.as_global_id(1, model_name: 'Foo')).to eq(GlobalID.new(uri))
+ end
+
+ it 'rejects any other value' do
+ [:symbol, 'string', nil, [], {}, project].each do |value|
+ expect { described_class.as_global_id(value) }.to raise_error(described_class::CoerceError)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/gon_helper_spec.rb b/spec/lib/gitlab/gon_helper_spec.rb
index 7b0e0d01257..95db6b2b4e0 100644
--- a/spec/lib/gitlab/gon_helper_spec.rb
+++ b/spec/lib/gitlab/gon_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GonHelper do
+RSpec.describe Gitlab::GonHelper do
let(:helper) do
Class.new do
include Gitlab::GonHelper
diff --git a/spec/lib/gitlab/google_code_import/client_spec.rb b/spec/lib/gitlab/google_code_import/client_spec.rb
index fb1c7085017..402d2169432 100644
--- a/spec/lib/gitlab/google_code_import/client_spec.rb
+++ b/spec/lib/gitlab/google_code_import/client_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::GoogleCodeImport::Client do
+RSpec.describe Gitlab::GoogleCodeImport::Client do
let(:raw_data) { Gitlab::Json.parse(fixture_file("GoogleCodeProjectHosting.json")) }
subject { described_class.new(raw_data) }
diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb
index 3118671bb5e..f681e3c9f31 100644
--- a/spec/lib/gitlab/google_code_import/importer_spec.rb
+++ b/spec/lib/gitlab/google_code_import/importer_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::GoogleCodeImport::Importer do
+RSpec.describe Gitlab::GoogleCodeImport::Importer do
let(:mapped_user) { create(:user, username: "thilo123") }
let(:raw_data) { Gitlab::Json.parse(fixture_file("GoogleCodeProjectHosting.json")) }
let(:client) { Gitlab::GoogleCodeImport::Client.new(raw_data) }
diff --git a/spec/lib/gitlab/google_code_import/project_creator_spec.rb b/spec/lib/gitlab/google_code_import/project_creator_spec.rb
index 2353c24f77b..4be2e16c116 100644
--- a/spec/lib/gitlab/google_code_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/google_code_import/project_creator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GoogleCodeImport::ProjectCreator do
+RSpec.describe Gitlab::GoogleCodeImport::ProjectCreator do
let(:user) { create(:user) }
let(:repo) do
Gitlab::GoogleCodeImport::Repository.new(
diff --git a/spec/lib/gitlab/gpg/commit_spec.rb b/spec/lib/gitlab/gpg/commit_spec.rb
index ea0a6e1b967..55102554508 100644
--- a/spec/lib/gitlab/gpg/commit_spec.rb
+++ b/spec/lib/gitlab/gpg/commit_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Gpg::Commit do
+RSpec.describe Gitlab::Gpg::Commit do
describe '#signature' do
shared_examples 'returns the cached signature on second call' do
it 'returns the cached signature on second call' do
diff --git a/spec/lib/gitlab/gpg_spec.rb b/spec/lib/gitlab/gpg_spec.rb
index c7b9775f642..72c6c8efb5e 100644
--- a/spec/lib/gitlab/gpg_spec.rb
+++ b/spec/lib/gitlab/gpg_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Gpg do
+RSpec.describe Gitlab::Gpg do
describe '.fingerprints_from_key' do
before do
# make sure that each method is using the temporary keychain
@@ -244,7 +244,7 @@ describe Gitlab::Gpg do
end
end
-describe Gitlab::Gpg::CurrentKeyChain do
+RSpec.describe Gitlab::Gpg::CurrentKeyChain do
around do |example|
Gitlab::Gpg.using_tmp_keychain do
example.run
diff --git a/spec/lib/gitlab/grape_logging/formatters/lograge_with_timestamp_spec.rb b/spec/lib/gitlab/grape_logging/formatters/lograge_with_timestamp_spec.rb
index 84f23bb2ad9..e5d0adcfd5f 100644
--- a/spec/lib/gitlab/grape_logging/formatters/lograge_with_timestamp_spec.rb
+++ b/spec/lib/gitlab/grape_logging/formatters/lograge_with_timestamp_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp do
+RSpec.describe Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp do
let(:log_entry) do
{
status: 200,
diff --git a/spec/lib/gitlab/grape_logging/loggers/cloudflare_logger_spec.rb b/spec/lib/gitlab/grape_logging/loggers/cloudflare_logger_spec.rb
index 922a433d7ac..f683f98f019 100644
--- a/spec/lib/gitlab/grape_logging/loggers/cloudflare_logger_spec.rb
+++ b/spec/lib/gitlab/grape_logging/loggers/cloudflare_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GrapeLogging::Loggers::CloudflareLogger do
+RSpec.describe Gitlab::GrapeLogging::Loggers::CloudflareLogger do
subject { described_class.new }
describe "#parameters" do
diff --git a/spec/lib/gitlab/grape_logging/loggers/exception_logger_spec.rb b/spec/lib/gitlab/grape_logging/loggers/exception_logger_spec.rb
index cc9535d4d2c..bcb1f6c5af7 100644
--- a/spec/lib/gitlab/grape_logging/loggers/exception_logger_spec.rb
+++ b/spec/lib/gitlab/grape_logging/loggers/exception_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GrapeLogging::Loggers::ExceptionLogger do
+RSpec.describe Gitlab::GrapeLogging::Loggers::ExceptionLogger do
let(:mock_request) { OpenStruct.new(env: {}) }
let(:response_body) { nil }
diff --git a/spec/lib/gitlab/grape_logging/loggers/perf_logger_spec.rb b/spec/lib/gitlab/grape_logging/loggers/perf_logger_spec.rb
index 09ba4b89a1a..95e3af34174 100644
--- a/spec/lib/gitlab/grape_logging/loggers/perf_logger_spec.rb
+++ b/spec/lib/gitlab/grape_logging/loggers/perf_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GrapeLogging::Loggers::PerfLogger do
+RSpec.describe Gitlab::GrapeLogging::Loggers::PerfLogger do
subject { described_class.new }
describe ".parameters" do
diff --git a/spec/lib/gitlab/grape_logging/loggers/queue_duration_logger_spec.rb b/spec/lib/gitlab/grape_logging/loggers/queue_duration_logger_spec.rb
index 17c0659327d..e68c1446502 100644
--- a/spec/lib/gitlab/grape_logging/loggers/queue_duration_logger_spec.rb
+++ b/spec/lib/gitlab/grape_logging/loggers/queue_duration_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GrapeLogging::Loggers::QueueDurationLogger do
+RSpec.describe Gitlab::GrapeLogging::Loggers::QueueDurationLogger do
subject { described_class.new }
describe ".parameters" do
diff --git a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb
index c1dab5feb91..efe6c27c463 100644
--- a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb
+++ b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
# Also see spec/graphql/features/authorization_spec.rb for
# integration tests of AuthorizeFieldService
-describe Gitlab::Graphql::Authorize::AuthorizeFieldService do
+RSpec.describe Gitlab::Graphql::Authorize::AuthorizeFieldService do
def type(type_authorizations = [])
Class.new(Types::BaseObject) do
graphql_name 'TestType'
diff --git a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
index 67cb064b966..83873081a98 100644
--- a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
+++ b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Authorize::AuthorizeResource do
+RSpec.describe Gitlab::Graphql::Authorize::AuthorizeResource do
let(:fake_class) do
Class.new do
include Gitlab::Graphql::Authorize::AuthorizeResource
diff --git a/spec/lib/gitlab/graphql/calls_gitaly/instrumentation_spec.rb b/spec/lib/gitlab/graphql/calls_gitaly/instrumentation_spec.rb
index d93ce464a92..f16767f7d14 100644
--- a/spec/lib/gitlab/graphql/calls_gitaly/instrumentation_spec.rb
+++ b/spec/lib/gitlab/graphql/calls_gitaly/instrumentation_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::Graphql::CallsGitaly::Instrumentation do
+RSpec.describe Gitlab::Graphql::CallsGitaly::Instrumentation do
subject { described_class.new }
describe '#calls_gitaly_check' do
diff --git a/spec/lib/gitlab/graphql/copy_field_description_spec.rb b/spec/lib/gitlab/graphql/copy_field_description_spec.rb
index e7462c5b954..310b4046b56 100644
--- a/spec/lib/gitlab/graphql/copy_field_description_spec.rb
+++ b/spec/lib/gitlab/graphql/copy_field_description_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::CopyFieldDescription do
+RSpec.describe Gitlab::Graphql::CopyFieldDescription do
subject { Class.new.include(described_class) }
describe '.copy_field_description' do
diff --git a/spec/lib/gitlab/graphql/docs/renderer_spec.rb b/spec/lib/gitlab/graphql/docs/renderer_spec.rb
index 87079a1786c..81ef7fcda97 100644
--- a/spec/lib/gitlab/graphql/docs/renderer_spec.rb
+++ b/spec/lib/gitlab/graphql/docs/renderer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Docs::Renderer do
+RSpec.describe Gitlab::Graphql::Docs::Renderer do
describe '#contents' do
# Returns a Schema that uses the given `type`
def mock_schema(type)
diff --git a/spec/lib/gitlab/graphql/find_argument_in_parent_spec.rb b/spec/lib/gitlab/graphql/find_argument_in_parent_spec.rb
index 91e90315b3e..68b24a60a99 100644
--- a/spec/lib/gitlab/graphql/find_argument_in_parent_spec.rb
+++ b/spec/lib/gitlab/graphql/find_argument_in_parent_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::FindArgumentInParent do
+RSpec.describe Gitlab::Graphql::FindArgumentInParent do
describe '#find' do
def build_node(parent = nil, args: {})
props = { irep_node: double(arguments: args) }
diff --git a/spec/lib/gitlab/graphql/generic_tracing_spec.rb b/spec/lib/gitlab/graphql/generic_tracing_spec.rb
index ae92dcc40af..cd116225ecd 100644
--- a/spec/lib/gitlab/graphql/generic_tracing_spec.rb
+++ b/spec/lib/gitlab/graphql/generic_tracing_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::GenericTracing do
+RSpec.describe Gitlab::Graphql::GenericTracing do
let(:graphql_duration_seconds_histogram) { double('Gitlab::Metrics::NullMetric') }
it 'updates graphql histogram with expected labels' do
diff --git a/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb
index b3d57c899d5..ae5d9686c54 100644
--- a/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb
+++ b/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Loaders::BatchLfsOidLoader do
+RSpec.describe Gitlab::Graphql::Loaders::BatchLfsOidLoader do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb
index 79f9ecb39cf..cf1f00bc176 100644
--- a/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb
+++ b/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Loaders::BatchModelLoader do
+RSpec.describe Gitlab::Graphql::Loaders::BatchModelLoader do
describe '#find' do
let(:issue) { create(:issue) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/graphql/loaders/batch_project_statistics_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_project_statistics_loader_spec.rb
index ec2fcad31e5..7ae28fdcf0f 100644
--- a/spec/lib/gitlab/graphql/loaders/batch_project_statistics_loader_spec.rb
+++ b/spec/lib/gitlab/graphql/loaders/batch_project_statistics_loader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Loaders::BatchProjectStatisticsLoader do
+RSpec.describe Gitlab::Graphql::Loaders::BatchProjectStatisticsLoader do
describe '#find' do
it 'only queries once for project statistics' do
stats = create_list(:project_statistics, 2)
diff --git a/spec/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader_spec.rb
index 38931f7ab5e..f73eace274d 100644
--- a/spec/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader_spec.rb
+++ b/spec/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Loaders::BatchRootStorageStatisticsLoader do
+RSpec.describe Gitlab::Graphql::Loaders::BatchRootStorageStatisticsLoader do
describe '#find' do
it 'only queries once for project statistics' do
stats = create_list(:namespace_root_storage_statistics, 2)
diff --git a/spec/lib/gitlab/graphql/loaders/issuable_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/issuable_loader_spec.rb
new file mode 100644
index 00000000000..180966de895
--- /dev/null
+++ b/spec/lib/gitlab/graphql/loaders/issuable_loader_spec.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Graphql::Loaders::IssuableLoader do
+ subject { described_class.new(parent, finder) }
+
+ let(:params) { HashWithIndifferentAccess.new }
+
+ describe '#find_all' do
+ let(:finder) { double(:finder, params: params, execute: %i[x y z]) }
+
+ where(:factory, :param_name) do
+ %i[project group].map { |thing| [thing, :"#{thing}_id"] }
+ end
+
+ with_them do
+ let(:parent) { build_stubbed(factory) }
+
+ it 'assignes the parent parameter, and batching_find_alls the finder' do
+ expect(subject.find_all).to contain_exactly(:x, :y, :z)
+ expect(params).to include(param_name => parent)
+ end
+ end
+
+ context 'the parent is of an unexpected type' do
+ let(:parent) { build(:merge_request) }
+
+ it 'raises an error if we pass an unexpected parent' do
+ expect { subject.find_all }.to raise_error(/Unexpected parent/)
+ end
+ end
+ end
+
+ describe '#batching_find_all' do
+ context 'the finder params are anything other than [iids]' do
+ let(:finder) { double(:finder, params: params, execute: [:foo]) }
+ let(:parent) { build_stubbed(:project) }
+
+ it 'batching_find_alls the finder, setting the correct parent parameter' do
+ expect(subject.batching_find_all).to eq([:foo])
+ expect(params[:project_id]).to eq(parent)
+ end
+
+ it 'allows a post-process block' do
+ expect(subject.batching_find_all(&:first)).to eq(:foo)
+ end
+ end
+
+ context 'the finder params are exactly [iids]' do
+ # Dumb finder class, that only implements what we need, and has
+ # predictable query counts.
+ let(:finder_class) do
+ Class.new do
+ attr_reader :current_user, :params
+
+ def initialize(user, args)
+ @current_user = user
+ @params = HashWithIndifferentAccess.new(args.to_h)
+ end
+
+ def execute
+ params[:project_id].issues.where(iid: params[:iids])
+ end
+ end
+ end
+
+ it 'batches requests' do
+ issue_a = create(:issue)
+ issue_b = create(:issue)
+ issue_c = create(:issue, project: issue_a.project)
+ proj_1 = issue_a.project
+ proj_2 = issue_b.project
+ user = create(:user, developer_projects: [proj_1, proj_2])
+
+ finder_a = finder_class.new(user, iids: [issue_a.iid])
+ finder_b = finder_class.new(user, iids: [issue_b.iid])
+ finder_c = finder_class.new(user, iids: [issue_c.iid])
+
+ results = []
+
+ expect do
+ results.concat(described_class.new(proj_1, finder_a).batching_find_all)
+ results.concat(described_class.new(proj_2, finder_b).batching_find_all)
+ results.concat(described_class.new(proj_1, finder_c).batching_find_all)
+ end.not_to exceed_query_limit(0)
+
+ expect do
+ results = results.map(&:sync)
+ end.not_to exceed_query_limit(2)
+
+ expect(results).to contain_exactly(issue_a, issue_b, issue_c)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/markdown_field/resolver_spec.rb b/spec/lib/gitlab/graphql/markdown_field/resolver_spec.rb
index b95bcdef188..af604e1c7d5 100644
--- a/spec/lib/gitlab/graphql/markdown_field/resolver_spec.rb
+++ b/spec/lib/gitlab/graphql/markdown_field/resolver_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::Graphql::MarkdownField::Resolver do
+RSpec.describe Gitlab::Graphql::MarkdownField::Resolver do
include Gitlab::Routing
let(:resolver) { described_class.new(:note) }
diff --git a/spec/lib/gitlab/graphql/markdown_field_spec.rb b/spec/lib/gitlab/graphql/markdown_field_spec.rb
index 866a20801d3..e3da925376e 100644
--- a/spec/lib/gitlab/graphql/markdown_field_spec.rb
+++ b/spec/lib/gitlab/graphql/markdown_field_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::Graphql::MarkdownField do
+RSpec.describe Gitlab::Graphql::MarkdownField do
describe '.markdown_field' do
it 'creates the field with some default attributes' do
field = class_with_markdown_field(:test_html, null: true, method: :hello).fields['testHtml']
diff --git a/spec/lib/gitlab/graphql/mount_mutation_spec.rb b/spec/lib/gitlab/graphql/mount_mutation_spec.rb
new file mode 100644
index 00000000000..d6b932e08d2
--- /dev/null
+++ b/spec/lib/gitlab/graphql/mount_mutation_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Gitlab::Graphql::MountMutation do
+ let_it_be(:mutation) do
+ Class.new(Mutations::BaseMutation) do
+ graphql_name 'TestMutation'
+
+ argument :foo, GraphQL::STRING_TYPE, required: false
+ field :bar, GraphQL::STRING_TYPE, null: true
+ end
+ end
+
+ describe '.mount_mutation' do
+ subject(:field) do
+ mutation_type = mutation_type_factory do |f|
+ f.mount_mutation(mutation)
+ end
+
+ mutation_type.get_field('testMutation').to_graphql
+ end
+
+ it 'mounts a mutation' do
+ expect(field.mutation).to be_present
+ end
+ end
+
+ describe '.mount_aliased_mutation' do
+ subject(:field) do
+ mutation_type = mutation_type_factory do |f|
+ f.mount_aliased_mutation('MyAlias', mutation)
+ end
+
+ mutation_type.get_field('myAlias').to_graphql
+ end
+
+ it 'mounts a mutation' do
+ expect(field.mutation).to be_present
+ end
+
+ it 'has a correct `graphql_name`' do
+ expect(field.mutation.graphql_name).to eq('MyAlias')
+ end
+
+ it 'has a correct type' do
+ expect(field.type.name).to eq('MyAliasPayload')
+ end
+
+ it 'has a correct input argument' do
+ expect(field.arguments['input'].type.unwrap.name).to eq('MyAliasInput')
+ end
+ end
+
+ def mutation_type_factory
+ Class.new(GraphQL::Schema::Object) do
+ include Gitlab::Graphql::MountMutation
+
+ graphql_name 'MutationType'
+
+ yield(self) if block_given?
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb b/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb
index 11cf14523c2..932bcd8cd92 100644
--- a/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Pagination::ExternallyPaginatedArrayConnection do
+RSpec.describe Gitlab::Graphql::Pagination::ExternallyPaginatedArrayConnection do
let(:prev_cursor) { 1 }
let(:next_cursor) { 6 }
let(:values) { [2, 3, 4, 5] }
diff --git a/spec/lib/gitlab/graphql/pagination/keyset/conditions/not_null_condition_spec.rb b/spec/lib/gitlab/graphql/pagination/keyset/conditions/not_null_condition_spec.rb
index 33dc6bdea97..eecdaa3409f 100644
--- a/spec/lib/gitlab/graphql/pagination/keyset/conditions/not_null_condition_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/keyset/conditions/not_null_condition_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Pagination::Keyset::Conditions::NotNullCondition do
+RSpec.describe Gitlab::Graphql::Pagination::Keyset::Conditions::NotNullCondition do
describe '#build' do
let(:operators) { ['>', '>'] }
let(:before_or_after) { :after }
diff --git a/spec/lib/gitlab/graphql/pagination/keyset/conditions/null_condition_spec.rb b/spec/lib/gitlab/graphql/pagination/keyset/conditions/null_condition_spec.rb
index 1c74f2fb0ab..582f96299ec 100644
--- a/spec/lib/gitlab/graphql/pagination/keyset/conditions/null_condition_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/keyset/conditions/null_condition_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Pagination::Keyset::Conditions::NullCondition do
+RSpec.describe Gitlab::Graphql::Pagination::Keyset::Conditions::NullCondition do
describe '#build' do
let(:values) { [nil, 500] }
let(:operators) { [nil, '>'] }
diff --git a/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb b/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb
index ed728444b17..65698caac34 100644
--- a/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Pagination::Keyset::Connection do
+RSpec.describe Gitlab::Graphql::Pagination::Keyset::Connection do
let(:nodes) { Project.all.order(id: :asc) }
let(:arguments) { {} }
let(:query_type) { GraphQL::ObjectType.new }
diff --git a/spec/lib/gitlab/graphql/pagination/keyset/order_info_spec.rb b/spec/lib/gitlab/graphql/pagination/keyset/order_info_spec.rb
index cb5656d7c00..9f310f30253 100644
--- a/spec/lib/gitlab/graphql/pagination/keyset/order_info_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/keyset/order_info_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Pagination::Keyset::OrderInfo do
+RSpec.describe Gitlab::Graphql::Pagination::Keyset::OrderInfo do
describe '#build_order_list' do
let(:order_list) { described_class.build_order_list(relation) }
diff --git a/spec/lib/gitlab/graphql/pagination/keyset/query_builder_spec.rb b/spec/lib/gitlab/graphql/pagination/keyset/query_builder_spec.rb
index 7fd5b889cf9..31c02fd43e8 100644
--- a/spec/lib/gitlab/graphql/pagination/keyset/query_builder_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/keyset/query_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Pagination::Keyset::QueryBuilder do
+RSpec.describe Gitlab::Graphql::Pagination::Keyset::QueryBuilder do
context 'when number of ordering fields is 0' do
it 'raises an error' do
expect { described_class.new(Issue.arel_table, [], {}, :after) }
diff --git a/spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb b/spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb
index 931b1e708de..86f35de94ed 100644
--- a/spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection do
+RSpec.describe Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection do
it 'subclasses from GraphQL::Relay::RelationConnection' do
expect(described_class.superclass).to eq GraphQL::Pagination::ActiveRecordRelationConnection
end
diff --git a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb
index 66033736e01..89d2ab8bb87 100644
--- a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb
+++ b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do
+RSpec.describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do
subject { described_class.new }
describe '#analyze?' do
@@ -17,9 +17,27 @@ describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do
end
context 'feature flag enabled by default' do
+ let(:monotonic_time_before) { 42 }
+ let(:monotonic_time_after) { 500 }
+ let(:monotonic_time_duration) { monotonic_time_after - monotonic_time_before }
+
it 'enables the analyzer' do
expect(subject.analyze?(anything)).to be_truthy
end
+
+ it 'returns a duration in seconds' do
+ allow(GraphQL::Analysis).to receive(:analyze_query).and_return([4, 2])
+ allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(monotonic_time_before, monotonic_time_after)
+ allow(Gitlab::GraphqlLogger).to receive(:info)
+
+ expected_duration = monotonic_time_duration
+ memo = subject.initial_value(spy('query'))
+
+ subject.final_value(memo)
+
+ expect(memo).to have_key(:duration_s)
+ expect(memo[:duration_s]).to eq(expected_duration)
+ end
end
end
end
diff --git a/spec/lib/gitlab/graphql/representation/submodule_tree_entry_spec.rb b/spec/lib/gitlab/graphql/representation/submodule_tree_entry_spec.rb
index ed092a846ae..eb56f1f9671 100644
--- a/spec/lib/gitlab/graphql/representation/submodule_tree_entry_spec.rb
+++ b/spec/lib/gitlab/graphql/representation/submodule_tree_entry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Representation::SubmoduleTreeEntry do
+RSpec.describe Gitlab::Graphql::Representation::SubmoduleTreeEntry do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
diff --git a/spec/lib/gitlab/graphql/representation/tree_entry_spec.rb b/spec/lib/gitlab/graphql/representation/tree_entry_spec.rb
index d45e690160c..f4eb7b37774 100644
--- a/spec/lib/gitlab/graphql/representation/tree_entry_spec.rb
+++ b/spec/lib/gitlab/graphql/representation/tree_entry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Representation::TreeEntry do
+RSpec.describe Gitlab::Graphql::Representation::TreeEntry do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
diff --git a/spec/lib/gitlab/graphql/timeout_spec.rb b/spec/lib/gitlab/graphql/timeout_spec.rb
index 8e04586d0ec..3669a89ba7c 100644
--- a/spec/lib/gitlab/graphql/timeout_spec.rb
+++ b/spec/lib/gitlab/graphql/timeout_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphql::Timeout do
+RSpec.describe Gitlab::Graphql::Timeout do
it 'inherits from ' do
expect(described_class.superclass).to eq GraphQL::Schema::Timeout
end
diff --git a/spec/lib/gitlab/graphql_logger_spec.rb b/spec/lib/gitlab/graphql_logger_spec.rb
index 12cb56c78c1..c92666e0663 100644
--- a/spec/lib/gitlab/graphql_logger_spec.rb
+++ b/spec/lib/gitlab/graphql_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GraphqlLogger do
+RSpec.describe Gitlab::GraphqlLogger do
subject { described_class.new('/dev/null') }
let(:now) { Time.now }
diff --git a/spec/lib/gitlab/graphs/commits_spec.rb b/spec/lib/gitlab/graphs/commits_spec.rb
index f92c7fb11a1..79cec2d8705 100644
--- a/spec/lib/gitlab/graphs/commits_spec.rb
+++ b/spec/lib/gitlab/graphs/commits_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Graphs::Commits do
+RSpec.describe Gitlab::Graphs::Commits do
let!(:project) { create(:project, :public) }
let!(:commit1) { create(:commit, git_commit: RepoHelpers.sample_commit, project: project, committed_date: Time.now) }
diff --git a/spec/lib/gitlab/group_search_results_spec.rb b/spec/lib/gitlab/group_search_results_spec.rb
index 746f505c877..b6a3c8b5e76 100644
--- a/spec/lib/gitlab/group_search_results_spec.rb
+++ b/spec/lib/gitlab/group_search_results_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GroupSearchResults do
+RSpec.describe Gitlab::GroupSearchResults do
let(:user) { create(:user) }
describe 'user search' do
diff --git a/spec/lib/gitlab/hashed_storage/migrator_spec.rb b/spec/lib/gitlab/hashed_storage/migrator_spec.rb
index c59b152a982..0549b3128c7 100644
--- a/spec/lib/gitlab/hashed_storage/migrator_spec.rb
+++ b/spec/lib/gitlab/hashed_storage/migrator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HashedStorage::Migrator, :redis do
+RSpec.describe Gitlab::HashedStorage::Migrator, :redis do
describe '#bulk_schedule_migration' do
it 'schedules job to HashedStorage::MigratorWorker' do
Sidekiq::Testing.fake! do
diff --git a/spec/lib/gitlab/health_checks/db_check_spec.rb b/spec/lib/gitlab/health_checks/db_check_spec.rb
index 3c1c1e3818d..60ebc596a0f 100644
--- a/spec/lib/gitlab/health_checks/db_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/db_check_spec.rb
@@ -3,6 +3,6 @@
require 'spec_helper'
require_relative './simple_check_shared'
-describe Gitlab::HealthChecks::DbCheck do
+RSpec.describe Gitlab::HealthChecks::DbCheck do
include_examples 'simple_check', 'db_ping', 'Db', '1'
end
diff --git a/spec/lib/gitlab/health_checks/gitaly_check_spec.rb b/spec/lib/gitlab/health_checks/gitaly_check_spec.rb
index d4ce16ce6fc..7c346e3eb69 100644
--- a/spec/lib/gitlab/health_checks/gitaly_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/gitaly_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HealthChecks::GitalyCheck do
+RSpec.describe Gitlab::HealthChecks::GitalyCheck do
let(:result_class) { Gitlab::HealthChecks::Result }
let(:repository_storages) { ['default'] }
diff --git a/spec/lib/gitlab/health_checks/master_check_spec.rb b/spec/lib/gitlab/health_checks/master_check_spec.rb
index dcfc733d5ad..1c1efe178e2 100644
--- a/spec/lib/gitlab/health_checks/master_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/master_check_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require_relative './simple_check_shared'
-describe Gitlab::HealthChecks::MasterCheck do
+RSpec.describe Gitlab::HealthChecks::MasterCheck do
let(:result_class) { Gitlab::HealthChecks::Result }
before do
diff --git a/spec/lib/gitlab/health_checks/probes/collection_spec.rb b/spec/lib/gitlab/health_checks/probes/collection_spec.rb
index d8c411fa27b..03138e936aa 100644
--- a/spec/lib/gitlab/health_checks/probes/collection_spec.rb
+++ b/spec/lib/gitlab/health_checks/probes/collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HealthChecks::Probes::Collection do
+RSpec.describe Gitlab::HealthChecks::Probes::Collection do
let(:readiness) { described_class.new(*checks) }
describe '#execute' do
@@ -47,6 +47,20 @@ describe Gitlab::HealthChecks::Probes::Collection do
status: 'failed', message: 'check error')
end
end
+
+ context 'when check raises exception not handled inside the check' do
+ before do
+ expect(Gitlab::HealthChecks::Redis::RedisCheck).to receive(:readiness).and_raise(
+ ::Redis::CannotConnectError, 'Redis down')
+ end
+
+ it 'responds with failure including the exception info' do
+ expect(subject.http_status).to eq(500)
+
+ expect(subject.json[:status]).to eq('failed')
+ expect(subject.json[:message]).to eq('Redis::CannotConnectError : Redis down')
+ end
+ end
end
context 'without checks' do
diff --git a/spec/lib/gitlab/health_checks/puma_check_spec.rb b/spec/lib/gitlab/health_checks/puma_check_spec.rb
index 93ef81978a8..0bd0965518f 100644
--- a/spec/lib/gitlab/health_checks/puma_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/puma_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HealthChecks::PumaCheck do
+RSpec.describe Gitlab::HealthChecks::PumaCheck do
let(:result_class) { Gitlab::HealthChecks::Result }
let(:readiness) { described_class.readiness }
let(:metrics) { described_class.metrics }
diff --git a/spec/lib/gitlab/health_checks/redis/cache_check_spec.rb b/spec/lib/gitlab/health_checks/redis/cache_check_spec.rb
index aaf474d7eeb..c44bd2ed585 100644
--- a/spec/lib/gitlab/health_checks/redis/cache_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/redis/cache_check_spec.rb
@@ -3,6 +3,6 @@
require 'spec_helper'
require_relative '../simple_check_shared'
-describe Gitlab::HealthChecks::Redis::CacheCheck do
+RSpec.describe Gitlab::HealthChecks::Redis::CacheCheck do
include_examples 'simple_check', 'redis_cache_ping', 'RedisCache', 'PONG'
end
diff --git a/spec/lib/gitlab/health_checks/redis/queues_check_spec.rb b/spec/lib/gitlab/health_checks/redis/queues_check_spec.rb
index f4b5e18da2a..3882e7db9d9 100644
--- a/spec/lib/gitlab/health_checks/redis/queues_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/redis/queues_check_spec.rb
@@ -3,6 +3,6 @@
require 'spec_helper'
require_relative '../simple_check_shared'
-describe Gitlab::HealthChecks::Redis::QueuesCheck do
+RSpec.describe Gitlab::HealthChecks::Redis::QueuesCheck do
include_examples 'simple_check', 'redis_queues_ping', 'RedisQueues', 'PONG'
end
diff --git a/spec/lib/gitlab/health_checks/redis/redis_check_spec.rb b/spec/lib/gitlab/health_checks/redis/redis_check_spec.rb
index ae7ee0d0859..43e890a6c4f 100644
--- a/spec/lib/gitlab/health_checks/redis/redis_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/redis/redis_check_spec.rb
@@ -3,6 +3,6 @@
require 'spec_helper'
require_relative '../simple_check_shared'
-describe Gitlab::HealthChecks::Redis::RedisCheck do
+RSpec.describe Gitlab::HealthChecks::Redis::RedisCheck do
include_examples 'simple_check', 'redis_ping', 'Redis', 'PONG'
end
diff --git a/spec/lib/gitlab/health_checks/redis/shared_state_check_spec.rb b/spec/lib/gitlab/health_checks/redis/shared_state_check_spec.rb
index 3e92b072254..25917741a1c 100644
--- a/spec/lib/gitlab/health_checks/redis/shared_state_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/redis/shared_state_check_spec.rb
@@ -3,6 +3,6 @@
require 'spec_helper'
require_relative '../simple_check_shared'
-describe Gitlab::HealthChecks::Redis::SharedStateCheck do
+RSpec.describe Gitlab::HealthChecks::Redis::SharedStateCheck do
include_examples 'simple_check', 'redis_shared_state_ping', 'RedisSharedState', 'PONG'
end
diff --git a/spec/lib/gitlab/health_checks/simple_check_shared.rb b/spec/lib/gitlab/health_checks/simple_check_shared.rb
index 3d0f9b3cf7a..591a11d5ab6 100644
--- a/spec/lib/gitlab/health_checks/simple_check_shared.rb
+++ b/spec/lib/gitlab/health_checks/simple_check_shared.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'simple_check' do |metrics_prefix, check_name, success_result|
+RSpec.shared_context 'simple_check' do |metrics_prefix, check_name, success_result|
describe '#metrics' do
subject { described_class.metrics }
diff --git a/spec/lib/gitlab/health_checks/unicorn_check_spec.rb b/spec/lib/gitlab/health_checks/unicorn_check_spec.rb
index 7c57b6f1ca5..1cc44016002 100644
--- a/spec/lib/gitlab/health_checks/unicorn_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/unicorn_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HealthChecks::UnicornCheck do
+RSpec.describe Gitlab::HealthChecks::UnicornCheck do
let(:result_class) { Gitlab::HealthChecks::Result }
let(:readiness) { described_class.readiness }
let(:metrics) { described_class.metrics }
diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
index 2140cbae488..9271b868e36 100644
--- a/spec/lib/gitlab/highlight_spec.rb
+++ b/spec/lib/gitlab/highlight_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Highlight do
+RSpec.describe Gitlab::Highlight do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/hook_data/base_builder_spec.rb b/spec/lib/gitlab/hook_data/base_builder_spec.rb
index 4c3fd854c09..5d9d9fbffe2 100644
--- a/spec/lib/gitlab/hook_data/base_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/base_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HookData::BaseBuilder do
+RSpec.describe Gitlab::HookData::BaseBuilder do
describe '#absolute_image_urls' do
let(:subclass) do
Class.new(described_class) do
diff --git a/spec/lib/gitlab/hook_data/issuable_builder_spec.rb b/spec/lib/gitlab/hook_data/issuable_builder_spec.rb
index afbc48e9ca2..50f3a4776be 100644
--- a/spec/lib/gitlab/hook_data/issuable_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/issuable_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HookData::IssuableBuilder do
+RSpec.describe Gitlab::HookData::IssuableBuilder do
let_it_be(:user) { create(:user) }
# This shared example requires a `builder` and `user` variable
diff --git a/spec/lib/gitlab/hook_data/issue_builder_spec.rb b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
index 1f681faa8f7..8a2395d70b2 100644
--- a/spec/lib/gitlab/hook_data/issue_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HookData::IssueBuilder do
+RSpec.describe Gitlab::HookData::IssueBuilder do
let_it_be(:label) { create(:label) }
let_it_be(:issue) { create(:labeled_issue, labels: [label], project: label.project) }
let(:builder) { described_class.new(issue) }
diff --git a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
index 67fa0a7426a..fede7f273f1 100644
--- a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HookData::MergeRequestBuilder do
+RSpec.describe Gitlab::HookData::MergeRequestBuilder do
let_it_be(:merge_request) { create(:merge_request) }
let(:builder) { described_class.new(merge_request) }
diff --git a/spec/lib/gitlab/http_connection_adapter_spec.rb b/spec/lib/gitlab/http_connection_adapter_spec.rb
index 1532fd1103e..389bc1a85f4 100644
--- a/spec/lib/gitlab/http_connection_adapter_spec.rb
+++ b/spec/lib/gitlab/http_connection_adapter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HTTPConnectionAdapter do
+RSpec.describe Gitlab::HTTPConnectionAdapter do
include StubRequests
describe '#connection' do
diff --git a/spec/lib/gitlab/http_io_spec.rb b/spec/lib/gitlab/http_io_spec.rb
index e53c361e169..5ba0cb5e686 100644
--- a/spec/lib/gitlab/http_io_spec.rb
+++ b/spec/lib/gitlab/http_io_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HttpIO do
+RSpec.describe Gitlab::HttpIO do
include HttpIOHelpers
let(:http_io) { described_class.new(url, size) }
diff --git a/spec/lib/gitlab/http_spec.rb b/spec/lib/gitlab/http_spec.rb
index 85cfc8e2852..09da94e7559 100644
--- a/spec/lib/gitlab/http_spec.rb
+++ b/spec/lib/gitlab/http_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::HTTP do
+RSpec.describe Gitlab::HTTP do
include StubRequests
context 'when allow_local_requests' do
diff --git a/spec/lib/gitlab/i18n/metadata_entry_spec.rb b/spec/lib/gitlab/i18n/metadata_entry_spec.rb
index 2d8bb538681..2f8816e62cc 100644
--- a/spec/lib/gitlab/i18n/metadata_entry_spec.rb
+++ b/spec/lib/gitlab/i18n/metadata_entry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::I18n::MetadataEntry do
+RSpec.describe Gitlab::I18n::MetadataEntry do
describe '#expected_forms' do
it 'returns the number of plurals' do
data = {
diff --git a/spec/lib/gitlab/i18n/po_linter_spec.rb b/spec/lib/gitlab/i18n/po_linter_spec.rb
index 2ab363ee45c..5dfc9d3613c 100644
--- a/spec/lib/gitlab/i18n/po_linter_spec.rb
+++ b/spec/lib/gitlab/i18n/po_linter_spec.rb
@@ -5,7 +5,7 @@ require 'simple_po_parser'
# Disabling this cop to allow for multi-language examples in comments
# rubocop:disable Style/AsciiComments
-describe Gitlab::I18n::PoLinter do
+RSpec.describe Gitlab::I18n::PoLinter do
let(:linter) { described_class.new(po_path) }
let(:po_path) { 'spec/fixtures/valid.po' }
diff --git a/spec/lib/gitlab/i18n/translation_entry_spec.rb b/spec/lib/gitlab/i18n/translation_entry_spec.rb
index 880da38052e..76879f75bec 100644
--- a/spec/lib/gitlab/i18n/translation_entry_spec.rb
+++ b/spec/lib/gitlab/i18n/translation_entry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::I18n::TranslationEntry do
+RSpec.describe Gitlab::I18n::TranslationEntry do
describe '#singular_translation' do
it 'returns the normal `msgstr` for translations without plural' do
data = { msgid: 'Hello world', msgstr: 'Bonjour monde' }
diff --git a/spec/lib/gitlab/i18n_spec.rb b/spec/lib/gitlab/i18n_spec.rb
index 2664423af88..592adadc362 100644
--- a/spec/lib/gitlab/i18n_spec.rb
+++ b/spec/lib/gitlab/i18n_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::I18n do
+RSpec.describe Gitlab::I18n do
let(:user) { create(:user, preferred_language: 'es') }
describe '.locale=' do
diff --git a/spec/lib/gitlab/identifier_spec.rb b/spec/lib/gitlab/identifier_spec.rb
index 9c7972d4bde..cc8d68e5c18 100644
--- a/spec/lib/gitlab/identifier_spec.rb
+++ b/spec/lib/gitlab/identifier_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Identifier do
+RSpec.describe Gitlab::Identifier do
let(:identifier) do
Class.new { include Gitlab::Identifier }.new
end
diff --git a/spec/lib/gitlab/import/database_helpers_spec.rb b/spec/lib/gitlab/import/database_helpers_spec.rb
index 3ac34455177..d56e05df5d7 100644
--- a/spec/lib/gitlab/import/database_helpers_spec.rb
+++ b/spec/lib/gitlab/import/database_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Import::DatabaseHelpers do
+RSpec.describe Gitlab::Import::DatabaseHelpers do
let(:database_helper) do
Class.new do
include Gitlab::Import::DatabaseHelpers
diff --git a/spec/lib/gitlab/import/merge_request_creator_spec.rb b/spec/lib/gitlab/import/merge_request_creator_spec.rb
index 48a57f9b251..9aedca40f1b 100644
--- a/spec/lib/gitlab/import/merge_request_creator_spec.rb
+++ b/spec/lib/gitlab/import/merge_request_creator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Import::MergeRequestCreator do
+RSpec.describe Gitlab::Import::MergeRequestCreator do
let(:project) { create(:project, :repository) }
subject { described_class.new(project) }
diff --git a/spec/lib/gitlab/import/merge_request_helpers_spec.rb b/spec/lib/gitlab/import/merge_request_helpers_spec.rb
index d81251c4a43..f858ab934bb 100644
--- a/spec/lib/gitlab/import/merge_request_helpers_spec.rb
+++ b/spec/lib/gitlab/import/merge_request_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Import::MergeRequestHelpers, type: :helper do
+RSpec.describe Gitlab::Import::MergeRequestHelpers, type: :helper do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/import/metrics_spec.rb b/spec/lib/gitlab/import/metrics_spec.rb
index 0799d19fcef..0a912427014 100644
--- a/spec/lib/gitlab/import/metrics_spec.rb
+++ b/spec/lib/gitlab/import/metrics_spec.rb
@@ -2,55 +2,39 @@
require 'spec_helper'
-describe Gitlab::Import::Metrics do
- let(:importer_stub) do
- Class.new do
- prepend Gitlab::Import::Metrics
-
- Gitlab::Import::Metrics.measure :execute, metrics: {
- importer_counter: {
- type: :counter,
- description: 'description'
- },
- importer_histogram: {
- type: :histogram,
- labels: { importer: 'importer' },
- description: 'description'
- }
- }
-
- def execute
- true
- end
+RSpec.describe Gitlab::Import::Metrics do
+ let(:importer) { :test_importer }
+ let(:project) { create(:project) }
+ let(:histogram) { double(:histogram) }
+ let(:counter) { double(:counter) }
+
+ subject { described_class.new(importer, project) }
+
+ describe '#report_import_time' do
+ before do
+ allow(Gitlab::Metrics).to receive(:counter) { counter }
+ allow(Gitlab::Metrics).to receive(:histogram) { histogram }
+ allow(counter).to receive(:increment)
+ allow(counter).to receive(:observe)
end
- end
-
- subject { importer_stub.new.execute }
- describe '#execute' do
- let(:counter) { double(:counter) }
- let(:histogram) { double(:histogram) }
+ it 'emits importer metrics' do
+ expect(Gitlab::Metrics).to receive(:counter).with(
+ :test_importer_imported_projects_total,
+ 'The number of imported projects'
+ )
- it 'increments counter metric' do
- expect(Gitlab::Metrics)
- .to receive(:counter)
- .with(:importer_counter, 'description')
- .and_return(counter)
+ expect(Gitlab::Metrics).to receive(:histogram).with(
+ :test_importer_total_duration_seconds,
+ 'Total time spent importing projects, in seconds',
+ {},
+ described_class::IMPORT_DURATION_BUCKETS
+ )
expect(counter).to receive(:increment)
+ expect(histogram).to receive(:observe).with({ importer: :test_importer }, anything)
- subject
- end
-
- it 'measures method duration and reports histogram metric' do
- expect(Gitlab::Metrics)
- .to receive(:histogram)
- .with(:importer_histogram, 'description')
- .and_return(histogram)
-
- expect(histogram).to receive(:observe).with({ importer: 'importer' }, anything)
-
- subject
+ subject.track_finished_import
end
end
end
diff --git a/spec/lib/gitlab/import/set_async_jid_spec.rb b/spec/lib/gitlab/import/set_async_jid_spec.rb
index d2933cfd371..6931a7a953d 100644
--- a/spec/lib/gitlab/import/set_async_jid_spec.rb
+++ b/spec/lib/gitlab/import/set_async_jid_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::Import::SetAsyncJid do
+RSpec.describe Gitlab::Import::SetAsyncJid do
describe '.set_jid', :clean_gitlab_redis_shared_state do
let(:project) { create(:project, :import_scheduled) }
diff --git a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb
index 1631de393b5..fc794f11499 100644
--- a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb
+++ b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy do
+RSpec.describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy do
before do
allow_next_instance_of(ProjectExportWorker) do |job|
allow(job).to receive(:jid).and_return(SecureRandom.hex(8))
diff --git a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
index 7792daed99c..38f1d48798b 100644
--- a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
+++ b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
+RSpec.describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
include StubRequests
before do
diff --git a/spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb b/spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb
index 9fe9e2eb73d..91a3dce0b4e 100644
--- a/spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb
+++ b/spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::AfterExportStrategyBuilder do
+RSpec.describe Gitlab::ImportExport::AfterExportStrategyBuilder do
let!(:strategies_namespace) { 'Gitlab::ImportExport::AfterExportStrategies' }
describe '.build!' do
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index ef9321dc1fc..02500778426 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -31,6 +31,8 @@ issues:
- closed_by
- epic_issue
- epic
+- feature_flag_issues
+- feature_flags
- designs
- design_versions
- description_versions
@@ -95,6 +97,7 @@ snippets:
- user_agent_detail
- user_mentions
- snippet_repository
+- statistics
releases:
- author
- project
@@ -217,13 +220,15 @@ ci_pipelines:
- parent_pipeline
- downstream_bridges
- job_artifacts
-- vulnerabilities_occurrence_pipelines
+- vulnerabilities_finding_pipelines
- vulnerability_findings
- pipeline_config
- security_scans
- daily_build_group_report_results
- latest_builds
- daily_report_results
+- latest_builds_report_results
+- messages
ci_refs:
- project
- ci_pipelines
@@ -317,6 +322,7 @@ project:
- last_event
- services
- campfire_service
+- confluence_service
- discord_service
- drone_ci_service
- emails_on_push_service
@@ -458,6 +464,8 @@ project:
- vulnerability_feedback
- vulnerability_identifiers
- vulnerability_scanners
+- dast_site_profiles
+- dast_sites
- operations_feature_flags
- operations_feature_flags_client
- operations_feature_flags_user_lists
@@ -465,6 +473,7 @@ project:
- prometheus_alert_events
- self_managed_prometheus_alert_events
- software_license_policies
+- software_licenses
- project_registry
- packages
- package_files
@@ -505,6 +514,7 @@ project:
- freeze_periods
- webex_teams_service
- build_report_results
+- vulnerability_statistic
award_emoji:
- awardable
- user
@@ -569,6 +579,9 @@ self_managed_prometheus_alert_events:
epic_issues:
- issue
- epic
+feature_flag_issues:
+- issue
+- feature_flag
tracing_setting:
- project
reviews:
diff --git a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
index 65e99c0c3b8..733be7fc226 100644
--- a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
+++ b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::AttributeCleaner do
+RSpec.describe Gitlab::ImportExport::AttributeCleaner do
let(:relation_class) { double('relation_class').as_null_object }
let(:unsafe_hash) do
{
diff --git a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb
index f97dafc6bf9..0581f07dd3f 100644
--- a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb
+++ b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb
@@ -9,7 +9,7 @@ require 'spec_helper'
# to be included as part of the export, or blacklist them using the import_export.yml configuration file.
# Likewise, new models added to import_export.yml, will need to be added with their correspondent attributes
# to this spec.
-describe 'Import/Export attribute configuration' do
+RSpec.describe 'Import/Export attribute configuration' do
include ConfigurationHelper
let(:safe_attributes_file) { 'spec/lib/gitlab/import_export/safe_model_attributes.yml' }
diff --git a/spec/lib/gitlab/import_export/attributes_finder_spec.rb b/spec/lib/gitlab/import_export/attributes_finder_spec.rb
index 3cbc1375d6e..7f6ebf577af 100644
--- a/spec/lib/gitlab/import_export/attributes_finder_spec.rb
+++ b/spec/lib/gitlab/import_export/attributes_finder_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::ImportExport::AttributesFinder do
+RSpec.describe Gitlab::ImportExport::AttributesFinder do
describe '#find_root' do
subject { described_class.new(config: config).find_root(model_key) }
diff --git a/spec/lib/gitlab/import_export/attributes_permitter_spec.rb b/spec/lib/gitlab/import_export/attributes_permitter_spec.rb
index d6217811b9c..0c1b1cd74bf 100644
--- a/spec/lib/gitlab/import_export/attributes_permitter_spec.rb
+++ b/spec/lib/gitlab/import_export/attributes_permitter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::AttributesPermitter do
+RSpec.describe Gitlab::ImportExport::AttributesPermitter do
let(:yml_config) do
<<-EOF
tree:
diff --git a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
index 662e1a5eaab..994132239b9 100644
--- a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::AvatarRestorer do
+RSpec.describe Gitlab::ImportExport::AvatarRestorer do
include UploadHelpers
let(:shared) { project.import_export_shared }
diff --git a/spec/lib/gitlab/import_export/avatar_saver_spec.rb b/spec/lib/gitlab/import_export/avatar_saver_spec.rb
index d2349e47c0a..334d930c47c 100644
--- a/spec/lib/gitlab/import_export/avatar_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/avatar_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::AvatarSaver do
+RSpec.describe Gitlab::ImportExport::AvatarSaver do
let(:shared) { project.import_export_shared }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:project_with_avatar) { create(:project, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
diff --git a/spec/lib/gitlab/import_export/base/object_builder_spec.rb b/spec/lib/gitlab/import_export/base/object_builder_spec.rb
index e5242ae0bfc..d560c8ea5a7 100644
--- a/spec/lib/gitlab/import_export/base/object_builder_spec.rb
+++ b/spec/lib/gitlab/import_export/base/object_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Base::ObjectBuilder do
+RSpec.describe Gitlab::ImportExport::Base::ObjectBuilder do
let(:project) do
create(:project, :repository,
:builds_disabled,
diff --git a/spec/lib/gitlab/import_export/base/relation_factory_spec.rb b/spec/lib/gitlab/import_export/base/relation_factory_spec.rb
index 50d93763ad6..09e6e5a03bb 100644
--- a/spec/lib/gitlab/import_export/base/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/base/relation_factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Base::RelationFactory do
+RSpec.describe Gitlab::ImportExport::Base::RelationFactory do
let(:user) { create(:admin) }
let(:project) { create(:project) }
let(:members_mapper) { double('members_mapper').as_null_object }
diff --git a/spec/lib/gitlab/import_export/command_line_util_spec.rb b/spec/lib/gitlab/import_export/command_line_util_spec.rb
index 8e5e0aefac0..b00a2597681 100644
--- a/spec/lib/gitlab/import_export/command_line_util_spec.rb
+++ b/spec/lib/gitlab/import_export/command_line_util_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::CommandLineUtil do
+RSpec.describe Gitlab::ImportExport::CommandLineUtil do
include ExportFileHelper
let(:path) { "#{Dir.tmpdir}/symlink_test" }
diff --git a/spec/lib/gitlab/import_export/config_spec.rb b/spec/lib/gitlab/import_export/config_spec.rb
index f09a29b84db..40cf75779b6 100644
--- a/spec/lib/gitlab/import_export/config_spec.rb
+++ b/spec/lib/gitlab/import_export/config_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require 'rspec-parameterized'
-describe Gitlab::ImportExport::Config do
+RSpec.describe Gitlab::ImportExport::Config do
let(:yaml_file) { described_class.new }
describe '#to_h' do
diff --git a/spec/lib/gitlab/import_export/design_repo_restorer_spec.rb b/spec/lib/gitlab/import_export/design_repo_restorer_spec.rb
index 5662b8af280..b311a02833c 100644
--- a/spec/lib/gitlab/import_export/design_repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/design_repo_restorer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::DesignRepoRestorer do
+RSpec.describe Gitlab::ImportExport::DesignRepoRestorer do
include GitHelpers
describe 'bundle a design Git repo' do
diff --git a/spec/lib/gitlab/import_export/design_repo_saver_spec.rb b/spec/lib/gitlab/import_export/design_repo_saver_spec.rb
index bff48e8b52a..2575d209db5 100644
--- a/spec/lib/gitlab/import_export/design_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/design_repo_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::DesignRepoSaver do
+RSpec.describe Gitlab::ImportExport::DesignRepoSaver do
describe 'bundle a design Git repo' do
let_it_be(:user) { create(:user) }
let_it_be(:design) { create(:design, :with_file, versions_count: 1) }
diff --git a/spec/lib/gitlab/import_export/error_spec.rb b/spec/lib/gitlab/import_export/error_spec.rb
index 067f7049097..015133a399b 100644
--- a/spec/lib/gitlab/import_export/error_spec.rb
+++ b/spec/lib/gitlab/import_export/error_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Error do
+RSpec.describe Gitlab::ImportExport::Error do
describe '.permission_error' do
subject(:error) do
described_class.permission_error(user, importable)
diff --git a/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb b/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
index 916ed692a05..5b6be0b3198 100644
--- a/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
+++ b/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::FastHashSerializer do
+RSpec.describe Gitlab::ImportExport::FastHashSerializer do
# FastHashSerializer#execute generates the hash which is not easily accessible
# and includes `JSONBatchRelation` items which are serialized at this point.
# Wrapping the result into JSON generating/parsing is for making
@@ -175,14 +175,6 @@ describe Gitlab::ImportExport::FastHashSerializer do
expect(subject['merge_requests'].first['resource_label_events']).not_to be_empty
end
- it 'saves the correct service type' do
- expect(subject['services'].first['type']).to eq('CustomIssueTrackerService')
- end
-
- it 'saves the properties for a service' do
- expect(subject['services'].first['properties']).to eq('one' => 'value')
- end
-
it 'has project feature' do
project_feature = subject['project_feature']
expect(project_feature).not_to be_empty
diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb
index 7c54c5f2da1..47485cc7edb 100644
--- a/spec/lib/gitlab/import_export/file_importer_spec.rb
+++ b/spec/lib/gitlab/import_export/file_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::FileImporter do
+RSpec.describe Gitlab::ImportExport::FileImporter do
include ExportFileHelper
let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
diff --git a/spec/lib/gitlab/import_export/fork_spec.rb b/spec/lib/gitlab/import_export/fork_spec.rb
index bb79331efac..ef7394053b9 100644
--- a/spec/lib/gitlab/import_export/fork_spec.rb
+++ b/spec/lib/gitlab/import_export/fork_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'forked project import' do
+RSpec.describe 'forked project import' do
include ProjectForksHelper
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/import_export/group/legacy_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/group/legacy_tree_restorer_spec.rb
index 4c926da1436..bfcd4994995 100644
--- a/spec/lib/gitlab/import_export/group/legacy_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/group/legacy_tree_restorer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Group::LegacyTreeRestorer do
+RSpec.describe Gitlab::ImportExport::Group::LegacyTreeRestorer do
include ImportExport::CommonUtil
let(:shared) { Gitlab::ImportExport::Shared.new(group) }
diff --git a/spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb b/spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb
index a0490c255aa..6b324b952dc 100644
--- a/spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Group::LegacyTreeSaver do
+RSpec.describe Gitlab::ImportExport::Group::LegacyTreeSaver do
describe 'saves the group tree into a json object' do
let(:shared) { Gitlab::ImportExport::Shared.new(group) }
let(:group_tree_saver) { described_class.new(group: group, current_user: user, shared: shared) }
diff --git a/spec/lib/gitlab/import_export/group/object_builder_spec.rb b/spec/lib/gitlab/import_export/group/object_builder_spec.rb
index 781670b0aa5..028bd5463a1 100644
--- a/spec/lib/gitlab/import_export/group/object_builder_spec.rb
+++ b/spec/lib/gitlab/import_export/group/object_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Group::ObjectBuilder do
+RSpec.describe Gitlab::ImportExport::Group::ObjectBuilder do
let(:group) { create(:group) }
let(:base_attributes) do
{
diff --git a/spec/lib/gitlab/import_export/group/relation_factory_spec.rb b/spec/lib/gitlab/import_export/group/relation_factory_spec.rb
index 332648d5c89..eb9a3fa9bd8 100644
--- a/spec/lib/gitlab/import_export/group/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/group/relation_factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Group::RelationFactory do
+RSpec.describe Gitlab::ImportExport::Group::RelationFactory do
let(:group) { create(:group) }
let(:members_mapper) { double('members_mapper').as_null_object }
let(:user) { create(:admin) }
diff --git a/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb b/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb
index 327f36c664e..6cc16ee9cbb 100644
--- a/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Group::TreeRestorer do
+RSpec.describe Gitlab::ImportExport::Group::TreeRestorer do
include ImportExport::CommonUtil
describe 'restore group tree' do
diff --git a/spec/lib/gitlab/import_export/group/tree_saver_spec.rb b/spec/lib/gitlab/import_export/group/tree_saver_spec.rb
index 06e8484a3cb..908896e4891 100644
--- a/spec/lib/gitlab/import_export/group/tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/group/tree_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Group::TreeSaver do
+RSpec.describe Gitlab::ImportExport::Group::TreeSaver do
describe 'saves the group tree into a json object' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { setup_groups }
diff --git a/spec/lib/gitlab/import_export/hash_util_spec.rb b/spec/lib/gitlab/import_export/hash_util_spec.rb
index b97c6665d0e..467342e6e96 100644
--- a/spec/lib/gitlab/import_export/hash_util_spec.rb
+++ b/spec/lib/gitlab/import_export/hash_util_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::HashUtil do
+RSpec.describe Gitlab::ImportExport::HashUtil do
let(:stringified_array) { [{ 'test' => 1 }] }
let(:stringified_array_with_date) { [{ 'test_date' => '2016-04-06 06:17:44 +0200' }] }
diff --git a/spec/lib/gitlab/import_export/import_export_equivalence_spec.rb b/spec/lib/gitlab/import_export/import_export_equivalence_spec.rb
index 95df9cd0e6e..18f2e8f80d7 100644
--- a/spec/lib/gitlab/import_export/import_export_equivalence_spec.rb
+++ b/spec/lib/gitlab/import_export/import_export_equivalence_spec.rb
@@ -13,7 +13,7 @@ require 'spec_helper'
# - randomly generated fields like tokens
#
# as these are expected to change between import/export cycles.
-describe Gitlab::ImportExport do
+RSpec.describe Gitlab::ImportExport do
include ImportExport::CommonUtil
include ConfigurationHelper
include ImportExport::ProjectTreeExpectations
diff --git a/spec/lib/gitlab/import_export/import_export_spec.rb b/spec/lib/gitlab/import_export/import_export_spec.rb
index 300ba66ee5b..62b4717fc96 100644
--- a/spec/lib/gitlab/import_export/import_export_spec.rb
+++ b/spec/lib/gitlab/import_export/import_export_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport do
+RSpec.describe Gitlab::ImportExport do
describe 'export filename' do
let(:group) { create(:group, :nested) }
let(:project) { create(:project, :public, path: 'project-path', namespace: group) }
diff --git a/spec/lib/gitlab/import_export/import_failure_service_spec.rb b/spec/lib/gitlab/import_export/import_failure_service_spec.rb
index 324328181e4..c8bb067d40c 100644
--- a/spec/lib/gitlab/import_export/import_failure_service_spec.rb
+++ b/spec/lib/gitlab/import_export/import_failure_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::ImportFailureService do
+RSpec.describe Gitlab::ImportExport::ImportFailureService do
let(:importable) { create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project') }
let(:label) { create(:label) }
let(:subject) { described_class.new(importable) }
diff --git a/spec/lib/gitlab/import_export/import_test_coverage_spec.rb b/spec/lib/gitlab/import_export/import_test_coverage_spec.rb
index c5a7327332e..9737a0f39fc 100644
--- a/spec/lib/gitlab/import_export/import_test_coverage_spec.rb
+++ b/spec/lib/gitlab/import_export/import_test_coverage_spec.rb
@@ -7,7 +7,7 @@ require 'spec_helper'
# Fixture JSONs we use for testing Import such as
# `spec/fixtures/lib/gitlab/import_export/complex/project.json`
# should include these relations being non-empty.
-describe 'Test coverage of the Project Import' do
+RSpec.describe 'Test coverage of the Project Import' do
include ConfigurationHelper
# `muted_relations` is a technical debt.
diff --git a/spec/lib/gitlab/import_export/importer_spec.rb b/spec/lib/gitlab/import_export/importer_spec.rb
index 494f7e3a00d..dc44296321c 100644
--- a/spec/lib/gitlab/import_export/importer_spec.rb
+++ b/spec/lib/gitlab/import_export/importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Importer do
+RSpec.describe Gitlab::ImportExport::Importer do
let(:user) { create(:user) }
let(:test_path) { "#{Dir.tmpdir}/importer_spec" }
let(:shared) { project.import_export_shared }
diff --git a/spec/lib/gitlab/import_export/json/legacy_reader/file_spec.rb b/spec/lib/gitlab/import_export/json/legacy_reader/file_spec.rb
index 99932404fd9..9c7f41cbb89 100644
--- a/spec/lib/gitlab/import_export/json/legacy_reader/file_spec.rb
+++ b/spec/lib/gitlab/import_export/json/legacy_reader/file_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require_relative 'shared_example.rb'
-describe Gitlab::ImportExport::JSON::LegacyReader::File do
+RSpec.describe Gitlab::ImportExport::JSON::LegacyReader::File do
it_behaves_like 'import/export json legacy reader' do
let(:valid_path) { 'spec/fixtures/lib/gitlab/import_export/light/project.json' }
let(:data) { valid_path }
diff --git a/spec/lib/gitlab/import_export/json/legacy_reader/hash_spec.rb b/spec/lib/gitlab/import_export/json/legacy_reader/hash_spec.rb
index e793dc7339d..d0899accf59 100644
--- a/spec/lib/gitlab/import_export/json/legacy_reader/hash_spec.rb
+++ b/spec/lib/gitlab/import_export/json/legacy_reader/hash_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require_relative 'shared_example.rb'
-describe Gitlab::ImportExport::JSON::LegacyReader::Hash do
+RSpec.describe Gitlab::ImportExport::JSON::LegacyReader::Hash do
it_behaves_like 'import/export json legacy reader' do
let(:path) { 'spec/fixtures/lib/gitlab/import_export/light/project.json' }
diff --git a/spec/lib/gitlab/import_export/json/legacy_writer_spec.rb b/spec/lib/gitlab/import_export/json/legacy_writer_spec.rb
index 1f39b26e46a..eb7a2d4aa8b 100644
--- a/spec/lib/gitlab/import_export/json/legacy_writer_spec.rb
+++ b/spec/lib/gitlab/import_export/json/legacy_writer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::JSON::LegacyWriter do
+RSpec.describe Gitlab::ImportExport::JSON::LegacyWriter do
let(:path) { "#{Dir.tmpdir}/legacy_writer_spec/test.json" }
subject do
diff --git a/spec/lib/gitlab/import_export/json/ndjson_reader_spec.rb b/spec/lib/gitlab/import_export/json/ndjson_reader_spec.rb
index 34e8b1ddd59..a347d835428 100644
--- a/spec/lib/gitlab/import_export/json/ndjson_reader_spec.rb
+++ b/spec/lib/gitlab/import_export/json/ndjson_reader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::JSON::NdjsonReader do
+RSpec.describe Gitlab::ImportExport::JSON::NdjsonReader do
include ImportExport::CommonUtil
let(:fixture) { 'spec/fixtures/lib/gitlab/import_export/light/tree' }
diff --git a/spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb b/spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb
index bae3672474c..0af74dee604 100644
--- a/spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb
+++ b/spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::ImportExport::JSON::NdjsonWriter do
+RSpec.describe Gitlab::ImportExport::JSON::NdjsonWriter do
include ImportExport::CommonUtil
let(:path) { "#{Dir.tmpdir}/ndjson_writer_spec/tree" }
diff --git a/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb b/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb
index 30f8280fda3..eb6b07ce02f 100644
--- a/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb
+++ b/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::JSON::StreamingSerializer do
+RSpec.describe Gitlab::ImportExport::JSON::StreamingSerializer do
let_it_be(:user) { create(:user) }
let_it_be(:release) { create(:release) }
let_it_be(:group) { create(:group) }
@@ -61,6 +61,20 @@ describe Gitlab::ImportExport::JSON::StreamingSerializer do
subject.execute
end
+
+ context 'relation ordering' do
+ before do
+ create_list(:issue, 5, project: exportable)
+ end
+
+ it 'orders exported issues by primary key' do
+ expected_issues = exportable.issues.reorder(:id).map(&:to_json)
+
+ expect(json_writer).to receive(:write_relation_array).with(exportable_path, :issues, expected_issues)
+
+ subject.execute
+ end
+ end
end
context 'with single relation' do
diff --git a/spec/lib/gitlab/import_export/legacy_relation_tree_saver_spec.rb b/spec/lib/gitlab/import_export/legacy_relation_tree_saver_spec.rb
index 6562aa5b8a6..454cc74b9d4 100644
--- a/spec/lib/gitlab/import_export/legacy_relation_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/legacy_relation_tree_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::LegacyRelationTreeSaver do
+RSpec.describe Gitlab::ImportExport::LegacyRelationTreeSaver do
let(:exportable) { create(:group) }
let(:relation_tree_saver) { described_class.new }
let(:tree) { {} }
diff --git a/spec/lib/gitlab/import_export/lfs_restorer_spec.rb b/spec/lib/gitlab/import_export/lfs_restorer_spec.rb
index a932dc3ee4e..a9f7fb72612 100644
--- a/spec/lib/gitlab/import_export/lfs_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/lfs_restorer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::LfsRestorer do
+RSpec.describe Gitlab::ImportExport::LfsRestorer do
include UploadHelpers
let(:export_path) { "#{Dir.tmpdir}/lfs_object_restorer_spec" }
diff --git a/spec/lib/gitlab/import_export/lfs_saver_spec.rb b/spec/lib/gitlab/import_export/lfs_saver_spec.rb
index e9d06573e70..db76eb9538b 100644
--- a/spec/lib/gitlab/import_export/lfs_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/lfs_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::LfsSaver do
+RSpec.describe Gitlab::ImportExport::LfsSaver do
let(:shared) { project.import_export_shared }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb
index 61e893bfb3c..9755e322221 100644
--- a/spec/lib/gitlab/import_export/members_mapper_spec.rb
+++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::MembersMapper do
+RSpec.describe Gitlab::ImportExport::MembersMapper do
describe 'map members' do
shared_examples 'imports exported members' do
let(:user) { create(:admin) }
diff --git a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
index 0cdb3c43992..c558c12f581 100644
--- a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
+++ b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::MergeRequestParser do
+RSpec.describe Gitlab::ImportExport::MergeRequestParser do
include ProjectForksHelper
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/import_export/model_configuration_spec.rb b/spec/lib/gitlab/import_export/model_configuration_spec.rb
index cfbfe244988..34591122a97 100644
--- a/spec/lib/gitlab/import_export/model_configuration_spec.rb
+++ b/spec/lib/gitlab/import_export/model_configuration_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
# Part of the test security suite for the Import/Export feature
# Finds if a new model has been added that can potentially be part of the Import/Export
# If it finds a new model, it will show a +failure_message+ with the options available.
-describe 'Import/Export model configuration' do
+RSpec.describe 'Import/Export model configuration' do
include ConfigurationHelper
let(:all_models_yml) { 'spec/lib/gitlab/import_export/all_models.yml' }
diff --git a/spec/lib/gitlab/import_export/project/export_task_spec.rb b/spec/lib/gitlab/import_export/project/export_task_spec.rb
index dc8eb54dc14..1048379a5d6 100644
--- a/spec/lib/gitlab/import_export/project/export_task_spec.rb
+++ b/spec/lib/gitlab/import_export/project/export_task_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe Gitlab::ImportExport::Project::ExportTask do
+RSpec.describe Gitlab::ImportExport::Project::ExportTask do
let_it_be(:username) { 'root' }
let(:namespace_path) { username }
let_it_be(:user) { create(:user, username: username) }
diff --git a/spec/lib/gitlab/import_export/project/import_task_spec.rb b/spec/lib/gitlab/import_export/project/import_task_spec.rb
index 7c11161aaa7..90f4501acdc 100644
--- a/spec/lib/gitlab/import_export/project/import_task_spec.rb
+++ b/spec/lib/gitlab/import_export/project/import_task_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe Gitlab::ImportExport::Project::ImportTask, :request_store do
+RSpec.describe Gitlab::ImportExport::Project::ImportTask, :request_store do
let(:username) { 'root' }
let(:namespace_path) { username }
let!(:user) { create(:user, username: username) }
diff --git a/spec/lib/gitlab/import_export/project/object_builder_spec.rb b/spec/lib/gitlab/import_export/project/object_builder_spec.rb
index c9d1410400a..20d882c82be 100644
--- a/spec/lib/gitlab/import_export/project/object_builder_spec.rb
+++ b/spec/lib/gitlab/import_export/project/object_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Project::ObjectBuilder do
+RSpec.describe Gitlab::ImportExport::Project::ObjectBuilder do
let!(:group) { create(:group, :private) }
let!(:subgroup) { create(:group, :private, parent: group) }
let!(:project) do
diff --git a/spec/lib/gitlab/import_export/project/relation_factory_spec.rb b/spec/lib/gitlab/import_export/project/relation_factory_spec.rb
index 3339129cb8f..31cf2362628 100644
--- a/spec/lib/gitlab/import_export/project/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/project/relation_factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Project::RelationFactory do
+RSpec.describe Gitlab::ImportExport::Project::RelationFactory do
let(:group) { create(:group) }
let(:project) { create(:project, :repository, group: group) }
let(:members_mapper) { double('members_mapper').as_null_object }
diff --git a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
index 867dc37c5c5..6d5604dc40f 100644
--- a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
@@ -6,7 +6,7 @@ def match_mr1_note(content_regex)
MergeRequest.find_by(title: 'MR1').notes.select { |n| n.note.match(/#{content_regex}/)}.first
end
-describe Gitlab::ImportExport::Project::TreeRestorer do
+RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
include ImportExport::CommonUtil
using RSpec::Parameterized::TableSyntax
@@ -291,10 +291,6 @@ describe Gitlab::ImportExport::Project::TreeRestorer do
expect(@project.auto_devops.deploy_strategy).to eq('continuous')
end
- it 'restores the correct service' do
- expect(CustomIssueTrackerService.first).not_to be_nil
- end
-
it 'restores zoom meetings' do
meetings = @project.issues.first.zoom_meetings
@@ -553,8 +549,7 @@ describe Gitlab::ImportExport::Project::TreeRestorer do
labels: 2,
label_with_priorities: 'A project label',
milestones: 1,
- first_issue_labels: 1,
- services: 1
+ first_issue_labels: 1
end
context 'when there is an existing build with build token' do
@@ -637,7 +632,6 @@ describe Gitlab::ImportExport::Project::TreeRestorer do
label_with_priorities: 'A project label',
milestones: 1,
first_issue_labels: 1,
- services: 1,
import_failures: 1
it 'records the failures in the database' do
@@ -757,18 +751,6 @@ describe Gitlab::ImportExport::Project::TreeRestorer do
setup_reader(reader)
end
- it 'does not import any templated services' do
- expect(restored_project_json).to eq(true)
-
- expect(project.services.where(template: true).count).to eq(0)
- end
-
- it 'does not import any instance services' do
- expect(restored_project_json).to eq(true)
-
- expect(project.services.where(instance: true).count).to eq(0)
- end
-
it 'imports labels' do
create(:group_label, name: 'Another label', group: project.group)
@@ -972,7 +954,6 @@ describe Gitlab::ImportExport::Project::TreeRestorer do
label_with_priorities: nil,
milestones: 1,
first_issue_labels: 0,
- services: 0,
import_failures: 1
it 'records the failures in the database' do
diff --git a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
index 533d1097928..40c103eeda6 100644
--- a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Project::TreeSaver do
+RSpec.describe Gitlab::ImportExport::Project::TreeSaver do
let_it_be(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let_it_be(:exportable_path) { 'project' }
@@ -223,18 +223,6 @@ describe Gitlab::ImportExport::Project::TreeSaver do
it { is_expected.not_to be_empty }
end
- context 'with services' do
- let(:relation_name) { :services }
-
- it 'saves the correct service type' do
- expect(subject.first['type']).to eq('CustomIssueTrackerService')
- end
-
- it 'saves the properties for a service' do
- expect(subject.first['properties']).to eq('one' => 'value')
- end
- end
-
context 'with project_feature' do
let(:relation_name) { :project_feature }
@@ -453,7 +441,6 @@ describe Gitlab::ImportExport::Project::TreeSaver do
create(:resource_label_event, label: group_label, merge_request: merge_request)
create(:event, :created, target: milestone, project: project, author: user)
- create(:service, project: project, type: 'CustomIssueTrackerService', category: 'issue_tracker', properties: { one: 'value' })
create(:project_custom_attribute, project: project)
create(:project_custom_attribute, project: project)
diff --git a/spec/lib/gitlab/import_export/reader_spec.rb b/spec/lib/gitlab/import_export/reader_spec.rb
index e37ad281eb5..8828c7ceb62 100644
--- a/spec/lib/gitlab/import_export/reader_spec.rb
+++ b/spec/lib/gitlab/import_export/reader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Reader do
+RSpec.describe Gitlab::ImportExport::Reader do
let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
describe '#project_tree' do
diff --git a/spec/lib/gitlab/import_export/references_configuration_spec.rb b/spec/lib/gitlab/import_export/references_configuration_spec.rb
index 91cf9f964c0..2934d0059ee 100644
--- a/spec/lib/gitlab/import_export/references_configuration_spec.rb
+++ b/spec/lib/gitlab/import_export/references_configuration_spec.rb
@@ -9,7 +9,7 @@ require 'spec_helper'
# or to be blacklisted by using the import_export.yml configuration file.
# Likewise, new models added to import_export.yml, will need to be added with their correspondent relations
# to this spec.
-describe 'Import/Export Project configuration' do
+RSpec.describe 'Import/Export Project configuration' do
include ConfigurationHelper
where(:relation_path, :relation_name) do
diff --git a/spec/lib/gitlab/import_export/relation_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/relation_tree_restorer_spec.rb
index 8fe419da450..ddc96b83208 100644
--- a/spec/lib/gitlab/import_export/relation_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/relation_tree_restorer_spec.rb
@@ -9,7 +9,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::RelationTreeRestorer do
+RSpec.describe Gitlab::ImportExport::RelationTreeRestorer do
include ImportExport::CommonUtil
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
index d5839589633..ace4449042e 100644
--- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::RepoRestorer do
+RSpec.describe Gitlab::ImportExport::RepoRestorer do
include GitHelpers
describe 'bundle a project Git repo' do
diff --git a/spec/lib/gitlab/import_export/repo_saver_spec.rb b/spec/lib/gitlab/import_export/repo_saver_spec.rb
index a95d661ec3c..73d51000c67 100644
--- a/spec/lib/gitlab/import_export/repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::RepoSaver do
+RSpec.describe Gitlab::ImportExport::RepoSaver do
describe 'bundle a project Git repo' do
let_it_be(:user) { create(:user) }
let!(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 0d112bfdb2a..2d313b4dcad 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -461,36 +461,6 @@ DeployKey:
- public
- can_push
- last_used_at
-Service:
-- id
-- type
-- title
-- project_id
-- created_at
-- updated_at
-- active
-- properties
-- template
-- instance
-- alert_events
-- push_events
-- issues_events
-- commit_events
-- merge_requests_events
-- tag_push_events
-- note_events
-- pipeline_events
-- job_events
-- comment_on_event_enabled
-- comment_detail
-- category
-- default
-- wiki_page_events
-- confidential_issues_events
-- confidential_note_events
-- deployment_events
-- description
-- inherit_from_id
ProjectHook:
- id
- url
@@ -618,6 +588,7 @@ ProtectedBranch::PushAccessLevel:
- updated_at
- user_id
- group_id
+- deploy_key_id
ProtectedBranch::UnprotectAccessLevel:
- id
- protected_branch_id
@@ -705,6 +676,7 @@ ProjectCiCdSetting:
- group_runners_enabled
ProjectSetting:
- allow_merge_on_skipped_pipeline
+- has_confluence
ProtectedEnvironment:
- id
- project_id
diff --git a/spec/lib/gitlab/import_export/saver_spec.rb b/spec/lib/gitlab/import_export/saver_spec.rb
index 18e9d7da32d..865c7e57b5a 100644
--- a/spec/lib/gitlab/import_export/saver_spec.rb
+++ b/spec/lib/gitlab/import_export/saver_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'fileutils'
-describe Gitlab::ImportExport::Saver do
+RSpec.describe Gitlab::ImportExport::Saver do
let!(:project) { create(:project, :public, name: 'project') }
let(:base_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:export_path) { "#{base_path}/project_tree_saver_spec/export" }
diff --git a/spec/lib/gitlab/import_export/shared_spec.rb b/spec/lib/gitlab/import_export/shared_spec.rb
index 8c16243576d..22f2d4c5077 100644
--- a/spec/lib/gitlab/import_export/shared_spec.rb
+++ b/spec/lib/gitlab/import_export/shared_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'fileutils'
-describe Gitlab::ImportExport::Shared do
+RSpec.describe Gitlab::ImportExport::Shared do
let(:project) { build(:project) }
subject { project.import_export_shared }
diff --git a/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb b/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb
index 43c4b164b2d..fe934cadedd 100644
--- a/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::SnippetRepoRestorer do
+RSpec.describe Gitlab::ImportExport::SnippetRepoRestorer do
let_it_be(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
@@ -35,6 +35,12 @@ describe Gitlab::ImportExport::SnippetRepoRestorer do
end
end
+ it 'does not call snippet update statistics service' do
+ expect(Snippets::UpdateStatisticsService).not_to receive(:new).with(snippet)
+
+ restorer.restore
+ end
+
context 'when the repository creation fails' do
it 'returns false' do
allow_any_instance_of(Gitlab::BackgroundMigration::BackfillSnippetRepositories).to receive(:perform_by_ids).and_return(nil)
@@ -66,6 +72,10 @@ describe Gitlab::ImportExport::SnippetRepoRestorer do
before do
expect(exporter.save).to be_truthy
+
+ allow_next_instance_of(Snippets::RepositoryValidationService) do |instance|
+ allow(instance).to receive(:execute).and_return(ServiceResponse.success)
+ end
end
context 'when it is valid' do
@@ -115,5 +125,19 @@ describe Gitlab::ImportExport::SnippetRepoRestorer do
end
end
end
+
+ it 'refreshes snippet statistics' do
+ expect(snippet.statistics.commit_count).to be_zero
+ expect(snippet.statistics.file_count).to be_zero
+ expect(snippet.statistics.repository_size).to be_zero
+
+ expect(Snippets::UpdateStatisticsService).to receive(:new).with(snippet).and_call_original
+
+ restorer.restore
+
+ expect(snippet.statistics.commit_count).not_to be_zero
+ expect(snippet.statistics.file_count).not_to be_zero
+ expect(snippet.statistics.repository_size).not_to be_zero
+ end
end
end
diff --git a/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb b/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb
index 7ad1ff213a1..323ed9a746e 100644
--- a/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::SnippetRepoSaver do
+RSpec.describe Gitlab::ImportExport::SnippetRepoSaver do
describe 'bundle a project Git repo' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, namespace: user.namespace) }
diff --git a/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb b/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb
index ac73462073e..7ca365762b5 100644
--- a/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::SnippetsRepoRestorer do
+RSpec.describe Gitlab::ImportExport::SnippetsRepoRestorer do
include GitHelpers
describe 'bundle a snippet Git repo' do
diff --git a/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb b/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb
index 5332990a975..8507c46ec83 100644
--- a/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::SnippetsRepoSaver do
+RSpec.describe Gitlab::ImportExport::SnippetsRepoSaver do
describe 'bundle a project Git repo' do
let_it_be(:user) { create(:user) }
let!(:project) { create(:project) }
diff --git a/spec/lib/gitlab/import_export/uploads_manager_spec.rb b/spec/lib/gitlab/import_export/uploads_manager_spec.rb
index e6d6ba840be..33ad0e12c37 100644
--- a/spec/lib/gitlab/import_export/uploads_manager_spec.rb
+++ b/spec/lib/gitlab/import_export/uploads_manager_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::UploadsManager do
+RSpec.describe Gitlab::ImportExport::UploadsManager do
let(:shared) { project.import_export_shared }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
index 077ece87b31..864fd8d72fa 100644
--- a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::UploadsRestorer do
+RSpec.describe Gitlab::ImportExport::UploadsRestorer do
describe 'bundle a project Git repo' do
let(:export_path) { "#{Dir.tmpdir}/uploads_saver_spec" }
let(:shared) { project.import_export_shared }
diff --git a/spec/lib/gitlab/import_export/uploads_saver_spec.rb b/spec/lib/gitlab/import_export/uploads_saver_spec.rb
index 8a36caef316..8e9be209f89 100644
--- a/spec/lib/gitlab/import_export/uploads_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/uploads_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::UploadsSaver do
+RSpec.describe Gitlab::ImportExport::UploadsSaver do
describe 'bundle a project Git repo' do
let(:export_path) { "#{Dir.tmpdir}/uploads_saver_spec" }
let(:file) { fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif') }
diff --git a/spec/lib/gitlab/import_export/version_checker_spec.rb b/spec/lib/gitlab/import_export/version_checker_spec.rb
index befbd1b4c19..8b39330656f 100644
--- a/spec/lib/gitlab/import_export/version_checker_spec.rb
+++ b/spec/lib/gitlab/import_export/version_checker_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
include ImportExport::CommonUtil
-describe Gitlab::ImportExport::VersionChecker do
+RSpec.describe Gitlab::ImportExport::VersionChecker do
let!(:shared) { Gitlab::ImportExport::Shared.new(nil) }
describe 'bundle a project Git repo' do
diff --git a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
index 264272d2026..778d0859bf1 100644
--- a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::WikiRepoSaver do
+RSpec.describe Gitlab::ImportExport::WikiRepoSaver do
describe 'bundle a wiki Git repo' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :wiki_repo) }
diff --git a/spec/lib/gitlab/import_export/wiki_restorer_spec.rb b/spec/lib/gitlab/import_export/wiki_restorer_spec.rb
index 33cd3e55393..6c80c410d07 100644
--- a/spec/lib/gitlab/import_export/wiki_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/wiki_restorer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::WikiRestorer do
+RSpec.describe Gitlab::ImportExport::WikiRestorer do
describe 'restore a wiki Git repo' do
let!(:project_with_wiki) { create(:project, :wiki_repo) }
let!(:project_without_wiki) { create(:project) }
diff --git a/spec/lib/gitlab/import_formatter_spec.rb b/spec/lib/gitlab/import_formatter_spec.rb
index e9f63ba5777..fbf00ab92d3 100644
--- a/spec/lib/gitlab/import_formatter_spec.rb
+++ b/spec/lib/gitlab/import_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportFormatter do
+RSpec.describe Gitlab::ImportFormatter do
let(:formatter) { Gitlab::ImportFormatter.new }
describe '#comment' do
diff --git a/spec/lib/gitlab/import_sources_spec.rb b/spec/lib/gitlab/import_sources_spec.rb
index 265241dc2af..0dfd8a2ee50 100644
--- a/spec/lib/gitlab/import_sources_spec.rb
+++ b/spec/lib/gitlab/import_sources_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportSources do
+RSpec.describe Gitlab::ImportSources do
describe '.options' do
it 'returns a hash' do
expected =
diff --git a/spec/lib/gitlab/incident_management/pager_duty/incident_issue_description_spec.rb b/spec/lib/gitlab/incident_management/pager_duty/incident_issue_description_spec.rb
new file mode 100644
index 00000000000..9a55e21d031
--- /dev/null
+++ b/spec/lib/gitlab/incident_management/pager_duty/incident_issue_description_spec.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'timecop'
+
+RSpec.describe Gitlab::IncidentManagement::PagerDuty::IncidentIssueDescription do
+ describe '#to_s' do
+ let(:markdown_line_break) { ' ' }
+ let(:created_at) { '2017-09-26T15:14:36Z' }
+ let(:assignees) do
+ [{ 'summary' => 'Laura Haley', 'url' => 'https://webdemo.pagerduty.com/users/P553OPV' }]
+ end
+ let(:impacted_services) do
+ [{ 'summary' => 'Production XDB Cluster', 'url' => 'https://webdemo.pagerduty.com/services/PN49J75' }]
+ end
+ let(:incident_payload) do
+ {
+ 'url' => 'https://webdemo.pagerduty.com/incidents/PRORDTY',
+ 'incident_number' => 33,
+ 'title' => 'My new incident',
+ 'status' => 'triggered',
+ 'created_at' => created_at,
+ 'urgency' => 'high',
+ 'incident_key' => 'SOME-KEY',
+ 'assignees' => assignees,
+ 'impacted_services' => impacted_services
+ }
+ end
+
+ subject(:to_s) { described_class.new(incident_payload).to_s }
+
+ it 'returns description' do
+ expect(to_s).to eq(
+ <<~MARKDOWN.chomp
+ **Incident:** [My new incident](https://webdemo.pagerduty.com/incidents/PRORDTY)#{markdown_line_break}
+ **Incident number:** 33#{markdown_line_break}
+ **Urgency:** high#{markdown_line_break}
+ **Status:** triggered#{markdown_line_break}
+ **Incident key:** SOME-KEY#{markdown_line_break}
+ **Created at:** 26 September 2017, 3:14PM (UTC)#{markdown_line_break}
+ **Assignees:** [Laura Haley](https://webdemo.pagerduty.com/users/P553OPV)#{markdown_line_break}
+ **Impacted services:** [Production XDB Cluster](https://webdemo.pagerduty.com/services/PN49J75)
+ MARKDOWN
+ )
+ end
+
+ context 'when created_at is missing' do
+ let(:created_at) { nil }
+
+ it 'description contains current time in UTC' do
+ Timecop.freeze do
+ now = Time.current.utc.strftime('%d %B %Y, %-l:%M%p (%Z)')
+
+ expect(to_s).to include(
+ <<~MARKDOWN.chomp
+ **Created at:** #{now}#{markdown_line_break}
+ MARKDOWN
+ )
+ end
+ end
+ end
+
+ context 'when there are several assignees' do
+ let(:assignees) do
+ [
+ { 'summary' => 'Laura Haley', 'url' => 'https://laura.pagerduty.com' },
+ { 'summary' => 'John Doe', 'url' => 'https://john.pagerduty.com' }
+ ]
+ end
+
+ it 'assignees is a list of links' do
+ expect(to_s).to include(
+ <<~MARKDOWN.chomp
+ **Assignees:** [Laura Haley](https://laura.pagerduty.com), [John Doe](https://john.pagerduty.com)#{markdown_line_break}
+ MARKDOWN
+ )
+ end
+ end
+
+ context 'when there are several impacted services' do
+ let(:impacted_services) do
+ [
+ { 'summary' => 'XDB Cluster', 'url' => 'https://xdb.pagerduty.com' },
+ { 'summary' => 'BRB Cluster', 'url' => 'https://brb.pagerduty.com' }
+ ]
+ end
+
+ it 'impacted services is a list of links' do
+ expect(to_s).to include(
+ <<~MARKDOWN.chomp
+ **Impacted services:** [XDB Cluster](https://xdb.pagerduty.com), [BRB Cluster](https://brb.pagerduty.com)
+ MARKDOWN
+ )
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb
index 2dd45d18ee9..19d608cf48e 100644
--- a/spec/lib/gitlab/incoming_email_spec.rb
+++ b/spec/lib/gitlab/incoming_email_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::IncomingEmail do
+RSpec.describe Gitlab::IncomingEmail do
describe "self.enabled?" do
context "when reply by email is enabled" do
before do
diff --git a/spec/lib/gitlab/insecure_key_fingerprint_spec.rb b/spec/lib/gitlab/insecure_key_fingerprint_spec.rb
index 8d0422bae9f..2f3489edcd8 100644
--- a/spec/lib/gitlab/insecure_key_fingerprint_spec.rb
+++ b/spec/lib/gitlab/insecure_key_fingerprint_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::InsecureKeyFingerprint do
+RSpec.describe Gitlab::InsecureKeyFingerprint do
let(:key) do
'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn' \
'1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qk' \
diff --git a/spec/lib/gitlab/instrumentation/redis_base_spec.rb b/spec/lib/gitlab/instrumentation/redis_base_spec.rb
index 5ea8f00114e..07be0ccf6e9 100644
--- a/spec/lib/gitlab/instrumentation/redis_base_spec.rb
+++ b/spec/lib/gitlab/instrumentation/redis_base_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Instrumentation::RedisBase, :request_store do
+RSpec.describe Gitlab::Instrumentation::RedisBase, :request_store do
let(:instrumentation_class_a) do
stub_const('InstanceA', Class.new(described_class))
end
diff --git a/spec/lib/gitlab/instrumentation/redis_cluster_validator_spec.rb b/spec/lib/gitlab/instrumentation/redis_cluster_validator_spec.rb
new file mode 100644
index 00000000000..2ca7465e775
--- /dev/null
+++ b/spec/lib/gitlab/instrumentation/redis_cluster_validator_spec.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'support/helpers/rails_helpers'
+require 'rspec-parameterized'
+
+RSpec.describe Gitlab::Instrumentation::RedisClusterValidator do
+ include RailsHelpers
+
+ describe '.validate!' do
+ using RSpec::Parameterized::TableSyntax
+
+ context 'Rails environments' do
+ where(:env, :should_raise) do
+ 'production' | false
+ 'staging' | false
+ 'development' | true
+ 'test' | true
+ end
+
+ with_them do
+ it do
+ stub_rails_env(env)
+
+ args = [:mget, 'foo', 'bar']
+
+ if should_raise
+ expect { described_class.validate!(args) }
+ .to raise_error(described_class::CrossSlotError)
+ else
+ expect { described_class.validate!(args) }.not_to raise_error
+ end
+ end
+ end
+ end
+
+ where(:command, :arguments, :should_raise) do
+ :rename | %w(foo bar) | true
+ :RENAME | %w(foo bar) | true
+ 'rename' | %w(foo bar) | true
+ 'RENAME' | %w(foo bar) | true
+ :rename | %w(iaa ahy) | false # 'iaa' and 'ahy' hash to the same slot
+ :rename | %w({foo}:1 {foo}:2) | false
+ :rename | %w(foo foo bar) | false # This is not a valid command but should not raise here
+ :mget | %w(foo bar) | true
+ :mget | %w(foo foo bar) | true
+ :mget | %w(foo foo) | false
+ :blpop | %w(foo bar 1) | true
+ :blpop | %w(foo foo 1) | false
+ :mset | %w(foo a bar a) | true
+ :mset | %w(foo a foo a) | false
+ :del | %w(foo bar) | true
+ :del | [%w(foo bar)] | true # Arguments can be a nested array
+ :del | %w(foo foo) | false
+ :hset | %w(foo bar) | false # Not a multi-key command
+ end
+
+ with_them do
+ it do
+ args = [command] + arguments
+
+ if should_raise
+ expect { described_class.validate!(args) }
+ .to raise_error(described_class::CrossSlotError)
+ else
+ expect { described_class.validate!(args) }.not_to raise_error
+ end
+ end
+ end
+ end
+
+ describe '.allow_cross_slot_commands' do
+ it 'does not raise for invalid arguments' do
+ expect do
+ described_class.allow_cross_slot_commands do
+ described_class.validate!([:mget, 'foo', 'bar'])
+ end
+ end.not_to raise_error
+ end
+
+ it 'allows nested invocation' do
+ expect do
+ described_class.allow_cross_slot_commands do
+ described_class.allow_cross_slot_commands do
+ described_class.validate!([:mget, 'foo', 'bar'])
+ end
+
+ described_class.validate!([:mget, 'foo', 'bar'])
+ end
+ end.not_to raise_error
+ end
+ end
+end
diff --git a/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb b/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb
index 25506d63091..5b0ad63ee72 100644
--- a/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb
+++ b/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rspec-parameterized'
-describe Gitlab::Instrumentation::RedisInterceptor, :clean_gitlab_redis_shared_state, :request_store do
+RSpec.describe Gitlab::Instrumentation::RedisInterceptor, :clean_gitlab_redis_shared_state, :request_store do
using RSpec::Parameterized::TableSyntax
describe 'read and write' do
@@ -42,4 +42,73 @@ describe Gitlab::Instrumentation::RedisInterceptor, :clean_gitlab_redis_shared_s
end
end
end
+
+ describe 'counting' do
+ let(:instrumentation_class) { Gitlab::Redis::SharedState.instrumentation_class }
+
+ it 'counts successful requests' do
+ expect(instrumentation_class).to receive(:instance_count_request).and_call_original
+
+ Gitlab::Redis::SharedState.with { |redis| redis.call(:get, 'foobar') }
+ end
+
+ it 'counts exceptions' do
+ expect(instrumentation_class).to receive(:instance_count_exception)
+ .with(instance_of(Redis::CommandError)).and_call_original
+ expect(instrumentation_class).to receive(:instance_count_request).and_call_original
+
+ expect do
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.call(:auth, 'foo', 'bar')
+ end
+ end.to raise_exception(Redis::CommandError)
+ end
+ end
+
+ describe 'latency' do
+ let(:instrumentation_class) { Gitlab::Redis::SharedState.instrumentation_class }
+
+ describe 'commands in the apdex' do
+ where(:command) do
+ [
+ [[:get, 'foobar']],
+ [%w[GET foobar]]
+ ]
+ end
+
+ with_them do
+ it 'measures requests we want in the apdex' do
+ expect(instrumentation_class).to receive(:instance_observe_duration).with(a_value > 0)
+ .and_call_original
+
+ Gitlab::Redis::SharedState.with { |redis| redis.call(*command) }
+ end
+ end
+ end
+
+ describe 'commands not in the apdex' do
+ where(:command) do
+ [
+ [%w[brpop foobar 0.01]],
+ [%w[blpop foobar 0.01]],
+ [%w[brpoplpush foobar bazqux 0.01]],
+ [%w[bzpopmin foobar 0.01]],
+ [%w[bzpopmax foobar 0.01]],
+ [%w[xread block 1 streams mystream 0-0]],
+ [%w[xreadgroup group mygroup myconsumer block 1 streams foobar 0-0]]
+ ]
+ end
+
+ with_them do
+ it 'skips requests we do not want in the apdex' do
+ expect(instrumentation_class).not_to receive(:instance_observe_duration)
+
+ begin
+ Gitlab::Redis::SharedState.with { |redis| redis.call(*command) }
+ rescue Gitlab::Instrumentation::RedisClusterValidator::CrossSlotError, ::Redis::CommandError
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/instrumentation/redis_spec.rb b/spec/lib/gitlab/instrumentation/redis_spec.rb
index 8311c4f5bbb..e927f39cae2 100644
--- a/spec/lib/gitlab/instrumentation/redis_spec.rb
+++ b/spec/lib/gitlab/instrumentation/redis_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Instrumentation::Redis do
+RSpec.describe Gitlab::Instrumentation::Redis do
def stub_storages(method, value)
described_class::STORAGES.each do |storage|
allow(storage).to receive(method) { value }
diff --git a/spec/lib/gitlab/instrumentation_helper_spec.rb b/spec/lib/gitlab/instrumentation_helper_spec.rb
index 15d377a16fc..35bbdcdccd6 100644
--- a/spec/lib/gitlab/instrumentation_helper_spec.rb
+++ b/spec/lib/gitlab/instrumentation_helper_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rspec-parameterized'
-describe Gitlab::InstrumentationHelper do
+RSpec.describe Gitlab::InstrumentationHelper do
using RSpec::Parameterized::TableSyntax
describe '.keys' do
diff --git a/spec/lib/gitlab/internal_post_receive/response_spec.rb b/spec/lib/gitlab/internal_post_receive/response_spec.rb
index d90b85a41ed..135596c2de3 100644
--- a/spec/lib/gitlab/internal_post_receive/response_spec.rb
+++ b/spec/lib/gitlab/internal_post_receive/response_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::InternalPostReceive::Response do
+RSpec.describe Gitlab::InternalPostReceive::Response do
subject { described_class.new }
describe '#add_merge_request_urls' do
diff --git a/spec/lib/gitlab/issuable_metadata_spec.rb b/spec/lib/gitlab/issuable_metadata_spec.rb
index 1920cecfc29..3ba1d069bc9 100644
--- a/spec/lib/gitlab/issuable_metadata_spec.rb
+++ b/spec/lib/gitlab/issuable_metadata_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::IssuableMetadata do
+RSpec.describe Gitlab::IssuableMetadata do
let(:user) { create(:user) }
let!(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace) }
diff --git a/spec/lib/gitlab/issuable_sorter_spec.rb b/spec/lib/gitlab/issuable_sorter_spec.rb
index 486e9539b92..60f62062f04 100644
--- a/spec/lib/gitlab/issuable_sorter_spec.rb
+++ b/spec/lib/gitlab/issuable_sorter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::IssuableSorter do
+RSpec.describe Gitlab::IssuableSorter do
let(:namespace1) { build_stubbed(:namespace, id: 1) }
let(:project1) { build_stubbed(:project, id: 1, namespace: namespace1) }
diff --git a/spec/lib/gitlab/issuables_count_for_state_spec.rb b/spec/lib/gitlab/issuables_count_for_state_spec.rb
index 9380aa53470..1c186a8e6ca 100644
--- a/spec/lib/gitlab/issuables_count_for_state_spec.rb
+++ b/spec/lib/gitlab/issuables_count_for_state_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::IssuablesCountForState do
+RSpec.describe Gitlab::IssuablesCountForState do
let(:finder) do
double(:finder, count_by_state: { opened: 2, closed: 1 })
end
diff --git a/spec/lib/gitlab/jira_import/base_importer_spec.rb b/spec/lib/gitlab/jira_import/base_importer_spec.rb
index cda491393e8..1470bad2c4c 100644
--- a/spec/lib/gitlab/jira_import/base_importer_spec.rb
+++ b/spec/lib/gitlab/jira_import/base_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::BaseImporter do
+RSpec.describe Gitlab::JiraImport::BaseImporter do
include JiraServiceHelper
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/jira_import/handle_labels_service_spec.rb b/spec/lib/gitlab/jira_import/handle_labels_service_spec.rb
index 0eeff180575..4e2c5afb077 100644
--- a/spec/lib/gitlab/jira_import/handle_labels_service_spec.rb
+++ b/spec/lib/gitlab/jira_import/handle_labels_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::HandleLabelsService do
+RSpec.describe Gitlab::JiraImport::HandleLabelsService do
describe '#execute' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
diff --git a/spec/lib/gitlab/jira_import/issue_serializer_spec.rb b/spec/lib/gitlab/jira_import/issue_serializer_spec.rb
index ce38a1234cf..4adc4e4d22a 100644
--- a/spec/lib/gitlab/jira_import/issue_serializer_spec.rb
+++ b/spec/lib/gitlab/jira_import/issue_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::IssueSerializer do
+RSpec.describe Gitlab::JiraImport::IssueSerializer do
describe '#execute' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
@@ -10,6 +10,7 @@ describe Gitlab::JiraImport::IssueSerializer do
let_it_be(:other_project_label) { create(:label, project: project, title: 'feature') }
let_it_be(:group_label) { create(:group_label, group: group, title: 'dev') }
let_it_be(:current_user) { create(:user) }
+ let_it_be(:user) { create(:user) }
let(:iid) { 5 }
let(:key) { 'PROJECT-5' }
@@ -17,8 +18,8 @@ describe Gitlab::JiraImport::IssueSerializer do
let(:description) { 'basic description' }
let(:created_at) { '2020-01-01 20:00:00' }
let(:updated_at) { '2020-01-10 20:00:00' }
- let(:assignee) { double(attrs: { 'displayName' => 'Solver', 'emailAddress' => 'assignee@example.com' }) }
- let(:reporter) { double(attrs: { 'displayName' => 'Reporter', 'emailAddress' => 'reporter@example.com' }) }
+ let(:assignee) { nil }
+ let(:reporter) { nil }
let(:jira_status) { 'new' }
let(:parent_field) do
@@ -109,11 +110,12 @@ describe Gitlab::JiraImport::IssueSerializer do
end
context 'author' do
- context 'when reporter maps to a valid GitLab user' do
- let!(:user) { create(:user, email: 'reporter@example.com') }
+ let(:reporter) { double(attrs: { 'displayName' => 'Solver', 'accountId' => 'abcd' }) }
+ context 'when reporter maps to a valid GitLab user' do
it 'sets the issue author to the mapped user' do
- project.add_developer(user)
+ expect(Gitlab::JiraImport).to receive(:get_user_mapping).with(project.id, 'abcd')
+ .and_return(user.id)
expect(subject[:author_id]).to eq(user.id)
end
@@ -121,6 +123,9 @@ describe Gitlab::JiraImport::IssueSerializer do
context 'when reporter does not map to a valid Gitlab user' do
it 'defaults the issue author to project creator' do
+ expect(Gitlab::JiraImport).to receive(:get_user_mapping).with(project.id, 'abcd')
+ .and_return(nil)
+
expect(subject[:author_id]).to eq(current_user.id)
end
end
@@ -129,25 +134,30 @@ describe Gitlab::JiraImport::IssueSerializer do
let(:reporter) { nil }
it 'defaults the issue author to project creator' do
+ expect(Gitlab::JiraImport).not_to receive(:get_user_mapping)
+
expect(subject[:author_id]).to eq(current_user.id)
end
end
- context 'when reporter field is missing email address' do
+ context 'when reporter field is missing accountId' do
let(:reporter) { double(attrs: { 'displayName' => 'Reporter' }) }
it 'defaults the issue author to project creator' do
+ expect(Gitlab::JiraImport).not_to receive(:get_user_mapping)
+
expect(subject[:author_id]).to eq(current_user.id)
end
end
end
context 'assignee' do
- context 'when assignee maps to a valid GitLab user' do
- let!(:user) { create(:user, email: 'assignee@example.com') }
+ let(:assignee) { double(attrs: { 'displayName' => 'Solver', 'accountId' => '1234' }) }
+ context 'when assignee maps to a valid GitLab user' do
it 'sets the issue assignees to the mapped user' do
- project.add_developer(user)
+ expect(Gitlab::JiraImport).to receive(:get_user_mapping).with(project.id, '1234')
+ .and_return(user.id)
expect(subject[:assignee_ids]).to eq([user.id])
end
@@ -155,6 +165,9 @@ describe Gitlab::JiraImport::IssueSerializer do
context 'when assignee does not map to a valid GitLab user' do
it 'leaves the assignee empty' do
+ expect(Gitlab::JiraImport).to receive(:get_user_mapping).with(project.id, '1234')
+ .and_return(nil)
+
expect(subject[:assignee_ids]).to be_nil
end
end
@@ -163,14 +176,18 @@ describe Gitlab::JiraImport::IssueSerializer do
let(:assignee) { nil }
it 'leaves the assignee empty' do
+ expect(Gitlab::JiraImport).not_to receive(:get_user_mapping)
+
expect(subject[:assignee_ids]).to be_nil
end
end
- context 'when assginee field is missing email address' do
- let(:assignee) { double(attrs: { 'displayName' => 'Reporter' }) }
+ context 'when assginee field is missing accountId' do
+ let(:assignee) { double(attrs: { 'displayName' => 'Solver' }) }
it 'leaves the assignee empty' do
+ expect(Gitlab::JiraImport).not_to receive(:get_user_mapping)
+
expect(subject[:assignee_ids]).to be_nil
end
end
diff --git a/spec/lib/gitlab/jira_import/issues_importer_spec.rb b/spec/lib/gitlab/jira_import/issues_importer_spec.rb
index 0d790f49450..4a32f0fd3a9 100644
--- a/spec/lib/gitlab/jira_import/issues_importer_spec.rb
+++ b/spec/lib/gitlab/jira_import/issues_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::IssuesImporter do
+RSpec.describe Gitlab::JiraImport::IssuesImporter do
include JiraServiceHelper
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/jira_import/labels_importer_spec.rb b/spec/lib/gitlab/jira_import/labels_importer_spec.rb
index 19661ff4e73..db98a83cb3c 100644
--- a/spec/lib/gitlab/jira_import/labels_importer_spec.rb
+++ b/spec/lib/gitlab/jira_import/labels_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::LabelsImporter do
+RSpec.describe Gitlab::JiraImport::LabelsImporter do
include JiraServiceHelper
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/jira_import/metadata_collector_spec.rb b/spec/lib/gitlab/jira_import/metadata_collector_spec.rb
index af479810df0..86863d67f25 100644
--- a/spec/lib/gitlab/jira_import/metadata_collector_spec.rb
+++ b/spec/lib/gitlab/jira_import/metadata_collector_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::MetadataCollector do
+RSpec.describe Gitlab::JiraImport::MetadataCollector do
describe '#execute' do
let(:key) { 'PROJECT-5' }
let(:summary) { 'some title' }
diff --git a/spec/lib/gitlab/jira_import/user_mapper_spec.rb b/spec/lib/gitlab/jira_import/user_mapper_spec.rb
deleted file mode 100644
index c8c8bd3c5b0..00000000000
--- a/spec/lib/gitlab/jira_import/user_mapper_spec.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::JiraImport::UserMapper do
- let_it_be(:group) { create(:group) }
- let_it_be(:project) { create(:project, group: group) }
- let_it_be(:user) { create(:user, email: 'user@example.com') }
- let_it_be(:email) { create(:email, user: user, email: 'second_email@example.com', confirmed_at: nil) }
-
- let(:jira_user) { { 'acountId' => '1a2b', 'emailAddress' => 'user@example.com' } }
-
- describe '#execute' do
- subject { described_class.new(project, jira_user).execute }
-
- context 'when jira_user is nil' do
- let(:jira_user) { nil }
-
- it 'returns nil' do
- expect(subject).to be_nil
- end
- end
-
- context 'when Gitlab user is not found by email' do
- let(:jira_user) { { 'acountId' => '1a2b', 'emailAddress' => 'other@example.com' } }
-
- it 'returns nil' do
- expect(subject).to be_nil
- end
- end
-
- context 'when jira_user emailAddress is nil' do
- let(:jira_user) { { 'acountId' => '1a2b', 'emailAddress' => nil } }
-
- it 'returns nil' do
- expect(subject).to be_nil
- end
- end
-
- context 'when jira_user emailAddress key is missing' do
- let(:jira_user) { { 'acountId' => '1a2b' } }
-
- it 'returns nil' do
- expect(subject).to be_nil
- end
- end
-
- context 'when found user is not a project member' do
- it 'returns nil' do
- expect(subject).to be_nil
- end
- end
-
- context 'when found user is a project member' do
- it 'returns the found user' do
- project.add_developer(user)
-
- expect(subject).to eq(user)
- end
- end
-
- context 'when user found by unconfirmd secondary address is a project member' do
- let(:jira_user) { { 'acountId' => '1a2b', 'emailAddress' => 'second_email@example.com' } }
-
- it 'returns the found user' do
- project.add_developer(user)
-
- expect(subject).to eq(user)
- end
- end
-
- context 'when user is a group member' do
- it 'returns the found user' do
- group.add_developer(user)
-
- expect(subject).to eq(user)
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/jira_import_spec.rb b/spec/lib/gitlab/jira_import_spec.rb
index 5b95891c97e..c8cecb576da 100644
--- a/spec/lib/gitlab/jira_import_spec.rb
+++ b/spec/lib/gitlab/jira_import_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport do
+RSpec.describe Gitlab::JiraImport do
let(:project_id) { 321 }
describe '.validate_project_settings!' do
diff --git a/spec/lib/gitlab/job_waiter_spec.rb b/spec/lib/gitlab/job_waiter_spec.rb
index da6a6a9149b..4d7c838aa3b 100644
--- a/spec/lib/gitlab/job_waiter_spec.rb
+++ b/spec/lib/gitlab/job_waiter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JobWaiter do
+RSpec.describe Gitlab::JobWaiter do
describe '.notify' do
it 'pushes the jid to the named queue' do
key = 'gitlab:job_waiter:foo'
diff --git a/spec/lib/gitlab/json_cache_spec.rb b/spec/lib/gitlab/json_cache_spec.rb
index 9379499f602..563b3d35823 100644
--- a/spec/lib/gitlab/json_cache_spec.rb
+++ b/spec/lib/gitlab/json_cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JsonCache do
+RSpec.describe Gitlab::JsonCache do
let_it_be(:broadcast_message) { create(:broadcast_message) }
let(:backend) { double('backend').as_null_object }
let(:namespace) { 'geo' }
diff --git a/spec/lib/gitlab/json_logger_spec.rb b/spec/lib/gitlab/json_logger_spec.rb
index 41dafc84ef2..23f7191454a 100644
--- a/spec/lib/gitlab/json_logger_spec.rb
+++ b/spec/lib/gitlab/json_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JsonLogger do
+RSpec.describe Gitlab::JsonLogger do
subject { described_class.new('/dev/null') }
let(:now) { Time.now }
diff --git a/spec/lib/gitlab/json_spec.rb b/spec/lib/gitlab/json_spec.rb
index ee7c98a5a54..d7671dda323 100644
--- a/spec/lib/gitlab/json_spec.rb
+++ b/spec/lib/gitlab/json_spec.rb
@@ -7,189 +7,404 @@ RSpec.describe Gitlab::Json do
stub_feature_flags(json_wrapper_legacy_mode: true)
end
- describe ".parse" do
- context "legacy_mode is disabled by default" do
- it "parses an object" do
- expect(subject.parse('{ "foo": "bar" }')).to eq({ "foo" => "bar" })
- end
+ shared_examples "json" do
+ describe ".parse" do
+ context "legacy_mode is disabled by default" do
+ it "parses an object" do
+ expect(subject.parse('{ "foo": "bar" }')).to eq({ "foo" => "bar" })
+ end
- it "parses an array" do
- expect(subject.parse('[{ "foo": "bar" }]')).to eq([{ "foo" => "bar" }])
- end
+ it "parses an array" do
+ expect(subject.parse('[{ "foo": "bar" }]')).to eq([{ "foo" => "bar" }])
+ end
- it "parses a string" do
- expect(subject.parse('"foo"', legacy_mode: false)).to eq("foo")
- end
+ it "parses a string" do
+ expect(subject.parse('"foo"', legacy_mode: false)).to eq("foo")
+ end
- it "parses a true bool" do
- expect(subject.parse("true", legacy_mode: false)).to be(true)
- end
+ it "parses a true bool" do
+ expect(subject.parse("true", legacy_mode: false)).to be(true)
+ end
- it "parses a false bool" do
- expect(subject.parse("false", legacy_mode: false)).to be(false)
+ it "parses a false bool" do
+ expect(subject.parse("false", legacy_mode: false)).to be(false)
+ end
end
- end
- context "legacy_mode is enabled" do
- it "parses an object" do
- expect(subject.parse('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
- end
+ context "legacy_mode is enabled" do
+ it "parses an object" do
+ expect(subject.parse('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
+ end
- it "parses an array" do
- expect(subject.parse('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
- end
+ it "parses an array" do
+ expect(subject.parse('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
+ end
- it "raises an error on a string" do
- expect { subject.parse('"foo"', legacy_mode: true) }.to raise_error(JSON::ParserError)
- end
+ it "raises an error on a string" do
+ expect { subject.parse('"foo"', legacy_mode: true) }.to raise_error(JSON::ParserError)
+ end
- it "raises an error on a true bool" do
- expect { subject.parse("true", legacy_mode: true) }.to raise_error(JSON::ParserError)
+ it "raises an error on a true bool" do
+ expect { subject.parse("true", legacy_mode: true) }.to raise_error(JSON::ParserError)
+ end
+
+ it "raises an error on a false bool" do
+ expect { subject.parse("false", legacy_mode: true) }.to raise_error(JSON::ParserError)
+ end
end
- it "raises an error on a false bool" do
- expect { subject.parse("false", legacy_mode: true) }.to raise_error(JSON::ParserError)
+ context "feature flag is disabled" do
+ before do
+ stub_feature_flags(json_wrapper_legacy_mode: false)
+ end
+
+ it "parses an object" do
+ expect(subject.parse('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
+ end
+
+ it "parses an array" do
+ expect(subject.parse('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
+ end
+
+ it "parses a string" do
+ expect(subject.parse('"foo"', legacy_mode: true)).to eq("foo")
+ end
+
+ it "parses a true bool" do
+ expect(subject.parse("true", legacy_mode: true)).to be(true)
+ end
+
+ it "parses a false bool" do
+ expect(subject.parse("false", legacy_mode: true)).to be(false)
+ end
end
end
- context "feature flag is disabled" do
- before do
- stub_feature_flags(json_wrapper_legacy_mode: false)
- end
+ describe ".parse!" do
+ context "legacy_mode is disabled by default" do
+ it "parses an object" do
+ expect(subject.parse!('{ "foo": "bar" }')).to eq({ "foo" => "bar" })
+ end
- it "parses an object" do
- expect(subject.parse('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
- end
+ it "parses an array" do
+ expect(subject.parse!('[{ "foo": "bar" }]')).to eq([{ "foo" => "bar" }])
+ end
- it "parses an array" do
- expect(subject.parse('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
- end
+ it "parses a string" do
+ expect(subject.parse!('"foo"', legacy_mode: false)).to eq("foo")
+ end
- it "parses a string" do
- expect(subject.parse('"foo"', legacy_mode: true)).to eq("foo")
+ it "parses a true bool" do
+ expect(subject.parse!("true", legacy_mode: false)).to be(true)
+ end
+
+ it "parses a false bool" do
+ expect(subject.parse!("false", legacy_mode: false)).to be(false)
+ end
end
- it "parses a true bool" do
- expect(subject.parse("true", legacy_mode: true)).to be(true)
+ context "legacy_mode is enabled" do
+ it "parses an object" do
+ expect(subject.parse!('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
+ end
+
+ it "parses an array" do
+ expect(subject.parse!('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
+ end
+
+ it "raises an error on a string" do
+ expect { subject.parse!('"foo"', legacy_mode: true) }.to raise_error(JSON::ParserError)
+ end
+
+ it "raises an error on a true bool" do
+ expect { subject.parse!("true", legacy_mode: true) }.to raise_error(JSON::ParserError)
+ end
+
+ it "raises an error on a false bool" do
+ expect { subject.parse!("false", legacy_mode: true) }.to raise_error(JSON::ParserError)
+ end
end
- it "parses a false bool" do
- expect(subject.parse("false", legacy_mode: true)).to be(false)
+ context "feature flag is disabled" do
+ before do
+ stub_feature_flags(json_wrapper_legacy_mode: false)
+ end
+
+ it "parses an object" do
+ expect(subject.parse!('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
+ end
+
+ it "parses an array" do
+ expect(subject.parse!('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
+ end
+
+ it "parses a string" do
+ expect(subject.parse!('"foo"', legacy_mode: true)).to eq("foo")
+ end
+
+ it "parses a true bool" do
+ expect(subject.parse!("true", legacy_mode: true)).to be(true)
+ end
+
+ it "parses a false bool" do
+ expect(subject.parse!("false", legacy_mode: true)).to be(false)
+ end
end
end
- end
- describe ".parse!" do
- context "legacy_mode is disabled by default" do
- it "parses an object" do
- expect(subject.parse!('{ "foo": "bar" }')).to eq({ "foo" => "bar" })
+ describe ".dump" do
+ it "dumps an object" do
+ expect(subject.dump({ "foo" => "bar" })).to eq('{"foo":"bar"}')
end
- it "parses an array" do
- expect(subject.parse!('[{ "foo": "bar" }]')).to eq([{ "foo" => "bar" }])
+ it "dumps an array" do
+ expect(subject.dump([{ "foo" => "bar" }])).to eq('[{"foo":"bar"}]')
end
- it "parses a string" do
- expect(subject.parse!('"foo"', legacy_mode: false)).to eq("foo")
+ it "dumps a string" do
+ expect(subject.dump("foo")).to eq('"foo"')
end
- it "parses a true bool" do
- expect(subject.parse!("true", legacy_mode: false)).to be(true)
+ it "dumps a true bool" do
+ expect(subject.dump(true)).to eq("true")
end
- it "parses a false bool" do
- expect(subject.parse!("false", legacy_mode: false)).to be(false)
+ it "dumps a false bool" do
+ expect(subject.dump(false)).to eq("false")
end
end
- context "legacy_mode is enabled" do
- it "parses an object" do
- expect(subject.parse!('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
+ describe ".generate" do
+ let(:obj) do
+ { test: true, "foo.bar" => "baz", is_json: 1, some: [1, 2, 3] }
end
- it "parses an array" do
- expect(subject.parse!('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
- end
+ it "generates JSON" do
+ expected_string = <<~STR.chomp
+ {"test":true,"foo.bar":"baz","is_json":1,"some":[1,2,3]}
+ STR
- it "raises an error on a string" do
- expect { subject.parse!('"foo"', legacy_mode: true) }.to raise_error(JSON::ParserError)
+ expect(subject.generate(obj)).to eq(expected_string)
end
- it "raises an error on a true bool" do
- expect { subject.parse!("true", legacy_mode: true) }.to raise_error(JSON::ParserError)
+ it "allows you to customise the output" do
+ opts = {
+ indent: " ",
+ space: " ",
+ space_before: " ",
+ object_nl: "\n",
+ array_nl: "\n"
+ }
+
+ json = subject.generate(obj, opts)
+
+ expected_string = <<~STR.chomp
+ {
+ "test" : true,
+ "foo.bar" : "baz",
+ "is_json" : 1,
+ "some" : [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ STR
+
+ expect(json).to eq(expected_string)
end
+ end
- it "raises an error on a false bool" do
- expect { subject.parse!("false", legacy_mode: true) }.to raise_error(JSON::ParserError)
+ describe ".pretty_generate" do
+ let(:obj) do
+ {
+ test: true,
+ "foo.bar" => "baz",
+ is_json: 1,
+ some: [1, 2, 3],
+ more: { test: true },
+ multi_line_empty_array: [],
+ multi_line_empty_obj: {}
+ }
+ end
+
+ it "generates pretty JSON" do
+ expected_string = <<~STR.chomp
+ {
+ "test": true,
+ "foo.bar": "baz",
+ "is_json": 1,
+ "some": [
+ 1,
+ 2,
+ 3
+ ],
+ "more": {
+ "test": true
+ },
+ "multi_line_empty_array": [
+
+ ],
+ "multi_line_empty_obj": {
+ }
+ }
+ STR
+
+ expect(subject.pretty_generate(obj)).to eq(expected_string)
+ end
+
+ it "allows you to customise the output" do
+ opts = {
+ space_before: " "
+ }
+
+ json = subject.pretty_generate(obj, opts)
+
+ expected_string = <<~STR.chomp
+ {
+ "test" : true,
+ "foo.bar" : "baz",
+ "is_json" : 1,
+ "some" : [
+ 1,
+ 2,
+ 3
+ ],
+ "more" : {
+ "test" : true
+ },
+ "multi_line_empty_array" : [
+
+ ],
+ "multi_line_empty_obj" : {
+ }
+ }
+ STR
+
+ expect(json).to eq(expected_string)
end
end
- context "feature flag is disabled" do
+ context "the feature table is missing" do
before do
- stub_feature_flags(json_wrapper_legacy_mode: false)
+ allow(Feature::FlipperFeature).to receive(:table_exists?).and_return(false)
end
- it "parses an object" do
- expect(subject.parse!('{ "foo": "bar" }', legacy_mode: true)).to eq({ "foo" => "bar" })
+ it "skips legacy mode handling" do
+ expect(Feature).not_to receive(:enabled?).with(:json_wrapper_legacy_mode, default_enabled: true)
+
+ subject.send(:handle_legacy_mode!, {})
end
- it "parses an array" do
- expect(subject.parse!('[{ "foo": "bar" }]', legacy_mode: true)).to eq([{ "foo" => "bar" }])
+ it "skips oj feature detection" do
+ expect(Feature).not_to receive(:enabled?).with(:oj_json, default_enabled: true)
+
+ subject.send(:enable_oj?)
end
+ end
- it "parses a string" do
- expect(subject.parse!('"foo"', legacy_mode: true)).to eq("foo")
+ context "the database is missing" do
+ before do
+ allow(Feature::FlipperFeature).to receive(:table_exists?).and_raise(PG::ConnectionBad)
end
- it "parses a true bool" do
- expect(subject.parse!("true", legacy_mode: true)).to be(true)
+ it "still parses json" do
+ expect(subject.parse("{}")).to eq({})
end
- it "parses a false bool" do
- expect(subject.parse!("false", legacy_mode: true)).to be(false)
+ it "still generates json" do
+ expect(subject.dump({})).to eq("{}")
end
end
end
- describe ".dump" do
- it "dumps an object" do
- expect(subject.dump({ "foo" => "bar" })).to eq('{"foo":"bar"}')
+ context "oj gem" do
+ before do
+ stub_feature_flags(oj_json: true)
end
- it "dumps an array" do
- expect(subject.dump([{ "foo" => "bar" }])).to eq('[{"foo":"bar"}]')
- end
+ it_behaves_like "json"
- it "dumps a string" do
- expect(subject.dump("foo")).to eq('"foo"')
+ describe "#enable_oj?" do
+ it "returns true" do
+ expect(subject.enable_oj?).to be(true)
+ end
end
+ end
- it "dumps a true bool" do
- expect(subject.dump(true)).to eq("true")
+ context "json gem" do
+ before do
+ stub_feature_flags(oj_json: false)
end
- it "dumps a false bool" do
- expect(subject.dump(false)).to eq("false")
+ it_behaves_like "json"
+
+ describe "#enable_oj?" do
+ it "returns false" do
+ expect(subject.enable_oj?).to be(false)
+ end
end
end
- describe ".generate" do
- it "delegates to the adapter" do
- args = [{ foo: "bar" }]
+ describe Gitlab::Json::GrapeFormatter do
+ subject { described_class.call(obj, env) }
+
+ let(:obj) { { test: true } }
+ let(:env) { {} }
+ let(:result) { "{\"test\":true}" }
+
+ context "oj is enabled" do
+ before do
+ stub_feature_flags(oj_json: true)
+ end
+
+ context "grape_gitlab_json flag is enabled" do
+ before do
+ stub_feature_flags(grape_gitlab_json: true)
+ end
+
+ it "generates JSON" do
+ expect(subject).to eq(result)
+ end
+
+ it "uses Gitlab::Json" do
+ expect(Gitlab::Json).to receive(:dump).with(obj)
+
+ subject
+ end
+ end
+
+ context "grape_gitlab_json flag is disabled" do
+ before do
+ stub_feature_flags(grape_gitlab_json: false)
+ end
+
+ it "generates JSON" do
+ expect(subject).to eq(result)
+ end
- expect(JSON).to receive(:generate).with(*args)
+ it "uses Grape::Formatter::Json" do
+ expect(Grape::Formatter::Json).to receive(:call).with(obj, env)
- subject.generate(*args)
+ subject
+ end
+ end
end
- end
- describe ".pretty_generate" do
- it "delegates to the adapter" do
- args = [{ foo: "bar" }]
+ context "oj is disabled" do
+ before do
+ stub_feature_flags(oj_json: false)
+ end
- expect(JSON).to receive(:pretty_generate).with(*args)
+ it "generates JSON" do
+ expect(subject).to eq(result)
+ end
+
+ it "uses Grape::Formatter::Json" do
+ expect(Grape::Formatter::Json).to receive(:call).with(obj, env)
- subject.pretty_generate(*args)
+ subject
+ end
end
end
end
diff --git a/spec/lib/gitlab/jwt_authenticatable_spec.rb b/spec/lib/gitlab/jwt_authenticatable_spec.rb
index 0c1c491b308..36bb46cb250 100644
--- a/spec/lib/gitlab/jwt_authenticatable_spec.rb
+++ b/spec/lib/gitlab/jwt_authenticatable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JwtAuthenticatable do
+RSpec.describe Gitlab::JwtAuthenticatable do
let(:test_class) do
Class.new do
include Gitlab::JwtAuthenticatable
diff --git a/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb b/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb
index e1106f7496a..02729d53da8 100644
--- a/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb
+++ b/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::ClusterRoleBinding do
+RSpec.describe Gitlab::Kubernetes::ClusterRoleBinding do
let(:cluster_role_binding) { described_class.new(name, cluster_role_name, subjects) }
let(:name) { 'cluster-role-binding-name' }
let(:cluster_role_name) { 'cluster-admin' }
diff --git a/spec/lib/gitlab/kubernetes/config_map_spec.rb b/spec/lib/gitlab/kubernetes/config_map_spec.rb
index 0203772e069..2d0d205ffb1 100644
--- a/spec/lib/gitlab/kubernetes/config_map_spec.rb
+++ b/spec/lib/gitlab/kubernetes/config_map_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::ConfigMap do
+RSpec.describe Gitlab::Kubernetes::ConfigMap do
let(:kubeclient) { double('kubernetes client') }
let(:application) { create(:clusters_applications_prometheus) }
let(:config_map) { described_class.new(application.name, application.files) }
diff --git a/spec/lib/gitlab/kubernetes/config_maps/aws_node_auth_spec.rb b/spec/lib/gitlab/kubernetes/config_maps/aws_node_auth_spec.rb
index f701643860a..9c3e41dfbff 100644
--- a/spec/lib/gitlab/kubernetes/config_maps/aws_node_auth_spec.rb
+++ b/spec/lib/gitlab/kubernetes/config_maps/aws_node_auth_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::ConfigMaps::AwsNodeAuth do
+RSpec.describe Gitlab::Kubernetes::ConfigMaps::AwsNodeAuth do
describe '#generate' do
let(:role) { 'arn:aws:iam::123456789012:role/node-instance-role' }
diff --git a/spec/lib/gitlab/kubernetes/default_namespace_spec.rb b/spec/lib/gitlab/kubernetes/default_namespace_spec.rb
index 1fda547f35c..976fe4a0a87 100644
--- a/spec/lib/gitlab/kubernetes/default_namespace_spec.rb
+++ b/spec/lib/gitlab/kubernetes/default_namespace_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::DefaultNamespace do
+RSpec.describe Gitlab::Kubernetes::DefaultNamespace do
let(:generator) { described_class.new(cluster, project: environment.project) }
describe '#from_environment_name' do
diff --git a/spec/lib/gitlab/kubernetes/generic_secret_spec.rb b/spec/lib/gitlab/kubernetes/generic_secret_spec.rb
index fe1d4cc11e6..8b56d76410e 100644
--- a/spec/lib/gitlab/kubernetes/generic_secret_spec.rb
+++ b/spec/lib/gitlab/kubernetes/generic_secret_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::GenericSecret do
+RSpec.describe Gitlab::Kubernetes::GenericSecret do
let(:secret) { described_class.new(name, data, namespace) }
let(:name) { 'example-name' }
let(:data) { 'example-data' }
diff --git a/spec/lib/gitlab/kubernetes/helm/api_spec.rb b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
index 0e4179d5887..dabbab27b13 100644
--- a/spec/lib/gitlab/kubernetes/helm/api_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::Helm::API do
+RSpec.describe Gitlab::Kubernetes::Helm::API do
let(:client) { double('kubernetes client') }
let(:helm) { described_class.new(client) }
let(:gitlab_namespace) { Gitlab::Kubernetes::Helm::NAMESPACE }
diff --git a/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb
index f9bcb8abdb1..57fafaebf98 100644
--- a/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::Helm::BaseCommand do
+RSpec.describe Gitlab::Kubernetes::Helm::BaseCommand do
subject(:base_command) do
test_class.new(rbac)
end
diff --git a/spec/lib/gitlab/kubernetes/helm/certificate_spec.rb b/spec/lib/gitlab/kubernetes/helm/certificate_spec.rb
index 04649353976..b446c5e1149 100644
--- a/spec/lib/gitlab/kubernetes/helm/certificate_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/certificate_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::Kubernetes::Helm::Certificate do
+RSpec.describe Gitlab::Kubernetes::Helm::Certificate do
describe '.generate_root' do
subject { described_class.generate_root }
diff --git a/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb
index 2bf8b294821..7b182478cc3 100644
--- a/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::Helm::DeleteCommand do
+RSpec.describe Gitlab::Kubernetes::Helm::DeleteCommand do
subject(:delete_command) { described_class.new(name: app_name, rbac: rbac, files: files, local_tiller_enabled: local_tiller_enabled) }
let(:app_name) { 'app-name' }
diff --git a/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb
index 61b8eb30b42..c982a417682 100644
--- a/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::Helm::InitCommand do
+RSpec.describe Gitlab::Kubernetes::Helm::InitCommand do
subject(:init_command) { described_class.new(name: application.name, files: files, rbac: rbac, local_tiller_enabled: false) }
let(:application) { create(:clusters_applications_helm) }
diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
index 6fc91300f5b..aad350256ec 100644
--- a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::Helm::InstallCommand do
+RSpec.describe Gitlab::Kubernetes::Helm::InstallCommand do
subject(:install_command) do
described_class.new(
name: 'app-name',
diff --git a/spec/lib/gitlab/kubernetes/helm/parsers/list_v2_spec.rb b/spec/lib/gitlab/kubernetes/helm/parsers/list_v2_spec.rb
index 0ad5dc189c0..435c296d5f1 100644
--- a/spec/lib/gitlab/kubernetes/helm/parsers/list_v2_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/parsers/list_v2_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Kubernetes::Helm::Parsers::ListV2 do
+RSpec.describe Gitlab::Kubernetes::Helm::Parsers::ListV2 do
let(:valid_file_contents) do
<<~EOF
{
diff --git a/spec/lib/gitlab/kubernetes/helm/patch_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/patch_command_spec.rb
index 8d965a25f84..ea2ade18e37 100644
--- a/spec/lib/gitlab/kubernetes/helm/patch_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/patch_command_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::Helm::PatchCommand do
+RSpec.describe Gitlab::Kubernetes::Helm::PatchCommand do
let(:files) { { 'ca.pem': 'some file content' } }
let(:repository) { 'https://repository.example.com' }
let(:rbac) { false }
diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
index ea32ac96213..54e3289dd25 100644
--- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::Helm::Pod do
+RSpec.describe Gitlab::Kubernetes::Helm::Pod do
describe '#generate' do
let(:app) { create(:clusters_applications_prometheus) }
let(:command) { app.install_command }
@@ -32,7 +32,7 @@ describe Gitlab::Kubernetes::Helm::Pod do
it 'generates the appropriate specifications for the container' do
container = subject.generate.spec.containers.first
expect(container.name).to eq('helm')
- expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.16.6-kube-1.13.12')
+ expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.16.9-kube-1.13.12')
expect(container.env.count).to eq(3)
expect(container.env.map(&:name)).to match_array([:HELM_VERSION, :TILLER_NAMESPACE, :COMMAND_SCRIPT])
expect(container.command).to match_array(["/bin/sh"])
diff --git a/spec/lib/gitlab/kubernetes/helm/reset_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/reset_command_spec.rb
index 3773c428713..8d386d41ad5 100644
--- a/spec/lib/gitlab/kubernetes/helm/reset_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/reset_command_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::Helm::ResetCommand do
+RSpec.describe Gitlab::Kubernetes::Helm::ResetCommand do
subject(:reset_command) { described_class.new(name: name, rbac: rbac, files: files, local_tiller_enabled: false) }
let(:rbac) { true }
diff --git a/spec/lib/gitlab/kubernetes/kube_client_spec.rb b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
index 32597aa4f5a..a15be42f393 100644
--- a/spec/lib/gitlab/kubernetes/kube_client_spec.rb
+++ b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::KubeClient do
+RSpec.describe Gitlab::Kubernetes::KubeClient do
include StubRequests
include KubernetesHelpers
diff --git a/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb b/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb
index b65d7b9fdc6..e80bb3dfb07 100644
--- a/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb
+++ b/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Kubernetes::KubectlCmd do
+RSpec.describe Gitlab::Kubernetes::KubectlCmd do
describe '.delete' do
it 'constructs string properly' do
args = %w(resource_type type --flag-1 --flag-2)
diff --git a/spec/lib/gitlab/kubernetes/namespace_spec.rb b/spec/lib/gitlab/kubernetes/namespace_spec.rb
index 467b10e21b1..5fe4e008469 100644
--- a/spec/lib/gitlab/kubernetes/namespace_spec.rb
+++ b/spec/lib/gitlab/kubernetes/namespace_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::Namespace do
+RSpec.describe Gitlab::Kubernetes::Namespace do
let(:name) { 'a_namespace' }
let(:client) { double('kubernetes client') }
let(:labels) { nil }
diff --git a/spec/lib/gitlab/kubernetes/network_policy_spec.rb b/spec/lib/gitlab/kubernetes/network_policy_spec.rb
index 5a920d78436..a8ca15f998b 100644
--- a/spec/lib/gitlab/kubernetes/network_policy_spec.rb
+++ b/spec/lib/gitlab/kubernetes/network_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::NetworkPolicy do
+RSpec.describe Gitlab::Kubernetes::NetworkPolicy do
let(:policy) do
described_class.new(
name: name,
diff --git a/spec/lib/gitlab/kubernetes/node_spec.rb b/spec/lib/gitlab/kubernetes/node_spec.rb
new file mode 100644
index 00000000000..732bf29bc44
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/node_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Kubernetes::Node do
+ include KubernetesHelpers
+
+ describe '#all' do
+ let(:cluster) { create(:cluster, :provided_by_user, :group) }
+ let(:expected_nodes) { [] }
+
+ before do
+ stub_kubeclient_nodes_and_nodes_metrics(cluster.platform.api_url)
+ end
+
+ subject { described_class.new(cluster).all }
+
+ context 'when connection to the cluster is successful' do
+ let(:expected_nodes) { [kube_node.merge(kube_node_metrics)] }
+
+ it { is_expected.to eq(expected_nodes) }
+ end
+
+ context 'when cluster cannot be reached' do
+ before do
+ allow(cluster.kubeclient.core_client).to receive(:discover)
+ .and_raise(SocketError)
+ end
+
+ it { is_expected.to eq(expected_nodes) }
+ end
+
+ context 'when cluster cannot be authenticated to' do
+ before do
+ allow(cluster.kubeclient.core_client).to receive(:discover)
+ .and_raise(OpenSSL::X509::CertificateError.new('Certificate error'))
+ end
+
+ it { is_expected.to eq(expected_nodes) }
+ end
+
+ context 'when Kubeclient::HttpError is raised' do
+ before do
+ allow(cluster.kubeclient.core_client).to receive(:discover)
+ .and_raise(Kubeclient::HttpError.new(403, 'Forbidden', nil))
+ end
+
+ it { is_expected.to eq(expected_nodes) }
+ end
+
+ context 'when an uncategorised error is raised' do
+ before do
+ allow(cluster.kubeclient.core_client).to receive(:discover)
+ .and_raise(StandardError)
+ end
+
+ it { is_expected.to eq(expected_nodes) }
+
+ it 'notifies Sentry' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception)
+ .with(instance_of(StandardError), hash_including(cluster_id: cluster.id))
+ .once
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes/role_binding_spec.rb b/spec/lib/gitlab/kubernetes/role_binding_spec.rb
index 4c200eb545f..3003fe25301 100644
--- a/spec/lib/gitlab/kubernetes/role_binding_spec.rb
+++ b/spec/lib/gitlab/kubernetes/role_binding_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::RoleBinding, '#generate' do
+RSpec.describe Gitlab::Kubernetes::RoleBinding, '#generate' do
let(:role_name) { 'edit' }
let(:role_kind) { 'ClusterRole' }
let(:namespace) { 'my-namespace' }
diff --git a/spec/lib/gitlab/kubernetes/role_spec.rb b/spec/lib/gitlab/kubernetes/role_spec.rb
index 3a5cd3b6704..acb9b5d4e8e 100644
--- a/spec/lib/gitlab/kubernetes/role_spec.rb
+++ b/spec/lib/gitlab/kubernetes/role_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::Role do
+RSpec.describe Gitlab::Kubernetes::Role do
let(:role) { described_class.new(name: name, namespace: namespace, rules: rules) }
let(:name) { 'example-name' }
let(:namespace) { 'example-namespace' }
diff --git a/spec/lib/gitlab/kubernetes/service_account_spec.rb b/spec/lib/gitlab/kubernetes/service_account_spec.rb
index 0d525966d18..998d1a6d954 100644
--- a/spec/lib/gitlab/kubernetes/service_account_spec.rb
+++ b/spec/lib/gitlab/kubernetes/service_account_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::ServiceAccount do
+RSpec.describe Gitlab::Kubernetes::ServiceAccount do
let(:name) { 'a_service_account' }
let(:namespace_name) { 'a_namespace' }
let(:service_account) { described_class.new(name, namespace_name) }
diff --git a/spec/lib/gitlab/kubernetes/service_account_token_spec.rb b/spec/lib/gitlab/kubernetes/service_account_token_spec.rb
index 0d334bed45f..18423fdb2a3 100644
--- a/spec/lib/gitlab/kubernetes/service_account_token_spec.rb
+++ b/spec/lib/gitlab/kubernetes/service_account_token_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::ServiceAccountToken do
+RSpec.describe Gitlab::Kubernetes::ServiceAccountToken do
let(:name) { 'token-name' }
let(:service_account_name) { 'a_service_account' }
let(:namespace_name) { 'a_namespace' }
diff --git a/spec/lib/gitlab/kubernetes/tls_secret_spec.rb b/spec/lib/gitlab/kubernetes/tls_secret_spec.rb
index 438a0dc79fc..b40ed3c88c9 100644
--- a/spec/lib/gitlab/kubernetes/tls_secret_spec.rb
+++ b/spec/lib/gitlab/kubernetes/tls_secret_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::TlsSecret do
+RSpec.describe Gitlab::Kubernetes::TlsSecret do
let(:secret) { described_class.new(name, cert, key, namespace) }
let(:name) { 'example-name' }
let(:cert) { 'example-cert' }
diff --git a/spec/lib/gitlab/kubernetes_spec.rb b/spec/lib/gitlab/kubernetes_spec.rb
index 40c3e7d0b3c..eff5c019ad0 100644
--- a/spec/lib/gitlab/kubernetes_spec.rb
+++ b/spec/lib/gitlab/kubernetes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes do
+RSpec.describe Gitlab::Kubernetes do
include KubernetesHelpers
include described_class
diff --git a/spec/lib/gitlab/language_data_spec.rb b/spec/lib/gitlab/language_data_spec.rb
index b08150855fe..bb4b0c3855c 100644
--- a/spec/lib/gitlab/language_data_spec.rb
+++ b/spec/lib/gitlab/language_data_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LanguageData do
+RSpec.describe Gitlab::LanguageData do
describe '#extensions' do
before do
described_class.clear_extensions!
diff --git a/spec/lib/gitlab/language_detection_spec.rb b/spec/lib/gitlab/language_detection_spec.rb
index 770fe2f80db..04ad19a04ec 100644
--- a/spec/lib/gitlab/language_detection_spec.rb
+++ b/spec/lib/gitlab/language_detection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LanguageDetection do
+RSpec.describe Gitlab::LanguageDetection do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:ruby) { create(:programming_language, name: 'Ruby') }
let_it_be(:haskell) { create(:programming_language, name: 'Haskell') }
diff --git a/spec/lib/gitlab/lazy_spec.rb b/spec/lib/gitlab/lazy_spec.rb
index 19758a18589..3e929cf200a 100644
--- a/spec/lib/gitlab/lazy_spec.rb
+++ b/spec/lib/gitlab/lazy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Lazy do
+RSpec.describe Gitlab::Lazy do
let(:dummy) { double(:dummy) }
context 'when not calling any methods' do
diff --git a/spec/lib/gitlab/legacy_github_import/branch_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/branch_formatter_spec.rb
index e96745f5fbe..1a21ed29ab7 100644
--- a/spec/lib/gitlab/legacy_github_import/branch_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/branch_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::BranchFormatter do
+RSpec.describe Gitlab::LegacyGithubImport::BranchFormatter do
let(:project) { create(:project, :repository) }
let(:commit) { create(:commit, project: project) }
let(:repo) { double }
diff --git a/spec/lib/gitlab/legacy_github_import/client_spec.rb b/spec/lib/gitlab/legacy_github_import/client_spec.rb
index d266b39bd81..0929b90d1f4 100644
--- a/spec/lib/gitlab/legacy_github_import/client_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::Client do
+RSpec.describe Gitlab::LegacyGithubImport::Client do
let(:token) { '123456' }
let(:github_provider) { Settingslogic.new('app_id' => 'asd123', 'app_secret' => 'asd123', 'name' => 'github', 'args' => { 'client_options' => {} }) }
let(:wait_for_rate_limit_reset) { true }
diff --git a/spec/lib/gitlab/legacy_github_import/comment_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/comment_formatter_spec.rb
index 0f03db312ce..85f7666fe85 100644
--- a/spec/lib/gitlab/legacy_github_import/comment_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/comment_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::CommentFormatter do
+RSpec.describe Gitlab::LegacyGithubImport::CommentFormatter do
let(:client) { double }
let(:project) { create(:project) }
let(:octocat) { double(id: 123456, login: 'octocat', email: 'octocat@example.com') }
diff --git a/spec/lib/gitlab/legacy_github_import/importer_spec.rb b/spec/lib/gitlab/legacy_github_import/importer_spec.rb
index 8cc3fd8efbd..c443944678f 100644
--- a/spec/lib/gitlab/legacy_github_import/importer_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::Importer do
+RSpec.describe Gitlab::LegacyGithubImport::Importer do
shared_examples 'Gitlab::LegacyGithubImport::Importer#execute' do
let(:expected_not_called) { [] }
diff --git a/spec/lib/gitlab/legacy_github_import/issuable_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/issuable_formatter_spec.rb
index f5bfc379e89..6a51cb6f39d 100644
--- a/spec/lib/gitlab/legacy_github_import/issuable_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/issuable_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::IssuableFormatter do
+RSpec.describe Gitlab::LegacyGithubImport::IssuableFormatter do
let(:raw_data) do
double(number: 42)
end
diff --git a/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb
index 9a7a34afbe7..4b1e0d2c144 100644
--- a/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::IssueFormatter do
+RSpec.describe Gitlab::LegacyGithubImport::IssueFormatter do
let(:client) { double }
let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) }
let(:octocat) { double(id: 123456, login: 'octocat', email: 'octocat@example.com') }
diff --git a/spec/lib/gitlab/legacy_github_import/label_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/label_formatter_spec.rb
index e56e2772f6a..ab7c8ea4a58 100644
--- a/spec/lib/gitlab/legacy_github_import/label_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/label_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::LabelFormatter do
+RSpec.describe Gitlab::LegacyGithubImport::LabelFormatter do
let(:project) { create(:project) }
let(:raw) { double(name: 'improvements', color: 'e6e6e6') }
diff --git a/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb
index f5d71888ac9..2ac79c4f5b8 100644
--- a/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::MilestoneFormatter do
+RSpec.describe Gitlab::LegacyGithubImport::MilestoneFormatter do
let(:project) { create(:project) }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
diff --git a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb
index b0687474c80..02cc2eba4da 100644
--- a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::ProjectCreator do
+RSpec.describe Gitlab::LegacyGithubImport::ProjectCreator do
let(:user) { create(:user) }
let(:namespace) { create(:group) }
diff --git a/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb
index 622210508b9..3e6b9340d0b 100644
--- a/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::PullRequestFormatter do
+RSpec.describe Gitlab::LegacyGithubImport::PullRequestFormatter do
let(:client) { double }
let(:project) { create(:project, :repository) }
let(:source_sha) { create(:commit, project: project).id }
diff --git a/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb
index 554be57fbec..73b35d3a4e7 100644
--- a/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::ReleaseFormatter do
+RSpec.describe Gitlab::LegacyGithubImport::ReleaseFormatter do
let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) }
let(:octocat) { double(id: 123456, login: 'octocat') }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
diff --git a/spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb
index 919847fe061..34659030020 100644
--- a/spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::UserFormatter do
+RSpec.describe Gitlab::LegacyGithubImport::UserFormatter do
let(:client) { double }
let(:octocat) { double(id: 123456, login: 'octocat', email: 'octocat@example.com') }
diff --git a/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb
index 639fb9d80eb..df443fa3dbb 100644
--- a/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LegacyGithubImport::WikiFormatter do
+RSpec.describe Gitlab::LegacyGithubImport::WikiFormatter do
let(:project) do
create(:project,
namespace: create(:namespace, path: 'gitlabhq'),
diff --git a/spec/lib/gitlab/lets_encrypt/challenge_spec.rb b/spec/lib/gitlab/lets_encrypt/challenge_spec.rb
index fcd92586362..d853275520b 100644
--- a/spec/lib/gitlab/lets_encrypt/challenge_spec.rb
+++ b/spec/lib/gitlab/lets_encrypt/challenge_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::LetsEncrypt::Challenge do
+RSpec.describe ::Gitlab::LetsEncrypt::Challenge do
include LetsEncryptHelpers
let(:challenge) { described_class.new(acme_challenge_double) }
diff --git a/spec/lib/gitlab/lets_encrypt/client_spec.rb b/spec/lib/gitlab/lets_encrypt/client_spec.rb
index e86de04b5cf..54b9bd3bfba 100644
--- a/spec/lib/gitlab/lets_encrypt/client_spec.rb
+++ b/spec/lib/gitlab/lets_encrypt/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::LetsEncrypt::Client do
+RSpec.describe ::Gitlab::LetsEncrypt::Client do
include LetsEncryptHelpers
let(:client) { described_class.new }
diff --git a/spec/lib/gitlab/lets_encrypt/order_spec.rb b/spec/lib/gitlab/lets_encrypt/order_spec.rb
index 333fce8e36a..419f9e28871 100644
--- a/spec/lib/gitlab/lets_encrypt/order_spec.rb
+++ b/spec/lib/gitlab/lets_encrypt/order_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::LetsEncrypt::Order do
+RSpec.describe ::Gitlab::LetsEncrypt::Order do
include LetsEncryptHelpers
let(:acme_order) { acme_order_double }
diff --git a/spec/lib/gitlab/lets_encrypt_spec.rb b/spec/lib/gitlab/lets_encrypt_spec.rb
index 2229393fb32..7597359847b 100644
--- a/spec/lib/gitlab/lets_encrypt_spec.rb
+++ b/spec/lib/gitlab/lets_encrypt_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::LetsEncrypt do
+RSpec.describe ::Gitlab::LetsEncrypt do
include LetsEncryptHelpers
before do
diff --git a/spec/lib/gitlab/lfs_token_spec.rb b/spec/lib/gitlab/lfs_token_spec.rb
index 58a3767b242..9b8b2c1417a 100644
--- a/spec/lib/gitlab/lfs_token_spec.rb
+++ b/spec/lib/gitlab/lfs_token_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LfsToken, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::LfsToken, :clean_gitlab_redis_shared_state do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:deploy_key) { create(:deploy_key) }
diff --git a/spec/lib/gitlab/log_timestamp_formatter_spec.rb b/spec/lib/gitlab/log_timestamp_formatter_spec.rb
index 1a76d02889b..e06baa2324f 100644
--- a/spec/lib/gitlab/log_timestamp_formatter_spec.rb
+++ b/spec/lib/gitlab/log_timestamp_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LogTimestampFormatter do
+RSpec.describe Gitlab::LogTimestampFormatter do
subject { described_class.new }
let(:formatted_timestamp) { Time.now.utc.iso8601(3) }
diff --git a/spec/lib/gitlab/logging/cloudflare_helper_spec.rb b/spec/lib/gitlab/logging/cloudflare_helper_spec.rb
index 2b73fb7bc1c..87ac438d38d 100644
--- a/spec/lib/gitlab/logging/cloudflare_helper_spec.rb
+++ b/spec/lib/gitlab/logging/cloudflare_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Logging::CloudflareHelper do
+RSpec.describe Gitlab::Logging::CloudflareHelper do
let(:helper) do
Class.new do
include Gitlab::Logging::CloudflareHelper
diff --git a/spec/lib/gitlab/lograge/custom_options_spec.rb b/spec/lib/gitlab/lograge/custom_options_spec.rb
index ebf150d21ef..218007c6e2a 100644
--- a/spec/lib/gitlab/lograge/custom_options_spec.rb
+++ b/spec/lib/gitlab/lograge/custom_options_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Lograge::CustomOptions do
+RSpec.describe Gitlab::Lograge::CustomOptions do
describe '.call' do
let(:params) do
{
@@ -44,18 +44,6 @@ describe Gitlab::Lograge::CustomOptions do
end
end
- context 'with transaction' do
- let(:transaction) { Gitlab::Metrics::WebTransaction.new({}) }
-
- before do
- allow(Gitlab::Metrics::Transaction).to receive(:current).and_return(transaction)
- end
-
- it 'adds db counters' do
- expect(subject).to include(:db_count, :db_write_count, :db_cached_count)
- end
- end
-
it 'adds the user id' do
expect(subject[:user_id]).to eq('test')
end
@@ -77,14 +65,14 @@ describe Gitlab::Lograge::CustomOptions do
end
end
- context 'when correlation_id is overriden' do
+ context 'when correlation_id is overridden' do
let(:correlation_id_key) { Labkit::Correlation::CorrelationId::LOG_KEY }
before do
event_payload[correlation_id_key] = '123456'
end
- it 'sets the overriden value' do
+ it 'sets the overridden value' do
expect(subject[correlation_id_key]).to eq('123456')
end
end
diff --git a/spec/lib/gitlab/loop_helpers_spec.rb b/spec/lib/gitlab/loop_helpers_spec.rb
index 7e59b41d5b9..0535cb6068c 100644
--- a/spec/lib/gitlab/loop_helpers_spec.rb
+++ b/spec/lib/gitlab/loop_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::LoopHelpers do
+RSpec.describe Gitlab::LoopHelpers do
let(:class_instance) { (Class.new { include ::Gitlab::LoopHelpers }).new }
describe '#loop_until' do
diff --git a/spec/lib/gitlab/mail_room/mail_room_spec.rb b/spec/lib/gitlab/mail_room/mail_room_spec.rb
index 4b09205a181..ab9a9a035f1 100644
--- a/spec/lib/gitlab/mail_room/mail_room_spec.rb
+++ b/spec/lib/gitlab/mail_room/mail_room_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::MailRoom do
+RSpec.describe Gitlab::MailRoom do
let(:default_port) { 143 }
let(:yml_config) do
{
diff --git a/spec/lib/gitlab/manifest_import/manifest_spec.rb b/spec/lib/gitlab/manifest_import/manifest_spec.rb
index c1135f710ea..2e8753b0880 100644
--- a/spec/lib/gitlab/manifest_import/manifest_spec.rb
+++ b/spec/lib/gitlab/manifest_import/manifest_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ManifestImport::Manifest do
+RSpec.describe Gitlab::ManifestImport::Manifest do
let(:file) { File.open(Rails.root.join('spec/fixtures/aosp_manifest.xml')) }
let(:manifest) { described_class.new(file) }
diff --git a/spec/lib/gitlab/manifest_import/project_creator_spec.rb b/spec/lib/gitlab/manifest_import/project_creator_spec.rb
index a8cfcfb41d3..354acf53b7a 100644
--- a/spec/lib/gitlab/manifest_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/manifest_import/project_creator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ManifestImport::ProjectCreator do
+RSpec.describe Gitlab::ManifestImport::ProjectCreator do
let(:group) { create(:group) }
let(:user) { create(:user) }
let(:repository) do
diff --git a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
index 5b6c769d6eb..be562d916d3 100644
--- a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
+++ b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::MarkdownCache::ActiveRecord::Extension do
+RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do
let(:klass) do
Class.new(ActiveRecord::Base) do
self.table_name = 'issues'
diff --git a/spec/lib/gitlab/markdown_cache/field_data_spec.rb b/spec/lib/gitlab/markdown_cache/field_data_spec.rb
index 393bf85aa43..76d8cbe6b7d 100644
--- a/spec/lib/gitlab/markdown_cache/field_data_spec.rb
+++ b/spec/lib/gitlab/markdown_cache/field_data_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::MarkdownCache::FieldData do
+RSpec.describe Gitlab::MarkdownCache::FieldData do
subject(:field_data) { described_class.new }
before do
diff --git a/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb b/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb
index b6a781de426..3dcb9f160ba 100644
--- a/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb
+++ b/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::MarkdownCache::Redis::Extension, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::MarkdownCache::Redis::Extension, :clean_gitlab_redis_cache do
let(:klass) do
Class.new do
include CacheMarkdownField
@@ -49,6 +49,31 @@ describe Gitlab::MarkdownCache::Redis::Extension, :clean_gitlab_redis_cache do
expect(thing.cached_markdown_version).to eq(cache_version)
end
+ describe '.preload_markdown_cache!' do
+ before do
+ Gitlab::Redis::Cache.with do |r|
+ r.mapped_hmset(expected_cache_key,
+ title_html: 'hello',
+ description_html: 'world',
+ cached_markdown_version: cache_version)
+ end
+ end
+
+ it 'does not preload the markdown twice' do
+ expect(Gitlab::MarkdownCache::Redis::Store).to receive(:bulk_read).and_call_original
+ expect(Gitlab::Redis::Cache).to receive(:with).twice.and_call_original
+
+ klass.preload_markdown_cache!([thing])
+
+ aggregate_failures do
+ expect(Gitlab::Redis::Cache).not_to receive(:with)
+ expect(thing.title_html).to eq('hello')
+ expect(thing.description_html).to eq('world')
+ expect(thing.cached_markdown_version).to eq(cache_version)
+ end
+ end
+ end
+
describe "#refresh_markdown_cache!" do
it "stores the value in redis" do
expected_results = { "title_html" => "`Hello`",
diff --git a/spec/lib/gitlab/markdown_cache/redis/store_spec.rb b/spec/lib/gitlab/markdown_cache/redis/store_spec.rb
index 95c68e7d491..40ff9a765a6 100644
--- a/spec/lib/gitlab/markdown_cache/redis/store_spec.rb
+++ b/spec/lib/gitlab/markdown_cache/redis/store_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::MarkdownCache::Redis::Store, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::MarkdownCache::Redis::Store, :clean_gitlab_redis_cache do
let(:storable_class) do
Class.new do
cattr_reader :cached_markdown_fields do
@@ -37,6 +37,23 @@ describe Gitlab::MarkdownCache::Redis::Store, :clean_gitlab_redis_cache do
end
end
+ describe '.bulk_read' do
+ before do
+ store.save(field_1_html: "hello", field_2_html: "world", cached_markdown_version: 1)
+ end
+
+ it 'returns a hash of values from store' do
+ Gitlab::Redis::Cache.with do |redis|
+ expect(redis).to receive(:pipelined).and_call_original
+ end
+
+ results = described_class.bulk_read([storable])
+
+ expect(results[storable.cache_key].value.symbolize_keys)
+ .to eq(field_1_html: "hello", field_2_html: "world", cached_markdown_version: "1")
+ end
+ end
+
describe '#save' do
it 'stores updates to html fields and version' do
values_to_store = { field_1_html: "hello", field_2_html: "world", cached_markdown_version: 1 }
@@ -44,7 +61,7 @@ describe Gitlab::MarkdownCache::Redis::Store, :clean_gitlab_redis_cache do
store.save(values_to_store)
expect(read_values)
- .to eq({ field_1_html: "hello", field_2_html: "world", cached_markdown_version: "1" })
+ .to eq(field_1_html: "hello", field_2_html: "world", cached_markdown_version: "1")
end
end
@@ -54,7 +71,8 @@ describe Gitlab::MarkdownCache::Redis::Store, :clean_gitlab_redis_cache do
store_values(stored_values)
- expect(store.read.symbolize_keys).to eq(stored_values)
+ expect(store.read.symbolize_keys)
+ .to eq(field_1_html: "hello", field_2_html: "world", cached_markdown_version: "1")
end
it 'is mared loaded after reading' do
diff --git a/spec/lib/gitlab/markup_helper_spec.rb b/spec/lib/gitlab/markup_helper_spec.rb
index b93538cae5a..bf5415ba1d7 100644
--- a/spec/lib/gitlab/markup_helper_spec.rb
+++ b/spec/lib/gitlab/markup_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::MarkupHelper do
+RSpec.describe Gitlab::MarkupHelper do
describe '#markup?' do
%w(textile rdoc org creole wiki
mediawiki rst adoc ad asciidoc mdown md markdown).each do |type|
diff --git a/spec/lib/gitlab/metrics/background_transaction_spec.rb b/spec/lib/gitlab/metrics/background_transaction_spec.rb
index 84f405d7369..640bbebf0da 100644
--- a/spec/lib/gitlab/metrics/background_transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/background_transaction_spec.rb
@@ -2,14 +2,23 @@
require 'spec_helper'
-describe Gitlab::Metrics::BackgroundTransaction do
+RSpec.describe Gitlab::Metrics::BackgroundTransaction do
let(:test_worker_class) { double(:class, name: 'TestWorker') }
subject { described_class.new(test_worker_class) }
describe '#label' do
it 'returns labels based on class name' do
- expect(subject.labels).to eq(controller: 'TestWorker', action: 'perform')
+ expect(subject.labels).to eq(controller: 'TestWorker', action: 'perform', feature_category: '')
+ end
+
+ it 'contains only the labels defined for metrics' do
+ expect(subject.labels.keys).to contain_exactly(*described_class.superclass::BASE_LABELS.keys)
+ end
+
+ it 'includes the feature category if there is one' do
+ expect(test_worker_class).to receive(:get_feature_category).and_return('source_code_management')
+ expect(subject.labels).to include(feature_category: 'source_code_management')
end
end
end
diff --git a/spec/lib/gitlab/metrics/dashboard/defaults_spec.rb b/spec/lib/gitlab/metrics/dashboard/defaults_spec.rb
index 420b246b3f5..dd61f8ebc4d 100644
--- a/spec/lib/gitlab/metrics/dashboard/defaults_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/defaults_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Dashboard::Defaults do
+RSpec.describe Gitlab::Metrics::Dashboard::Defaults do
it { is_expected.to be_const_defined(:DEFAULT_PANEL_TYPE) }
it { is_expected.to be_const_defined(:DEFAULT_PANEL_WEIGHT) }
end
diff --git a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
index 2703339d89c..60e1e29d4c5 100644
--- a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_caching do
+RSpec.describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
let_it_be(:project) { create(:project) }
@@ -118,7 +118,7 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi
end
describe '.find_raw' do
- let(:dashboard) { YAML.load_file(Rails.root.join('config', 'prometheus', 'common_metrics.yml')) }
+ let(:dashboard) { load_dashboard_yaml(File.read(Rails.root.join('config', 'prometheus', 'common_metrics.yml'))) }
let(:params) { {} }
subject { described_class.find_raw(project, **params) }
@@ -132,7 +132,7 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi
end
context 'when an existing project dashboard is specified' do
- let(:dashboard) { YAML.safe_load(fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml')) }
+ let(:dashboard) { load_sample_dashboard }
let(:params) { { dashboard_path: '.gitlab/dashboards/test.yml' } }
let(:project) { project_with_dashboard(params[:dashboard_path]) }
@@ -142,7 +142,7 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi
describe '.find_all_paths' do
let(:all_dashboard_paths) { described_class.find_all_paths(project) }
- let(:system_dashboard) { { path: system_dashboard_path, display_name: 'Default dashboard', default: true, system_dashboard: true } }
+ let(:system_dashboard) { { path: system_dashboard_path, display_name: 'Default dashboard', default: true, system_dashboard: true, out_of_the_box_dashboard: true } }
it 'includes only the system dashboard by default' do
expect(all_dashboard_paths).to eq([system_dashboard])
@@ -153,7 +153,7 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi
let(:project) { project_with_dashboard(dashboard_path) }
it 'includes system and project dashboards' do
- project_dashboard = { path: dashboard_path, display_name: 'test.yml', default: false, system_dashboard: false }
+ project_dashboard = { path: dashboard_path, display_name: 'test.yml', default: false, system_dashboard: false, out_of_the_box_dashboard: false }
expect(all_dashboard_paths).to contain_exactly(system_dashboard, project_dashboard)
end
@@ -165,7 +165,8 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi
path: self_monitoring_dashboard_path,
display_name: 'Default dashboard',
default: true,
- system_dashboard: false
+ system_dashboard: false,
+ out_of_the_box_dashboard: true
}
end
let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
@@ -180,7 +181,8 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi
path: dashboard_path,
display_name: 'test.yml',
default: false,
- system_dashboard: false
+ system_dashboard: false,
+ out_of_the_box_dashboard: false
}
expect(all_dashboard_paths).to contain_exactly(self_monitoring_dashboard, project_dashboard)
diff --git a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
index 7250cefb9ff..7f7070dfafb 100644
--- a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
@@ -2,10 +2,12 @@
require 'spec_helper'
-describe Gitlab::Metrics::Dashboard::Processor do
+RSpec.describe Gitlab::Metrics::Dashboard::Processor do
+ include MetricsDashboardHelpers
+
let(:project) { build(:project) }
let(:environment) { create(:environment, project: project) }
- let(:dashboard_yml) { YAML.load_file('spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml') }
+ let(:dashboard_yml) { load_sample_dashboard }
describe 'process' do
let(:sequence) do
@@ -13,7 +15,7 @@ describe Gitlab::Metrics::Dashboard::Processor do
Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter,
Gitlab::Metrics::Dashboard::Stages::CustomMetricsInserter,
Gitlab::Metrics::Dashboard::Stages::CustomMetricsDetailsInserter,
- Gitlab::Metrics::Dashboard::Stages::EndpointInserter,
+ Gitlab::Metrics::Dashboard::Stages::MetricEndpointInserter,
Gitlab::Metrics::Dashboard::Stages::Sorter,
Gitlab::Metrics::Dashboard::Stages::AlertsInserter,
Gitlab::Metrics::Dashboard::Stages::PanelIdsInserter,
@@ -98,7 +100,7 @@ describe Gitlab::Metrics::Dashboard::Processor do
let(:sequence) do
[
Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter,
- Gitlab::Metrics::Dashboard::Stages::EndpointInserter,
+ Gitlab::Metrics::Dashboard::Stages::MetricEndpointInserter,
Gitlab::Metrics::Dashboard::Stages::Sorter
]
end
@@ -202,27 +204,6 @@ describe Gitlab::Metrics::Dashboard::Processor do
it_behaves_like 'errors with message', 'Each "metric" must define one of :query or :query_range'
end
-
- describe 'validating links' do
- context 'when the links contain a blocked url' do
- let(:dashboard_yml_links) do
- [{ 'url' => 'http://1.1.1.1.1' }, { 'url' => 'https://gitlab.com' }]
- end
-
- let(:expected) do
- [{ url: '' }, { url: 'https://gitlab.com' }]
- end
-
- before do
- stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
- dashboard_yml['links'] = dashboard_yml_links
- end
-
- it 'replaces the blocked url with an empty string' do
- expect(dashboard[:links]).to eq(expected)
- end
- end
- end
end
private
diff --git a/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb
index 245c98cdd00..f3c8209e0b6 100644
--- a/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Dashboard::ServiceSelector do
+RSpec.describe Gitlab::Metrics::Dashboard::ServiceSelector do
include MetricsDashboardHelpers
describe '#call' do
@@ -109,6 +109,46 @@ describe Gitlab::Metrics::Dashboard::ServiceSelector do
it { is_expected.to be Metrics::Dashboard::TransientEmbedService }
end
+
+ context 'when cluster is provided' do
+ let(:arguments) { { cluster: "some cluster" } }
+
+ it { is_expected.to be Metrics::Dashboard::ClusterDashboardService }
+ end
+
+ context 'when cluster is provided and embedded is not true' do
+ let(:arguments) { { cluster: "some cluster", embedded: 'false' } }
+
+ it { is_expected.to be Metrics::Dashboard::ClusterDashboardService }
+ end
+
+ context 'when cluster dashboard_path is provided' do
+ let(:arguments) { { dashboard_path: ::Metrics::Dashboard::ClusterDashboardService::DASHBOARD_PATH } }
+
+ it { is_expected.to be Metrics::Dashboard::ClusterDashboardService }
+ end
+
+ context 'when cluster is provided and embed params' do
+ let(:arguments) do
+ {
+ cluster: "some cluster",
+ embedded: 'true',
+ cluster_type: 'project',
+ format: :json,
+ group: 'Food metrics',
+ title: 'Pizza Consumption',
+ y_label: 'Slice Count'
+ }
+ end
+
+ it { is_expected.to be Metrics::Dashboard::ClusterMetricsEmbedService }
+ end
+
+ context 'when metrics embed is for an alert' do
+ let(:arguments) { { embedded: true, prometheus_alert_id: 5 } }
+
+ it { is_expected.to be Metrics::Dashboard::GitlabAlertEmbedService }
+ end
end
end
end
diff --git a/spec/lib/gitlab/metrics/dashboard/stages/grafana_formatter_spec.rb b/spec/lib/gitlab/metrics/dashboard/stages/grafana_formatter_spec.rb
index 5d4bd4512e3..8a236f72a60 100644
--- a/spec/lib/gitlab/metrics/dashboard/stages/grafana_formatter_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/stages/grafana_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Dashboard::Stages::GrafanaFormatter do
+RSpec.describe Gitlab::Metrics::Dashboard::Stages::GrafanaFormatter do
include GrafanaApiHelpers
let_it_be(:namespace) { create(:namespace, name: 'foo') }
diff --git a/spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb b/spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb
index 6124f471e39..7a3a9021f86 100644
--- a/spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb
@@ -2,7 +2,9 @@
require 'spec_helper'
-describe Gitlab::Metrics::Dashboard::Stages::PanelIdsInserter do
+RSpec.describe Gitlab::Metrics::Dashboard::Stages::PanelIdsInserter do
+ include MetricsDashboardHelpers
+
let(:project) { build_stubbed(:project) }
def fetch_panel_ids(dashboard_hash)
@@ -12,7 +14,7 @@ describe Gitlab::Metrics::Dashboard::Stages::PanelIdsInserter do
describe '#transform!' do
subject(:transform!) { described_class.new(project, dashboard, nil).transform! }
- let(:dashboard) { YAML.safe_load(fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml')).deep_symbolize_keys }
+ let(:dashboard) { load_sample_dashboard.deep_symbolize_keys }
context 'when dashboard panels are present' do
it 'assigns unique ids to each panel using PerformanceMonitoring::PrometheusPanel', :aggregate_failures do
diff --git a/spec/lib/gitlab/metrics/dashboard/stages/url_validator_spec.rb b/spec/lib/gitlab/metrics/dashboard/stages/url_validator_spec.rb
new file mode 100644
index 00000000000..83cf161c4e2
--- /dev/null
+++ b/spec/lib/gitlab/metrics/dashboard/stages/url_validator_spec.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Metrics::Dashboard::Stages::UrlValidator do
+ let(:project) { build_stubbed(:project) }
+
+ describe '#transform!' do
+ context 'when the links contain a blocked url' do
+ let(:dashboard) do
+ {
+ dashboard: "Test Dashboard",
+ links: [
+ { url: "http://1.1.1.1.1" },
+ { url: "https://gitlab.com" },
+ { url: "http://0.0.0.0" }
+ ],
+ panel_groups: [
+ {
+ group: "Group A",
+ panels: [
+ {
+ title: "Super Chart A1",
+ type: "area-chart",
+ y_label: "y_label",
+ metrics: [
+ {
+ id: "metric_a1",
+ query_range: "query",
+ unit: "unit",
+ label: "Legend Label"
+ }
+ ],
+ links: [
+ { url: "http://1.1.1.1.1" },
+ { url: "https://gitlab.com" },
+ { url: "http://0.0.0.0" }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ end
+
+ let(:expected) do
+ [{ url: '' }, { url: 'https://gitlab.com' }, { url: 'http://0.0.0.0' }]
+ end
+
+ let(:transform!) { described_class.new(project, dashboard, nil).transform! }
+
+ before do
+ stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
+ end
+
+ context 'dashboard related links' do
+ it 'replaces the blocked url with an empty string' do
+ transform!
+
+ expect(dashboard[:links]).to eq(expected)
+ end
+ end
+
+ context 'chart links' do
+ it 'replaces the blocked url with an empty string' do
+ transform!
+
+ result = dashboard.dig(:panel_groups, 0, :panels, 0, :links)
+ expect(result).to eq(expected)
+ end
+ end
+
+ context 'when local requests are not allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: false)
+ end
+
+ let(:expected) do
+ [{ url: '' }, { url: 'https://gitlab.com' }, { url: '' }]
+ end
+
+ it 'replaces the blocked url with an empty string' do
+ transform!
+
+ expect(dashboard[:links]).to eq(expected)
+ end
+ end
+
+ context 'when the links are an array of strings instead of hashes' do
+ before do
+ dashboard[:links] = dashboard[:links].map(&:values)
+ end
+
+ it 'prevents an invalid link definition from erroring out' do
+ expect { transform! }.not_to raise_error
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/dashboard/stages/variable_endpoint_inserter_spec.rb b/spec/lib/gitlab/metrics/dashboard/stages/variable_endpoint_inserter_spec.rb
new file mode 100644
index 00000000000..9303ff981fb
--- /dev/null
+++ b/spec/lib/gitlab/metrics/dashboard/stages/variable_endpoint_inserter_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Metrics::Dashboard::Stages::VariableEndpointInserter do
+ include MetricsDashboardHelpers
+
+ let(:project) { build_stubbed(:project) }
+ let(:environment) { build_stubbed(:environment, project: project) }
+
+ describe '#transform!' do
+ subject(:transform!) { described_class.new(project, dashboard, environment: environment).transform! }
+
+ let(:dashboard) { load_sample_dashboard.deep_symbolize_keys }
+
+ context 'when dashboard variables are present' do
+ it 'assigns prometheus_endpoint_path to metric_label_values variable type' do
+ endpoint_path = Gitlab::Routing.url_helpers.prometheus_api_project_environment_path(
+ project,
+ environment,
+ proxy_path: :series,
+ match: ['backend:haproxy_backend_availability:ratio{env="{{env}}"}']
+ )
+
+ transform!
+
+ expect(
+ dashboard.dig(:templating, :variables, :metric_label_values_variable, :options)
+ ).to include(prometheus_endpoint_path: endpoint_path)
+ end
+
+ it 'does not modify other variable types' do
+ original_text_variable = dashboard[:templating][:variables][:text_variable_full_syntax].deep_dup
+
+ transform!
+
+ expect(dashboard[:templating][:variables][:text_variable_full_syntax]).to eq(original_text_variable)
+ end
+
+ context 'when variable does not have the required series_selector' do
+ it 'adds prometheus_endpoint_path without match parameter' do
+ dashboard[:templating][:variables][:metric_label_values_variable][:options].delete(:series_selector)
+ endpoint_path = Gitlab::Routing.url_helpers.prometheus_api_project_environment_path(
+ project,
+ environment,
+ proxy_path: :series
+ )
+
+ transform!
+
+ expect(
+ dashboard.dig(:templating, :variables, :metric_label_values_variable, :options)
+ ).to include(prometheus_endpoint_path: endpoint_path)
+ end
+ end
+ end
+
+ context 'when no variables are present' do
+ it 'does not fail' do
+ dashboard.delete(:templating)
+
+ expect { transform! }.not_to raise_error
+ end
+ end
+
+ context 'with no environment' do
+ subject(:transform!) { described_class.new(project, dashboard, {}).transform! }
+
+ it 'raises error' do
+ expect { transform! }.to raise_error(
+ Gitlab::Metrics::Dashboard::Errors::DashboardProcessingError,
+ 'Environment is required for Stages::VariableEndpointInserter'
+ )
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/dashboard/url_spec.rb b/spec/lib/gitlab/metrics/dashboard/url_spec.rb
index 75f9f99c8a6..56556423b05 100644
--- a/spec/lib/gitlab/metrics/dashboard/url_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/url_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Dashboard::Url do
+RSpec.describe Gitlab::Metrics::Dashboard::Url do
include Gitlab::Routing.url_helpers
describe '#metrics_regex' do
@@ -46,6 +46,35 @@ describe Gitlab::Metrics::Dashboard::Url do
end
end
+ describe '#clusters_regex' do
+ let(:url) do
+ Gitlab::Routing.url_helpers.namespace_project_cluster_url(
+ 'foo',
+ 'bar',
+ '1',
+ group: 'Cluster Health',
+ title: 'Memory Usage',
+ y_label: 'Memory 20(GiB)',
+ anchor: 'title'
+ )
+ end
+
+ let(:expected_params) do
+ {
+ 'url' => url,
+ 'namespace' => 'foo',
+ 'project' => 'bar',
+ 'cluster_id' => '1',
+ 'query' => '?group=Cluster+Health&title=Memory+Usage&y_label=Memory+20%28GiB%29',
+ 'anchor' => '#title'
+ }
+ end
+
+ subject { described_class.clusters_regex }
+
+ it_behaves_like 'regex which matches url when expected'
+ end
+
describe '#grafana_regex' do
let(:url) do
namespace_project_grafana_api_metrics_dashboard_url(
diff --git a/spec/lib/gitlab/metrics/delta_spec.rb b/spec/lib/gitlab/metrics/delta_spec.rb
index 9bb011dc8fc..e768da875c2 100644
--- a/spec/lib/gitlab/metrics/delta_spec.rb
+++ b/spec/lib/gitlab/metrics/delta_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Delta do
+RSpec.describe Gitlab::Metrics::Delta do
let(:delta) { described_class.new }
describe '#compared_with' do
diff --git a/spec/lib/gitlab/metrics/elasticsearch_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/elasticsearch_rack_middleware_spec.rb
index 305768ef060..1fbd41bcc88 100644
--- a/spec/lib/gitlab/metrics/elasticsearch_rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/elasticsearch_rack_middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::ElasticsearchRackMiddleware do
+RSpec.describe Gitlab::Metrics::ElasticsearchRackMiddleware do
let(:app) { double(:app, call: 'app call result') }
let(:middleware) { described_class.new(app) }
let(:env) { {} }
diff --git a/spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb b/spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb
index 47ec69e2f45..e4f85243528 100644
--- a/spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb
+++ b/spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Exporter::BaseExporter do
+RSpec.describe Gitlab::Metrics::Exporter::BaseExporter do
let(:exporter) { described_class.new }
let(:log_filename) { File.join(Rails.root, 'log', 'sidekiq_exporter.log') }
let(:settings) { double('settings') }
diff --git a/spec/lib/gitlab/metrics/exporter/sidekiq_exporter_spec.rb b/spec/lib/gitlab/metrics/exporter/sidekiq_exporter_spec.rb
index 0b820fdbde9..2c5ef09f799 100644
--- a/spec/lib/gitlab/metrics/exporter/sidekiq_exporter_spec.rb
+++ b/spec/lib/gitlab/metrics/exporter/sidekiq_exporter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Exporter::SidekiqExporter do
+RSpec.describe Gitlab::Metrics::Exporter::SidekiqExporter do
let(:exporter) { described_class.new }
after do
diff --git a/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb b/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb
index f22993cf057..ce98c807e2e 100644
--- a/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb
+++ b/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Exporter::WebExporter do
+RSpec.describe Gitlab::Metrics::Exporter::WebExporter do
let(:exporter) { described_class.new }
let(:readiness_probe) { exporter.send(:readiness_probe).execute }
diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb
index bf84a476df9..2729fbce974 100644
--- a/spec/lib/gitlab/metrics/instrumentation_spec.rb
+++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Instrumentation do
+RSpec.describe Gitlab::Metrics::Instrumentation do
let(:env) { {} }
let(:transaction) { Gitlab::Metrics::WebTransaction.new(env) }
diff --git a/spec/lib/gitlab/metrics/method_call_spec.rb b/spec/lib/gitlab/metrics/method_call_spec.rb
index 035d875258c..42361cbc36a 100644
--- a/spec/lib/gitlab/metrics/method_call_spec.rb
+++ b/spec/lib/gitlab/metrics/method_call_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::MethodCall do
+RSpec.describe Gitlab::Metrics::MethodCall do
let(:transaction) { double(:transaction, labels: {}) }
let(:method_call) { described_class.new('Foo#bar', :Foo, '#bar', transaction) }
diff --git a/spec/lib/gitlab/metrics/methods_spec.rb b/spec/lib/gitlab/metrics/methods_spec.rb
index 5cf8db55142..3c171680272 100644
--- a/spec/lib/gitlab/metrics/methods_spec.rb
+++ b/spec/lib/gitlab/metrics/methods_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Methods do
+RSpec.describe Gitlab::Metrics::Methods do
subject { Class.new { include Gitlab::Metrics::Methods } }
shared_context 'metric' do |metric_type, *args|
diff --git a/spec/lib/gitlab/metrics/prometheus_spec.rb b/spec/lib/gitlab/metrics/prometheus_spec.rb
index e15a063fc9e..2273987551d 100644
--- a/spec/lib/gitlab/metrics/prometheus_spec.rb
+++ b/spec/lib/gitlab/metrics/prometheus_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Prometheus, :prometheus do
+RSpec.describe Gitlab::Metrics::Prometheus, :prometheus do
let(:all_metrics) { Gitlab::Metrics }
let(:registry) { all_metrics.registry }
diff --git a/spec/lib/gitlab/metrics/rack_middleware_spec.rb b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
index dd1dbf7a1f4..335e5a490a6 100644
--- a/spec/lib/gitlab/metrics/rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::RackMiddleware do
+RSpec.describe Gitlab::Metrics::RackMiddleware do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
diff --git a/spec/lib/gitlab/metrics/redis_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/redis_rack_middleware_spec.rb
index f2f36ccad20..a85968dbd43 100644
--- a/spec/lib/gitlab/metrics/redis_rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/redis_rack_middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::RedisRackMiddleware do
+RSpec.describe Gitlab::Metrics::RedisRackMiddleware do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
let(:env) { {} }
diff --git a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
index 6ee8acbf6fd..69b779d36eb 100644
--- a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::RequestsRackMiddleware do
+RSpec.describe Gitlab::Metrics::RequestsRackMiddleware do
let(:app) { double('app') }
subject { described_class.new(app) }
diff --git a/spec/lib/gitlab/metrics/samplers/database_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/database_sampler_spec.rb
index 087a0bfbac5..b94d19ff227 100644
--- a/spec/lib/gitlab/metrics/samplers/database_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/database_sampler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Samplers::DatabaseSampler do
+RSpec.describe Gitlab::Metrics::Samplers::DatabaseSampler do
subject { described_class.new }
describe '#interval' do
diff --git a/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
index df63f2ebe28..214649d3e7e 100644
--- a/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Samplers::PumaSampler do
+RSpec.describe Gitlab::Metrics::Samplers::PumaSampler do
subject { described_class.new }
let(:null_metric) { double('null_metric', set: nil, observe: nil) }
diff --git a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
index 9fc8dd10922..59a70ac74a5 100644
--- a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Samplers::RubySampler do
+RSpec.describe Gitlab::Metrics::Samplers::RubySampler do
let(:sampler) { described_class.new }
let(:null_metric) { double('null_metric', set: nil, observe: nil) }
diff --git a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
index a64aae73d43..9f2180c4170 100644
--- a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Samplers::UnicornSampler do
+RSpec.describe Gitlab::Metrics::Samplers::UnicornSampler do
subject { described_class.new(1.second) }
describe '#sample' do
diff --git a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
index ea9e8fa6795..c66d8b1075c 100644
--- a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::SidekiqMiddleware do
+RSpec.describe Gitlab::Metrics::SidekiqMiddleware do
let(:middleware) { described_class.new }
let(:message) { { 'args' => ['test'], 'enqueued_at' => Time.new(2016, 6, 23, 6, 59).to_f } }
@@ -18,8 +18,20 @@ describe Gitlab::Metrics::SidekiqMiddleware do
middleware.call(worker, message, :test) do
ActiveRecord::Base.connection.execute('SELECT pg_sleep(0.1);')
end
+ end
+
+ it 'prevents database counters from leaking to the next transaction' do
+ worker = double(:worker, class: double(:class, name: 'TestWorker'))
- expect(message).to include(:db_count, :db_write_count, :db_cached_count)
+ 2.times do
+ Gitlab::WithRequestStore.with_request_store do
+ middleware.call(worker, message, :test) do
+ ActiveRecord::Base.connection.execute('SELECT pg_sleep(0.1);')
+ end
+ end
+ end
+
+ expect(message).to include(db_count: 1, db_write_count: 0, db_cached_count: 0)
end
it 'tracks the transaction (for messages without `enqueued_at`)', :aggregate_failures do
@@ -35,19 +47,20 @@ describe Gitlab::Metrics::SidekiqMiddleware do
middleware.call(worker, {}, :test) { nil }
end
- it 'tracks any raised exceptions', :aggregate_failures do
+ it 'tracks any raised exceptions', :aggregate_failures, :request_store do
worker = double(:worker, class: double(:class, name: 'TestWorker'))
expect_any_instance_of(Gitlab::Metrics::Transaction)
- .to receive(:run).and_raise(RuntimeError)
-
- expect_any_instance_of(Gitlab::Metrics::Transaction)
.to receive(:add_event).with(:sidekiq_exception)
- expect { middleware.call(worker, message, :test) }
- .to raise_error(RuntimeError)
+ expect do
+ middleware.call(worker, message, :test) do
+ ActiveRecord::Base.connection.execute('SELECT pg_sleep(0.1);')
+ raise RuntimeError
+ end
+ end.to raise_error(RuntimeError)
- expect(message).to include(:db_count, :db_write_count, :db_cached_count)
+ expect(message).to include(db_count: 1, db_write_count: 0, db_cached_count: 0)
end
end
end
diff --git a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
index 857e54d3432..161527c01aa 100644
--- a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Subscribers::ActionView do
+RSpec.describe Gitlab::Metrics::Subscribers::ActionView do
let(:env) { {} }
let(:transaction) { Gitlab::Metrics::WebTransaction.new(env) }
diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
index a78d048908d..2fd5dd1d83b 100644
--- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Subscribers::ActiveRecord do
+RSpec.describe Gitlab::Metrics::Subscribers::ActiveRecord do
let(:env) { {} }
let(:transaction) { Gitlab::Metrics::WebTransaction.new(env) }
let(:subscriber) { described_class.new }
@@ -28,60 +28,45 @@ describe Gitlab::Metrics::Subscribers::ActiveRecord do
end
describe 'with a current transaction' do
- shared_examples 'read only query' do
- it 'increments only db count value' do
+ shared_examples 'track executed query' do
+ before do
allow(subscriber).to receive(:current_transaction)
- .at_least(:once)
- .and_return(transaction)
-
- expect(transaction).to receive(:increment)
- .with(:db_count, 1)
-
- expect(transaction).not_to receive(:increment)
- .with(:db_cached_count, 1)
-
- expect(transaction).not_to receive(:increment)
- .with(:db_write_count, 1)
-
- subscriber.sql(event)
+ .at_least(:once)
+ .and_return(transaction)
end
- end
-
- shared_examples 'write query' do
- it 'increments db_write_count and db_count value' do
- expect(subscriber).to receive(:current_transaction)
- .at_least(:once)
- .and_return(transaction)
- expect(transaction).to receive(:increment)
- .with(:db_count, 1)
-
- expect(transaction).not_to receive(:increment)
- .with(:db_cached_count, 1)
-
- expect(transaction).to receive(:increment)
- .with(:db_write_count, 1)
+ it 'increments only db count value' do
+ described_class::DB_COUNTERS.each do |counter|
+ if expected_counters[counter] > 0
+ expect(transaction).to receive(:increment).with(counter, 1)
+ else
+ expect(transaction).not_to receive(:increment).with(counter, 1)
+ end
+ end
subscriber.sql(event)
end
- end
- shared_examples 'cached query' do
- it 'increments db_cached_count and db_count value' do
- expect(subscriber).to receive(:current_transaction)
- .at_least(:once)
- .and_return(transaction)
-
- expect(transaction).to receive(:increment)
- .with(:db_count, 1)
-
- expect(transaction).to receive(:increment)
- .with(:db_cached_count, 1)
-
- expect(transaction).not_to receive(:increment)
- .with(:db_write_count, 1)
-
- subscriber.sql(event)
+ context 'when RequestStore is enabled' do
+ it 'caches db count value', :request_store, :aggregate_failures do
+ subscriber.sql(event)
+
+ described_class::DB_COUNTERS.each do |counter|
+ expect(Gitlab::SafeRequestStore[counter].to_i).to eq expected_counters[counter]
+ end
+ end
+
+ it 'prevents db counters from leaking to the next transaction' do
+ 2.times do
+ Gitlab::WithRequestStore.with_request_store do
+ subscriber.sql(event)
+
+ described_class::DB_COUNTERS.each do |counter|
+ expect(Gitlab::SafeRequestStore[counter].to_i).to eq expected_counters[counter]
+ end
+ end
+ end
+ end
end
end
@@ -93,66 +78,96 @@ describe Gitlab::Metrics::Subscribers::ActiveRecord do
subscriber.sql(event)
end
- it_behaves_like 'read only query'
+ context 'with read query' do
+ let(:expected_counters) do
+ {
+ db_count: 1,
+ db_write_count: 0,
+ db_cached_count: 0
+ }
+ end
+
+ it_behaves_like 'track executed query'
- context 'with select for update sql event' do
- let(:payload) { { sql: 'SELECT * FROM users WHERE id = 10 FOR UPDATE' } }
+ context 'with only select' do
+ let(:payload) { { sql: 'WITH active_milestones AS (SELECT COUNT(*), state FROM milestones GROUP BY state) SELECT * FROM active_milestones' } }
- it_behaves_like 'write query'
+ it_behaves_like 'track executed query'
+ end
end
- context 'with common table expression' do
- context 'with insert' do
- let(:payload) { { sql: 'WITH archived_rows AS (SELECT * FROM users WHERE archived = true) INSERT INTO products_log SELECT * FROM archived_rows' } }
+ context 'write query' do
+ let(:expected_counters) do
+ {
+ db_count: 1,
+ db_write_count: 1,
+ db_cached_count: 0
+ }
+ end
+
+ context 'with select for update sql event' do
+ let(:payload) { { sql: 'SELECT * FROM users WHERE id = 10 FOR UPDATE' } }
- it_behaves_like 'write query'
+ it_behaves_like 'track executed query'
end
- context 'with only select' do
- let(:payload) { { sql: 'WITH active_milestones AS (SELECT COUNT(*), state FROM milestones GROUP BY state) SELECT * FROM active_milestones' } }
+ context 'with common table expression' do
+ context 'with insert' do
+ let(:payload) { { sql: 'WITH archived_rows AS (SELECT * FROM users WHERE archived = true) INSERT INTO products_log SELECT * FROM archived_rows' } }
- it_behaves_like 'read only query'
+ it_behaves_like 'track executed query'
+ end
end
- end
- context 'with delete sql event' do
- let(:payload) { { sql: 'DELETE FROM users where id = 10' } }
+ context 'with delete sql event' do
+ let(:payload) { { sql: 'DELETE FROM users where id = 10' } }
- it_behaves_like 'write query'
- end
+ it_behaves_like 'track executed query'
+ end
- context 'with insert sql event' do
- let(:payload) { { sql: 'INSERT INTO project_ci_cd_settings (project_id) SELECT id FROM projects' } }
+ context 'with insert sql event' do
+ let(:payload) { { sql: 'INSERT INTO project_ci_cd_settings (project_id) SELECT id FROM projects' } }
- it_behaves_like 'write query'
- end
+ it_behaves_like 'track executed query'
+ end
- context 'with update sql event' do
- let(:payload) { { sql: 'UPDATE users SET admin = true WHERE id = 10' } }
+ context 'with update sql event' do
+ let(:payload) { { sql: 'UPDATE users SET admin = true WHERE id = 10' } }
- it_behaves_like 'write query'
+ it_behaves_like 'track executed query'
+ end
end
- context 'with cached payload ' do
- let(:payload) do
+ context 'with cached query' do
+ let(:expected_counters) do
{
- sql: 'SELECT * FROM users WHERE id = 10',
- cached: true
+ db_count: 1,
+ db_write_count: 0,
+ db_cached_count: 1
}
end
- it_behaves_like 'cached query'
- end
+ context 'with cached payload ' do
+ let(:payload) do
+ {
+ sql: 'SELECT * FROM users WHERE id = 10',
+ cached: true
+ }
+ end
- context 'with cached payload name' do
- let(:payload) do
- {
- sql: 'SELECT * FROM users WHERE id = 10',
- name: 'CACHE'
- }
+ it_behaves_like 'track executed query'
end
- it_behaves_like 'cached query'
+ context 'with cached payload name' do
+ let(:payload) do
+ {
+ sql: 'SELECT * FROM users WHERE id = 10',
+ name: 'CACHE'
+ }
+ end
+
+ it_behaves_like 'track executed query'
+ end
end
context 'events are internal to Rails or irrelevant' do
@@ -215,4 +230,54 @@ describe Gitlab::Metrics::Subscribers::ActiveRecord do
end
end
end
+
+ describe 'self.db_counter_payload' do
+ before do
+ allow(subscriber).to receive(:current_transaction)
+ .at_least(:once)
+ .and_return(transaction)
+ end
+
+ context 'when RequestStore is enabled', :request_store do
+ context 'when query is executed' do
+ let(:expected_payload) do
+ {
+ db_count: 1,
+ db_cached_count: 0,
+ db_write_count: 0
+ }
+ end
+
+ it 'returns correct payload' do
+ subscriber.sql(event)
+
+ expect(described_class.db_counter_payload).to eq(expected_payload)
+ end
+ end
+
+ context 'when query is not executed' do
+ let(:expected_payload) do
+ {
+ db_count: 0,
+ db_cached_count: 0,
+ db_write_count: 0
+ }
+ end
+
+ it 'returns correct payload' do
+ expect(described_class.db_counter_payload).to eq(expected_payload)
+ end
+ end
+ end
+
+ context 'when RequestStore is disabled' do
+ let(:expected_payload) { {} }
+
+ it 'returns empty payload' do
+ subscriber.sql(event)
+
+ expect(described_class.db_counter_payload).to eq(expected_payload)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
index ab0d89b2683..f7ac719c16a 100644
--- a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Subscribers::RailsCache do
+RSpec.describe Gitlab::Metrics::Subscribers::RailsCache do
let(:env) { {} }
let(:transaction) { Gitlab::Metrics::WebTransaction.new(env) }
let(:subscriber) { described_class.new }
diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb
index abb6a0096d6..720bd5d79b3 100644
--- a/spec/lib/gitlab/metrics/system_spec.rb
+++ b/spec/lib/gitlab/metrics/system_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::System do
+RSpec.describe Gitlab::Metrics::System do
context 'when /proc files exist' do
# Fixtures pulled from:
# Linux carbon 5.3.0-7648-generic #41~1586789791~19.10~9593806-Ubuntu SMP Mon Apr 13 17:50:40 UTC x86_64 x86_64 x86_64 GNU/Linux
diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb
index 693ec3cb7e7..e64179bd5c1 100644
--- a/spec/lib/gitlab/metrics/transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/transaction_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Transaction do
+RSpec.describe Gitlab::Metrics::Transaction do
let(:transaction) { described_class.new }
let(:sensitive_tags) do
@@ -114,15 +114,4 @@ describe Gitlab::Metrics::Transaction do
transaction.set(:meow, 1)
end
end
-
- describe '#get' do
- let(:prometheus_metric) { instance_double(Prometheus::Client::Counter, get: nil) }
-
- it 'gets a metric' do
- expect(described_class).to receive(:fetch_metric).with(:counter, :gitlab_transaction_meow_total).and_return(prometheus_metric)
- expect(prometheus_metric).to receive(:get)
-
- transaction.get(:meow, :counter)
- end
- end
end
diff --git a/spec/lib/gitlab/metrics/web_transaction_spec.rb b/spec/lib/gitlab/metrics/web_transaction_spec.rb
index 47f1bd3bd10..12e98089066 100644
--- a/spec/lib/gitlab/metrics/web_transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/web_transaction_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::WebTransaction do
+RSpec.describe Gitlab::Metrics::WebTransaction do
let(:env) { {} }
let(:transaction) { described_class.new(env) }
let(:prometheus_metric) { double("prometheus metric") }
@@ -70,6 +70,9 @@ describe Gitlab::Metrics::WebTransaction do
end
describe '#labels' do
+ let(:request) { double(:request, format: double(:format, ref: :html)) }
+ let(:controller_class) { double(:controller_class, name: 'TestController') }
+
context 'when request goes to Grape endpoint' do
before do
route = double(:route, request_method: 'GET', path: '/:version/projects/:id/archive(.:format)')
@@ -77,8 +80,13 @@ describe Gitlab::Metrics::WebTransaction do
env['api.endpoint'] = endpoint
end
+
it 'provides labels with the method and path of the route in the grape endpoint' do
- expect(transaction.labels).to eq({ controller: 'Grape', action: 'GET /projects/:id/archive' })
+ expect(transaction.labels).to eq({ controller: 'Grape', action: 'GET /projects/:id/archive', feature_category: '' })
+ end
+
+ it 'contains only the labels defined for transactions' do
+ expect(transaction.labels.keys).to contain_exactly(*described_class.superclass::BASE_LABELS.keys)
end
it 'does not provide labels if route infos are missing' do
@@ -92,24 +100,25 @@ describe Gitlab::Metrics::WebTransaction do
end
context 'when request goes to ActionController' do
- let(:request) { double(:request, format: double(:format, ref: :html)) }
-
before do
- klass = double(:klass, name: 'TestController')
- controller = double(:controller, class: klass, action_name: 'show', request: request)
+ controller = double(:controller, class: controller_class, action_name: 'show', request: request)
env['action_controller.instance'] = controller
end
it 'tags a transaction with the name and action of a controller' do
- expect(transaction.labels).to eq({ controller: 'TestController', action: 'show' })
+ expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: '' })
+ end
+
+ it 'contains only the labels defined for transactions' do
+ expect(transaction.labels.keys).to contain_exactly(*described_class.superclass::BASE_LABELS.keys)
end
context 'when the request content type is not :html' do
let(:request) { double(:request, format: double(:format, ref: :json)) }
it 'appends the mime type to the transaction action' do
- expect(transaction.labels).to eq({ controller: 'TestController', action: 'show.json' })
+ expect(transaction.labels).to eq({ controller: 'TestController', action: 'show.json', feature_category: '' })
end
end
@@ -117,7 +126,14 @@ describe Gitlab::Metrics::WebTransaction do
let(:request) { double(:request, format: double(:format, ref: 'http://example.com')) }
it 'does not append the MIME type to the transaction action' do
- expect(transaction.labels).to eq({ controller: 'TestController', action: 'show' })
+ expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: '' })
+ end
+ end
+
+ context 'when the feature category is known' do
+ it 'includes it in the feature category label' do
+ expect(controller_class).to receive(:feature_category_for_action).with('show').and_return(:source_code_management)
+ expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: "source_code_management" })
end
end
end
diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb
index 2ebe1958487..bdf72a3c288 100644
--- a/spec/lib/gitlab/metrics_spec.rb
+++ b/spec/lib/gitlab/metrics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics do
+RSpec.describe Gitlab::Metrics do
include StubENV
describe '.settings' do
diff --git a/spec/lib/gitlab/middleware/basic_health_check_spec.rb b/spec/lib/gitlab/middleware/basic_health_check_spec.rb
index 07fda691ac8..0ca96de38da 100644
--- a/spec/lib/gitlab/middleware/basic_health_check_spec.rb
+++ b/spec/lib/gitlab/middleware/basic_health_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Middleware::BasicHealthCheck do
+RSpec.describe Gitlab::Middleware::BasicHealthCheck do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
let(:env) { {} }
diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb
index 43a489f6df0..1fffef53a82 100644
--- a/spec/lib/gitlab/middleware/go_spec.rb
+++ b/spec/lib/gitlab/middleware/go_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Middleware::Go do
+RSpec.describe Gitlab::Middleware::Go do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
let(:env) do
diff --git a/spec/lib/gitlab/middleware/handle_ip_spoof_attack_error_spec.rb b/spec/lib/gitlab/middleware/handle_ip_spoof_attack_error_spec.rb
index ccfc5e93887..553ee589c62 100644
--- a/spec/lib/gitlab/middleware/handle_ip_spoof_attack_error_spec.rb
+++ b/spec/lib/gitlab/middleware/handle_ip_spoof_attack_error_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Middleware::HandleIpSpoofAttackError do
+RSpec.describe Gitlab::Middleware::HandleIpSpoofAttackError do
let(:spoof_error) { ActionDispatch::RemoteIp::IpSpoofAttackError.new('sensitive') }
let(:standard_error) { StandardError.new('error') }
let(:app) { -> (env) { env.is_a?(Exception) ? raise(env) : env } }
diff --git a/spec/lib/gitlab/middleware/multipart_spec.rb b/spec/lib/gitlab/middleware/multipart_spec.rb
index 705164d5445..3b64fe335e8 100644
--- a/spec/lib/gitlab/middleware/multipart_spec.rb
+++ b/spec/lib/gitlab/middleware/multipart_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require 'tempfile'
-describe Gitlab::Middleware::Multipart do
+RSpec.describe Gitlab::Middleware::Multipart do
include_context 'multipart middleware context'
RSpec.shared_examples_for 'multipart upload files' do
@@ -232,4 +232,82 @@ describe Gitlab::Middleware::Multipart do
middleware.call(env)
end
end
+
+ describe '#call' do
+ context 'with packages storage' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:storage_path) { 'shared/packages' }
+
+ RSpec.shared_examples 'allowing the multipart upload' do
+ it 'allows files to be uploaded' do
+ with_tmp_dir('tmp/uploads', storage_path) do |dir, env|
+ allow(Packages::PackageFileUploader).to receive(:root).and_return(File.join(dir, storage_path))
+
+ expect(app).to receive(:call) do |env|
+ expect(get_params(env)['file']).to be_a(::UploadedFile)
+ end
+
+ middleware.call(env)
+ end
+ end
+ end
+
+ RSpec.shared_examples 'not allowing the multipart upload when package upload path is used' do
+ it 'does not allow files to be uploaded' do
+ with_tmp_dir('tmp/uploads', storage_path) do |dir, env|
+ # with_tmp_dir sets the same workhorse_upload_path for all Uploaders,
+ # so we have to prevent JobArtifactUploader and LfsObjectUploader to
+ # allow the tested path
+ allow(JobArtifactUploader).to receive(:workhorse_upload_path).and_return(Dir.tmpdir)
+ allow(LfsObjectUploader).to receive(:workhorse_upload_path).and_return(Dir.tmpdir)
+
+ status, headers, body = middleware.call(env)
+
+ expect(status).to eq(400)
+ expect(headers).to eq({ 'Content-Type' => 'text/plain' })
+ expect(body).to start_with('insecure path used')
+ end
+ end
+ end
+
+ RSpec.shared_examples 'adding package storage to multipart allowed paths' do
+ before do
+ expect(::Packages::PackageFileUploader).to receive(:workhorse_upload_path).and_call_original
+ end
+
+ it_behaves_like 'allowing the multipart upload'
+ end
+
+ RSpec.shared_examples 'not adding package storage to multipart allowed paths' do
+ before do
+ expect(::Packages::PackageFileUploader).not_to receive(:workhorse_upload_path)
+ end
+
+ it_behaves_like 'not allowing the multipart upload when package upload path is used'
+ end
+
+ where(:object_storage_enabled, :direct_upload_enabled, :example_name) do
+ false | true | 'adding package storage to multipart allowed paths'
+ false | false | 'adding package storage to multipart allowed paths'
+ true | true | 'not adding package storage to multipart allowed paths'
+ true | false | 'adding package storage to multipart allowed paths'
+ end
+
+ with_them do
+ before do
+ stub_config(packages: {
+ enabled: true,
+ object_store: {
+ enabled: object_storage_enabled,
+ direct_upload: direct_upload_enabled
+ },
+ storage_path: storage_path
+ })
+ end
+
+ it_behaves_like params[:example_name]
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb b/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb
index 4f21bd14122..8f9b0aec9eb 100644
--- a/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb
+++ b/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Middleware::RailsQueueDuration do
+RSpec.describe Gitlab::Middleware::RailsQueueDuration do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
let(:env) { {} }
diff --git a/spec/lib/gitlab/middleware/read_only_spec.rb b/spec/lib/gitlab/middleware/read_only_spec.rb
index c7e9b38e3ca..3bdf2a5077f 100644
--- a/spec/lib/gitlab/middleware/read_only_spec.rb
+++ b/spec/lib/gitlab/middleware/read_only_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Middleware::ReadOnly do
+RSpec.describe Gitlab::Middleware::ReadOnly do
include Rack::Test::Methods
using RSpec::Parameterized::TableSyntax
diff --git a/spec/lib/gitlab/middleware/release_env_spec.rb b/spec/lib/gitlab/middleware/release_env_spec.rb
index 3ca40f4ebd0..ca0ec0b9d83 100644
--- a/spec/lib/gitlab/middleware/release_env_spec.rb
+++ b/spec/lib/gitlab/middleware/release_env_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Middleware::ReleaseEnv do
+RSpec.describe Gitlab::Middleware::ReleaseEnv do
let(:inner_app) { double(:app, call: 'yay') }
let(:app) { described_class.new(inner_app) }
let(:env) { { 'action_controller.instance' => 'something' } }
diff --git a/spec/lib/gitlab/middleware/request_context_spec.rb b/spec/lib/gitlab/middleware/request_context_spec.rb
index 1ed06a97c1e..431f4453e37 100644
--- a/spec/lib/gitlab/middleware/request_context_spec.rb
+++ b/spec/lib/gitlab/middleware/request_context_spec.rb
@@ -4,7 +4,7 @@ require 'rack'
require 'request_store'
require_relative '../../../support/helpers/next_instance_of'
-describe Gitlab::Middleware::RequestContext do
+RSpec.describe Gitlab::Middleware::RequestContext do
include NextInstanceOf
let(:app) { -> (env) {} }
diff --git a/spec/lib/gitlab/middleware/same_site_cookies_spec.rb b/spec/lib/gitlab/middleware/same_site_cookies_spec.rb
index 0cf1028a930..7c5262ca318 100644
--- a/spec/lib/gitlab/middleware/same_site_cookies_spec.rb
+++ b/spec/lib/gitlab/middleware/same_site_cookies_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Middleware::SameSiteCookies do
+RSpec.describe Gitlab::Middleware::SameSiteCookies do
include Rack::Test::Methods
let(:mock_app) do
diff --git a/spec/lib/gitlab/monitor/demo_projects_spec.rb b/spec/lib/gitlab/monitor/demo_projects_spec.rb
index 92024a3f9c1..262c78eb62e 100644
--- a/spec/lib/gitlab/monitor/demo_projects_spec.rb
+++ b/spec/lib/gitlab/monitor/demo_projects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Monitor::DemoProjects do
+RSpec.describe Gitlab::Monitor::DemoProjects do
describe '#primary_keys' do
subject { described_class.primary_keys }
diff --git a/spec/lib/gitlab/multi_collection_paginator_spec.rb b/spec/lib/gitlab/multi_collection_paginator_spec.rb
index f2049884b83..c7c8f4f969f 100644
--- a/spec/lib/gitlab/multi_collection_paginator_spec.rb
+++ b/spec/lib/gitlab/multi_collection_paginator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::MultiCollectionPaginator do
+RSpec.describe Gitlab::MultiCollectionPaginator do
subject(:paginator) { described_class.new(Project.all.order(:id), Group.all.order(:id), per_page: 3) }
it 'combines both collections' do
diff --git a/spec/lib/gitlab/multi_destination_logger_spec.rb b/spec/lib/gitlab/multi_destination_logger_spec.rb
index 7acd7906a26..e0d76afd9bf 100644
--- a/spec/lib/gitlab/multi_destination_logger_spec.rb
+++ b/spec/lib/gitlab/multi_destination_logger_spec.rb
@@ -31,7 +31,7 @@ class EmptyLogger < Gitlab::MultiDestinationLogger
end
end
-describe Gitlab::MultiDestinationLogger do
+RSpec.describe Gitlab::MultiDestinationLogger do
after(:all) do
TestLogger.loggers.each do |logger|
log_file_path = "#{Rails.root}/log/#{logger.file_name}"
diff --git a/spec/lib/gitlab/namespaced_session_store_spec.rb b/spec/lib/gitlab/namespaced_session_store_spec.rb
index e177c44ad67..a569c86960c 100644
--- a/spec/lib/gitlab/namespaced_session_store_spec.rb
+++ b/spec/lib/gitlab/namespaced_session_store_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::NamespacedSessionStore do
+RSpec.describe Gitlab::NamespacedSessionStore do
let(:key) { :some_key }
context 'current session' do
diff --git a/spec/lib/gitlab/no_cache_headers_spec.rb b/spec/lib/gitlab/no_cache_headers_spec.rb
index c7a73f0e2dc..513a39978e6 100644
--- a/spec/lib/gitlab/no_cache_headers_spec.rb
+++ b/spec/lib/gitlab/no_cache_headers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::NoCacheHeaders do
+RSpec.describe Gitlab::NoCacheHeaders do
before do
stub_const('NoCacheTester', Class.new)
NoCacheTester.class_eval do
diff --git a/spec/lib/gitlab/noteable_metadata_spec.rb b/spec/lib/gitlab/noteable_metadata_spec.rb
index b12a1825f04..98c07fc9f62 100644
--- a/spec/lib/gitlab/noteable_metadata_spec.rb
+++ b/spec/lib/gitlab/noteable_metadata_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::NoteableMetadata do
+RSpec.describe Gitlab::NoteableMetadata do
subject { Class.new { include Gitlab::NoteableMetadata }.new }
it 'returns an empty Hash if an empty collection is provided' do
diff --git a/spec/lib/gitlab/null_request_store_spec.rb b/spec/lib/gitlab/null_request_store_spec.rb
index c023dac53ad..f600af2e31f 100644
--- a/spec/lib/gitlab/null_request_store_spec.rb
+++ b/spec/lib/gitlab/null_request_store_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::NullRequestStore do
+RSpec.describe Gitlab::NullRequestStore do
let(:null_store) { described_class.new }
describe '#store' do
diff --git a/spec/lib/gitlab/object_hierarchy_spec.rb b/spec/lib/gitlab/object_hierarchy_spec.rb
index b72aeb6d601..ef2d4fa0cbf 100644
--- a/spec/lib/gitlab/object_hierarchy_spec.rb
+++ b/spec/lib/gitlab/object_hierarchy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ObjectHierarchy do
+RSpec.describe Gitlab::ObjectHierarchy do
let!(:parent) { create(:group) }
let!(:child1) { create(:group, parent: parent) }
let!(:child2) { create(:group, parent: child1) }
diff --git a/spec/lib/gitlab/octokit/middleware_spec.rb b/spec/lib/gitlab/octokit/middleware_spec.rb
index 8aa6d17ac9e..bc4d95738c7 100644
--- a/spec/lib/gitlab/octokit/middleware_spec.rb
+++ b/spec/lib/gitlab/octokit/middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Octokit::Middleware do
+RSpec.describe Gitlab::Octokit::Middleware do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
diff --git a/spec/lib/gitlab/omniauth_initializer_spec.rb b/spec/lib/gitlab/omniauth_initializer_spec.rb
index 4afe4545891..a38dffcfce0 100644
--- a/spec/lib/gitlab/omniauth_initializer_spec.rb
+++ b/spec/lib/gitlab/omniauth_initializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::OmniauthInitializer do
+RSpec.describe Gitlab::OmniauthInitializer do
let(:devise_config) { class_double(Devise) }
subject { described_class.new(devise_config) }
diff --git a/spec/lib/gitlab/omniauth_logging/json_formatter_spec.rb b/spec/lib/gitlab/omniauth_logging/json_formatter_spec.rb
index 36405daed5a..f65b247d5d7 100644
--- a/spec/lib/gitlab/omniauth_logging/json_formatter_spec.rb
+++ b/spec/lib/gitlab/omniauth_logging/json_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::OmniauthLogging::JSONFormatter do
+RSpec.describe Gitlab::OmniauthLogging::JSONFormatter do
it "generates log in json format" do
Timecop.freeze(Time.utc(2019, 12, 04, 9, 10, 11, 123456)) do
expect(subject.call(:info, Time.now, 'omniauth', 'log message'))
diff --git a/spec/lib/gitlab/optimistic_locking_spec.rb b/spec/lib/gitlab/optimistic_locking_spec.rb
index 9dfcb775dfa..0862a9c880e 100644
--- a/spec/lib/gitlab/optimistic_locking_spec.rb
+++ b/spec/lib/gitlab/optimistic_locking_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::OptimisticLocking do
+RSpec.describe Gitlab::OptimisticLocking do
let!(:pipeline) { create(:ci_pipeline) }
let!(:pipeline2) { Ci::Pipeline.find(pipeline.id) }
diff --git a/spec/lib/gitlab/other_markup_spec.rb b/spec/lib/gitlab/other_markup_spec.rb
index b5cf5b0999d..26e60251abb 100644
--- a/spec/lib/gitlab/other_markup_spec.rb
+++ b/spec/lib/gitlab/other_markup_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::OtherMarkup do
+RSpec.describe Gitlab::OtherMarkup do
let(:context) { {} }
context "XSS Checks" do
diff --git a/spec/lib/gitlab/otp_key_rotator_spec.rb b/spec/lib/gitlab/otp_key_rotator_spec.rb
index f5a567d5ea0..e328b190db4 100644
--- a/spec/lib/gitlab/otp_key_rotator_spec.rb
+++ b/spec/lib/gitlab/otp_key_rotator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::OtpKeyRotator do
+RSpec.describe Gitlab::OtpKeyRotator do
let(:file) { Tempfile.new("otp-key-rotator-test") }
let(:filename) { file.path }
let(:old_key) { Gitlab::Application.secrets.otp_key_base }
diff --git a/spec/lib/gitlab/pages_spec.rb b/spec/lib/gitlab/pages_spec.rb
index 5889689cb81..9f85efd56e6 100644
--- a/spec/lib/gitlab/pages_spec.rb
+++ b/spec/lib/gitlab/pages_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Pages do
+RSpec.describe Gitlab::Pages do
using RSpec::Parameterized::TableSyntax
let(:pages_secret) { SecureRandom.random_bytes(Gitlab::Pages::SECRET_LENGTH) }
diff --git a/spec/lib/gitlab/pagination/keyset/page_spec.rb b/spec/lib/gitlab/pagination/keyset/page_spec.rb
index c5ca27231d8..3f4ef192b5a 100644
--- a/spec/lib/gitlab/pagination/keyset/page_spec.rb
+++ b/spec/lib/gitlab/pagination/keyset/page_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Pagination::Keyset::Page do
+RSpec.describe Gitlab::Pagination::Keyset::Page do
describe '#per_page' do
it 'limits to a maximum of 100 records per page' do
per_page = described_class.new(per_page: 101).per_page
diff --git a/spec/lib/gitlab/pagination/keyset/pager_spec.rb b/spec/lib/gitlab/pagination/keyset/pager_spec.rb
index 3ad1bee7225..a3169142279 100644
--- a/spec/lib/gitlab/pagination/keyset/pager_spec.rb
+++ b/spec/lib/gitlab/pagination/keyset/pager_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Pagination::Keyset::Pager do
+RSpec.describe Gitlab::Pagination::Keyset::Pager do
let(:relation) { Project.all.order(id: :asc) }
let(:request) { double('request', page: page, apply_headers: nil) }
let(:page) { Gitlab::Pagination::Keyset::Page.new(order_by: { id: :asc }, per_page: 3) }
diff --git a/spec/lib/gitlab/pagination/keyset/request_context_spec.rb b/spec/lib/gitlab/pagination/keyset/request_context_spec.rb
index d6d5340f38b..d4255176a4e 100644
--- a/spec/lib/gitlab/pagination/keyset/request_context_spec.rb
+++ b/spec/lib/gitlab/pagination/keyset/request_context_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Pagination::Keyset::RequestContext do
+RSpec.describe Gitlab::Pagination::Keyset::RequestContext do
let(:request) { double('request', params: params) }
describe '#page' do
diff --git a/spec/lib/gitlab/pagination/keyset_spec.rb b/spec/lib/gitlab/pagination/keyset_spec.rb
index 0ac40080872..81dc40b35d5 100644
--- a/spec/lib/gitlab/pagination/keyset_spec.rb
+++ b/spec/lib/gitlab/pagination/keyset_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Pagination::Keyset do
+RSpec.describe Gitlab::Pagination::Keyset do
describe '.available_for_type?' do
subject { described_class }
diff --git a/spec/lib/gitlab/pagination/offset_pagination_spec.rb b/spec/lib/gitlab/pagination/offset_pagination_spec.rb
index 9c7dd385726..be20f0194f7 100644
--- a/spec/lib/gitlab/pagination/offset_pagination_spec.rb
+++ b/spec/lib/gitlab/pagination/offset_pagination_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Pagination::OffsetPagination do
+RSpec.describe Gitlab::Pagination::OffsetPagination do
let(:resource) { Project.all }
let(:custom_port) { 8080 }
let(:incoming_api_projects_url) { "#{Gitlab.config.gitlab.url}:#{custom_port}/api/v4/projects" }
diff --git a/spec/lib/gitlab/patch/action_dispatch_journey_formatter_spec.rb b/spec/lib/gitlab/patch/action_dispatch_journey_formatter_spec.rb
index b1c7f73489d..ca74f7573f3 100644
--- a/spec/lib/gitlab/patch/action_dispatch_journey_formatter_spec.rb
+++ b/spec/lib/gitlab/patch/action_dispatch_journey_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Patch::ActionDispatchJourneyFormatter do
+RSpec.describe Gitlab::Patch::ActionDispatchJourneyFormatter do
let(:group) { create(:group) }
let(:project) { create(:project, namespace: group) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
diff --git a/spec/lib/gitlab/patch/draw_route_spec.rb b/spec/lib/gitlab/patch/draw_route_spec.rb
index 4009b903dc3..4d1c7bf9fcf 100644
--- a/spec/lib/gitlab/patch/draw_route_spec.rb
+++ b/spec/lib/gitlab/patch/draw_route_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Patch::DrawRoute do
+RSpec.describe Gitlab::Patch::DrawRoute do
subject do
Class.new do
include Gitlab::Patch::DrawRoute
diff --git a/spec/lib/gitlab/patch/prependable_spec.rb b/spec/lib/gitlab/patch/prependable_spec.rb
index 255324f89d5..8feab57a8f3 100644
--- a/spec/lib/gitlab/patch/prependable_spec.rb
+++ b/spec/lib/gitlab/patch/prependable_spec.rb
@@ -5,7 +5,7 @@ require 'fast_spec_helper'
# Patching ActiveSupport::Concern
require_relative '../../../../config/initializers/0_as_concern'
-describe Gitlab::Patch::Prependable do
+RSpec.describe Gitlab::Patch::Prependable do
before do
@prepended_modules = []
end
diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb
index ac506c49100..7cecc29afa4 100644
--- a/spec/lib/gitlab/path_regex_spec.rb
+++ b/spec/lib/gitlab/path_regex_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PathRegex do
+RSpec.describe Gitlab::PathRegex do
let(:starting_with_namespace) { %r{^/\*namespace_id/:(project_)?id} }
let(:non_param_parts) { %r{[^:*][a-z\-_/]*} }
let(:any_other_path_part) { %r{[a-z\-_/:]*} }
diff --git a/spec/lib/gitlab/performance_bar/with_top_level_warnings_spec.rb b/spec/lib/gitlab/performance_bar/with_top_level_warnings_spec.rb
index 3b92261f0fe..735f04b3bce 100644
--- a/spec/lib/gitlab/performance_bar/with_top_level_warnings_spec.rb
+++ b/spec/lib/gitlab/performance_bar/with_top_level_warnings_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require 'rspec-parameterized'
-describe Gitlab::PerformanceBar::WithTopLevelWarnings do
+RSpec.describe Gitlab::PerformanceBar::WithTopLevelWarnings do
using RSpec::Parameterized::TableSyntax
subject { Module.new }
diff --git a/spec/lib/gitlab/performance_bar_spec.rb b/spec/lib/gitlab/performance_bar_spec.rb
index 7b79cc82816..12916c41f0f 100644
--- a/spec/lib/gitlab/performance_bar_spec.rb
+++ b/spec/lib/gitlab/performance_bar_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PerformanceBar do
+RSpec.describe Gitlab::PerformanceBar do
it { expect(described_class.l1_cache_backend).to eq(Gitlab::ProcessMemoryCache.cache_backend) }
it { expect(described_class.l2_cache_backend).to eq(Rails.cache) }
diff --git a/spec/lib/gitlab/phabricator_import/cache/map_spec.rb b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
index 4935ef1bd90..0f760852a68 100644
--- a/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Cache::Map, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::PhabricatorImport::Cache::Map, :clean_gitlab_redis_cache do
let_it_be(:project) { create(:project) }
let(:redis) { Gitlab::Redis::Cache }
diff --git a/spec/lib/gitlab/phabricator_import/conduit/client_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/client_spec.rb
index 542b3cd060f..dad349f3255 100644
--- a/spec/lib/gitlab/phabricator_import/conduit/client_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/conduit/client_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Conduit::Client do
+RSpec.describe Gitlab::PhabricatorImport::Conduit::Client do
let(:client) do
described_class.new('https://see-ya-later.phabricator', 'api-token')
end
diff --git a/spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb
index 0d7714649b9..e655a39a28d 100644
--- a/spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Conduit::Maniphest do
+RSpec.describe Gitlab::PhabricatorImport::Conduit::Maniphest do
let(:maniphest) do
described_class.new(phabricator_url: 'https://see-ya-later.phabricator', api_token: 'api-token')
end
diff --git a/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb
index 1ffb811cbc1..c368b349a3c 100644
--- a/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Conduit::Response do
+RSpec.describe Gitlab::PhabricatorImport::Conduit::Response do
let(:response) { described_class.new(Gitlab::Json.parse(fixture_file('phabricator_responses/maniphest.search.json')))}
let(:error_response) { described_class.new(Gitlab::Json.parse(fixture_file('phabricator_responses/auth_failed.json'))) }
@@ -30,7 +30,7 @@ describe Gitlab::PhabricatorImport::Conduit::Response do
body: 'This is no JSON')
expect { described_class.parse!(fake_response) }
- .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /unexpected token at/)
+ .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /unexpected character/)
end
it 'returns a parsed response for valid input' do
diff --git a/spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb
index 2cc12ee0165..4e56dead5c0 100644
--- a/spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Conduit::TasksResponse do
+RSpec.describe Gitlab::PhabricatorImport::Conduit::TasksResponse do
let(:conduit_response) do
Gitlab::PhabricatorImport::Conduit::Response
.new(Gitlab::Json.parse(fixture_file('phabricator_responses/maniphest.search.json')))
diff --git a/spec/lib/gitlab/phabricator_import/conduit/user_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/user_spec.rb
index f3928f390bc..d38421c9405 100644
--- a/spec/lib/gitlab/phabricator_import/conduit/user_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/conduit/user_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Conduit::User do
+RSpec.describe Gitlab::PhabricatorImport::Conduit::User do
let(:user_client) do
described_class.new(phabricator_url: 'https://see-ya-later.phabricator', api_token: 'api-token')
end
diff --git a/spec/lib/gitlab/phabricator_import/conduit/users_response_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/users_response_spec.rb
index 999a986b73c..ebbb2c0598c 100644
--- a/spec/lib/gitlab/phabricator_import/conduit/users_response_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/conduit/users_response_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Conduit::UsersResponse do
+RSpec.describe Gitlab::PhabricatorImport::Conduit::UsersResponse do
let(:conduit_response) do
Gitlab::PhabricatorImport::Conduit::Response
.new(Gitlab::Json.parse(fixture_file('phabricator_responses/user.search.json')))
diff --git a/spec/lib/gitlab/phabricator_import/importer_spec.rb b/spec/lib/gitlab/phabricator_import/importer_spec.rb
index 2715b785379..e78024c35c1 100644
--- a/spec/lib/gitlab/phabricator_import/importer_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Importer do
+RSpec.describe Gitlab::PhabricatorImport::Importer do
it { expect(described_class).to be_async }
it "acts like it's importing repositories" do
diff --git a/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb b/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb
index 02dafd4bb3b..63ba575aea3 100644
--- a/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Issues::Importer do
+RSpec.describe Gitlab::PhabricatorImport::Issues::Importer do
let(:project) { create(:project) }
let(:response) do
diff --git a/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
index 79f11d7fae6..3cb15f08627 100644
--- a/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Issues::TaskImporter do
+RSpec.describe Gitlab::PhabricatorImport::Issues::TaskImporter do
let_it_be(:project) { create(:project) }
let(:task) do
Gitlab::PhabricatorImport::Representation::Task.new(
diff --git a/spec/lib/gitlab/phabricator_import/project_creator_spec.rb b/spec/lib/gitlab/phabricator_import/project_creator_spec.rb
index 0b6a71290ed..016aa0abe4d 100644
--- a/spec/lib/gitlab/phabricator_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/project_creator_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::PhabricatorImport::ProjectCreator do
+RSpec.describe Gitlab::PhabricatorImport::ProjectCreator do
let(:user) { create(:user) }
let(:params) do
{ path: 'new-phab-import',
diff --git a/spec/lib/gitlab/phabricator_import/representation/task_spec.rb b/spec/lib/gitlab/phabricator_import/representation/task_spec.rb
index 5603a6961d6..25a52af3a7a 100644
--- a/spec/lib/gitlab/phabricator_import/representation/task_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/representation/task_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Representation::Task do
+RSpec.describe Gitlab::PhabricatorImport::Representation::Task do
subject(:task) do
described_class.new(
{
diff --git a/spec/lib/gitlab/phabricator_import/representation/user_spec.rb b/spec/lib/gitlab/phabricator_import/representation/user_spec.rb
index f52467a0cf1..f51be0f7d8d 100644
--- a/spec/lib/gitlab/phabricator_import/representation/user_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/representation/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PhabricatorImport::Representation::User do
+RSpec.describe Gitlab::PhabricatorImport::Representation::User do
subject(:user) do
described_class.new(
{
diff --git a/spec/lib/gitlab/phabricator_import/user_finder_spec.rb b/spec/lib/gitlab/phabricator_import/user_finder_spec.rb
index f260e38b7c8..e63ad6e6626 100644
--- a/spec/lib/gitlab/phabricator_import/user_finder_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/user_finder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PhabricatorImport::UserFinder, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::PhabricatorImport::UserFinder, :clean_gitlab_redis_cache do
let(:project) { create(:project, namespace: create(:group)) }
subject(:finder) { described_class.new(project, %w[first-phid second-phid]) }
diff --git a/spec/lib/gitlab/phabricator_import/worker_state_spec.rb b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
index 51514dd0ffd..4a07e28440f 100644
--- a/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PhabricatorImport::WorkerState, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::PhabricatorImport::WorkerState, :clean_gitlab_redis_shared_state do
subject(:state) { described_class.new('weird-project-id') }
let(:key) { 'phabricator-import/jobs/project-weird-project-id/job-count' }
diff --git a/spec/lib/gitlab/polling_interval_spec.rb b/spec/lib/gitlab/polling_interval_spec.rb
index 31569b2c51e..bf0eaf80d3b 100644
--- a/spec/lib/gitlab/polling_interval_spec.rb
+++ b/spec/lib/gitlab/polling_interval_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PollingInterval do
+RSpec.describe Gitlab::PollingInterval do
let(:polling_interval) { described_class }
describe '.set_header' do
diff --git a/spec/lib/gitlab/popen/runner_spec.rb b/spec/lib/gitlab/popen/runner_spec.rb
index de19106eaee..5f72852c63e 100644
--- a/spec/lib/gitlab/popen/runner_spec.rb
+++ b/spec/lib/gitlab/popen/runner_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Popen::Runner do
+RSpec.describe Gitlab::Popen::Runner do
subject { described_class.new }
describe '#run' do
diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb
index b398381a7e0..891482a5f17 100644
--- a/spec/lib/gitlab/popen_spec.rb
+++ b/spec/lib/gitlab/popen_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Popen do
+RSpec.describe Gitlab::Popen do
let(:path) { Rails.root.join('tmp').to_s }
before do
diff --git a/spec/lib/gitlab/private_commit_email_spec.rb b/spec/lib/gitlab/private_commit_email_spec.rb
index 7b7a0f7c0ca..502cc82fd8d 100644
--- a/spec/lib/gitlab/private_commit_email_spec.rb
+++ b/spec/lib/gitlab/private_commit_email_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PrivateCommitEmail do
+RSpec.describe Gitlab::PrivateCommitEmail do
let(:hostname) { Gitlab::CurrentSettings.current_application_settings.commit_email_hostname }
let(:id) { 1 }
let(:valid_email) { "#{id}-foo@#{hostname}" }
diff --git a/spec/lib/gitlab/process_memory_cache/helper_spec.rb b/spec/lib/gitlab/process_memory_cache/helper_spec.rb
index 890642b1d5e..27d7fd0bdcf 100644
--- a/spec/lib/gitlab/process_memory_cache/helper_spec.rb
+++ b/spec/lib/gitlab/process_memory_cache/helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ProcessMemoryCache::Helper, :use_clean_rails_memory_store_caching do
+RSpec.describe Gitlab::ProcessMemoryCache::Helper, :use_clean_rails_memory_store_caching do
let(:minimal_test_class) do
Class.new do
include Gitlab::ProcessMemoryCache::Helper
diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb
index 6440f74a49a..89917e515d0 100644
--- a/spec/lib/gitlab/profiler_spec.rb
+++ b/spec/lib/gitlab/profiler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Profiler do
+RSpec.describe Gitlab::Profiler do
let(:null_logger) { Logger.new('/dev/null') }
let(:private_token) { 'private' }
@@ -229,7 +229,6 @@ describe Gitlab::Profiler do
.map { |(total)| total.to_f }
expect(total_times).to eq(total_times.sort.reverse)
- expect(total_times).not_to eq(total_times.uniq)
end
end
diff --git a/spec/lib/gitlab/project_authorizations_spec.rb b/spec/lib/gitlab/project_authorizations_spec.rb
index 261e44bc5fa..5ff07dcec4f 100644
--- a/spec/lib/gitlab/project_authorizations_spec.rb
+++ b/spec/lib/gitlab/project_authorizations_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ProjectAuthorizations do
+RSpec.describe Gitlab::ProjectAuthorizations do
def map_access_levels(rows)
rows.each_with_object({}) do |row, hash|
hash[row.project_id] = row.access_level
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index aa52949ed60..75a3fe06632 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ProjectSearchResults do
+RSpec.describe Gitlab::ProjectSearchResults do
include SearchHelpers
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb
index 35f79042df0..fa45c605b1b 100644
--- a/spec/lib/gitlab/project_template_spec.rb
+++ b/spec/lib/gitlab/project_template_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ProjectTemplate do
+RSpec.describe Gitlab::ProjectTemplate do
describe '.all' do
it 'returns all templates' do
expected = %w[
@@ -10,7 +10,7 @@ describe Gitlab::ProjectTemplate do
gomicro gatsby hugo jekyll plainhtml gitbook
hexo sse_middleman nfhugo nfjekyll nfplainhtml
nfgitbook nfhexo salesforcedx serverless_framework
- cluster_management
+ jsonnet cluster_management
]
expect(described_class.all).to be_an(Array)
diff --git a/spec/lib/gitlab/project_transfer_spec.rb b/spec/lib/gitlab/project_transfer_spec.rb
index d54817ea02b..87c4014264f 100644
--- a/spec/lib/gitlab/project_transfer_spec.rb
+++ b/spec/lib/gitlab/project_transfer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ProjectTransfer do
+RSpec.describe Gitlab::ProjectTransfer do
before do
@root_dir = File.join(Rails.root, "public", "uploads")
@project_transfer = described_class.new
diff --git a/spec/lib/gitlab/prometheus/adapter_spec.rb b/spec/lib/gitlab/prometheus/adapter_spec.rb
index afee95467fa..4762e4ad108 100644
--- a/spec/lib/gitlab/prometheus/adapter_spec.rb
+++ b/spec/lib/gitlab/prometheus/adapter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Prometheus::Adapter do
+RSpec.describe Gitlab::Prometheus::Adapter do
let_it_be(:project) { create(:project) }
let_it_be(:cluster, reload: true) { create(:cluster, :provided_by_user, environment_scope: '*', projects: [project]) }
diff --git a/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb b/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
index 3f97a69b5eb..3c7496cabd0 100644
--- a/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
+++ b/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Prometheus::AdditionalMetricsParser do
+RSpec.describe Gitlab::Prometheus::AdditionalMetricsParser do
include Prometheus::MetricBuilders
let(:parser_error_class) { Gitlab::Prometheus::ParsingError }
diff --git a/spec/lib/gitlab/prometheus/internal_spec.rb b/spec/lib/gitlab/prometheus/internal_spec.rb
index 884bdcb4e9b..1254610fe32 100644
--- a/spec/lib/gitlab/prometheus/internal_spec.rb
+++ b/spec/lib/gitlab/prometheus/internal_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Prometheus::Internal do
+RSpec.describe Gitlab::Prometheus::Internal do
let(:listen_address) { 'localhost:9090' }
let(:prometheus_settings) do
diff --git a/spec/lib/gitlab/prometheus/metric_group_spec.rb b/spec/lib/gitlab/prometheus/metric_group_spec.rb
index 787f14daf47..a68cdfe5fb2 100644
--- a/spec/lib/gitlab/prometheus/metric_group_spec.rb
+++ b/spec/lib/gitlab/prometheus/metric_group_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Prometheus::MetricGroup do
+RSpec.describe Gitlab::Prometheus::MetricGroup do
describe '.common_metrics' do
let!(:project_metric) { create(:prometheus_metric) }
let!(:common_metric_group_a) { create(:prometheus_metric, :common, group: :aws_elb) }
diff --git a/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb
index 55e89395452..8abc944eeb1 100644
--- a/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery do
+RSpec.describe Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery do
around do |example|
Timecop.freeze(Time.local(2008, 9, 1, 12, 0, 0)) { example.run }
end
diff --git a/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
index 95df8880b90..f5911963108 100644
--- a/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Prometheus::Queries::AdditionalMetricsEnvironmentQuery do
+RSpec.describe Gitlab::Prometheus::Queries::AdditionalMetricsEnvironmentQuery do
around do |example|
Timecop.freeze { example.run }
end
diff --git a/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
index 4af233291f6..4683c4eae28 100644
--- a/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Prometheus::Queries::DeploymentQuery do
+RSpec.describe Gitlab::Prometheus::Queries::DeploymentQuery do
let(:environment) { create(:environment, slug: 'environment-slug') }
let(:deployment) { create(:deployment, environment: environment) }
let(:client) { double('prometheus_client') }
diff --git a/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb b/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb
index 8eefd22bd29..ff48b9ada90 100644
--- a/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Prometheus::Queries::KnativeInvocationQuery do
+RSpec.describe Gitlab::Prometheus::Queries::KnativeInvocationQuery do
include PrometheusHelpers
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb b/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb
index 35034d814bf..117ca798022 100644
--- a/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Prometheus::Queries::MatchedMetricQuery do
+RSpec.describe Gitlab::Prometheus::Queries::MatchedMetricQuery do
include Prometheus::MetricBuilders
let(:metric_group_class) { Gitlab::Prometheus::MetricGroup }
diff --git a/spec/lib/gitlab/prometheus/queries/validate_query_spec.rb b/spec/lib/gitlab/prometheus/queries/validate_query_spec.rb
index 9c0170718f4..045c063ab34 100644
--- a/spec/lib/gitlab/prometheus/queries/validate_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/validate_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Prometheus::Queries::ValidateQuery do
+RSpec.describe Gitlab::Prometheus::Queries::ValidateQuery do
include PrometheusHelpers
let(:api_url) { 'https://prometheus.example.com' }
diff --git a/spec/lib/gitlab/prometheus/query_variables_spec.rb b/spec/lib/gitlab/prometheus/query_variables_spec.rb
index 7dfa4de35d6..1422d48152a 100644
--- a/spec/lib/gitlab/prometheus/query_variables_spec.rb
+++ b/spec/lib/gitlab/prometheus/query_variables_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Prometheus::QueryVariables do
+RSpec.describe Gitlab::Prometheus::QueryVariables do
describe '.call' do
let(:project) { environment.project }
let(:environment) { create(:environment) }
diff --git a/spec/lib/gitlab/prometheus_client_spec.rb b/spec/lib/gitlab/prometheus_client_spec.rb
index 749192e5795..242a0ced031 100644
--- a/spec/lib/gitlab/prometheus_client_spec.rb
+++ b/spec/lib/gitlab/prometheus_client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PrometheusClient do
+RSpec.describe Gitlab::PrometheusClient do
include PrometheusHelpers
subject { described_class.new('https://prometheus.example.com') }
@@ -32,7 +32,7 @@ describe Gitlab::PrometheusClient do
it 'raises error when status code not 200' do
stub_request(:get, subject.health_url).to_return(status: 500, body: '')
- expect { subject.healthy? }.to raise_error(Gitlab::PrometheusClient::Error)
+ expect { subject.healthy? }.to raise_error(Gitlab::PrometheusClient::UnexpectedResponseError)
end
end
@@ -41,41 +41,41 @@ describe Gitlab::PrometheusClient do
# - execute_query: A query call
shared_examples 'failure response' do
context 'when request returns 400 with an error message' do
- it 'raises a Gitlab::PrometheusClient::Error error' do
+ it 'raises a Gitlab::PrometheusClient::QueryError error' do
req_stub = stub_prometheus_request(query_url, status: 400, body: { error: 'bar!' })
expect { execute_query }
- .to raise_error(Gitlab::PrometheusClient::Error, 'bar!')
+ .to raise_error(Gitlab::PrometheusClient::QueryError, 'bar!')
expect(req_stub).to have_been_requested
end
end
context 'when request returns 400 without an error message' do
- it 'raises a Gitlab::PrometheusClient::Error error' do
+ it 'raises a Gitlab::PrometheusClient::QueryError error' do
req_stub = stub_prometheus_request(query_url, status: 400)
expect { execute_query }
- .to raise_error(Gitlab::PrometheusClient::Error, 'Bad data received')
+ .to raise_error(Gitlab::PrometheusClient::QueryError, 'Bad data received')
expect(req_stub).to have_been_requested
end
end
context 'when request returns 500' do
- it 'raises a Gitlab::PrometheusClient::Error error' do
+ it 'raises a Gitlab::PrometheusClient::UnexpectedResponseError error' do
req_stub = stub_prometheus_request(query_url, status: 500, body: { message: 'FAIL!' })
expect { execute_query }
- .to raise_error(Gitlab::PrometheusClient::Error, '500 - {"message":"FAIL!"}')
+ .to raise_error(Gitlab::PrometheusClient::UnexpectedResponseError, '500 - {"message":"FAIL!"}')
expect(req_stub).to have_been_requested
end
end
context 'when request returns non json data' do
- it 'raises a Gitlab::PrometheusClient::Error error' do
+ it 'raises a Gitlab::PrometheusClient::UnexpectedResponseError error' do
req_stub = stub_prometheus_request(query_url, status: 200, body: 'not json')
expect { execute_query }
- .to raise_error(Gitlab::PrometheusClient::Error, 'Parsing response failed')
+ .to raise_error(Gitlab::PrometheusClient::UnexpectedResponseError, 'Parsing response failed')
expect(req_stub).to have_been_requested
end
end
@@ -85,35 +85,35 @@ describe Gitlab::PrometheusClient do
let(:prometheus_url) {"https://prometheus.invalid.example.com/api/v1/query?query=1"}
shared_examples 'exceptions are raised' do
- it 'raises a Gitlab::PrometheusClient::Error error when a SocketError is rescued' do
+ it 'raises a Gitlab::PrometheusClient::ConnectionError error when a SocketError is rescued' do
req_stub = stub_prometheus_request_with_exception(prometheus_url, SocketError)
expect { subject }
- .to raise_error(Gitlab::PrometheusClient::Error, "Can't connect to #{prometheus_url}")
+ .to raise_error(Gitlab::PrometheusClient::ConnectionError, "Can't connect to #{prometheus_url}")
expect(req_stub).to have_been_requested
end
- it 'raises a Gitlab::PrometheusClient::Error error when a SSLError is rescued' do
+ it 'raises a Gitlab::PrometheusClient::ConnectionError error when a SSLError is rescued' do
req_stub = stub_prometheus_request_with_exception(prometheus_url, OpenSSL::SSL::SSLError)
expect { subject }
- .to raise_error(Gitlab::PrometheusClient::Error, "#{prometheus_url} contains invalid SSL data")
+ .to raise_error(Gitlab::PrometheusClient::ConnectionError, "#{prometheus_url} contains invalid SSL data")
expect(req_stub).to have_been_requested
end
- it 'raises a Gitlab::PrometheusClient::Error error when a Gitlab::HTTP::ResponseError is rescued' do
+ it 'raises a Gitlab::PrometheusClient::ConnectionError error when a Gitlab::HTTP::ResponseError is rescued' do
req_stub = stub_prometheus_request_with_exception(prometheus_url, Gitlab::HTTP::ResponseError)
expect { subject }
- .to raise_error(Gitlab::PrometheusClient::Error, "Network connection error")
+ .to raise_error(Gitlab::PrometheusClient::ConnectionError, "Network connection error")
expect(req_stub).to have_been_requested
end
- it 'raises a Gitlab::PrometheusClient::Error error when a Gitlab::HTTP::ResponseError with a code is rescued' do
+ it 'raises a Gitlab::PrometheusClient::ConnectionError error when a Gitlab::HTTP::ResponseError with a code is rescued' do
req_stub = stub_prometheus_request_with_exception(prometheus_url, Gitlab::HTTP::ResponseError.new(code: 400))
expect { subject }
- .to raise_error(Gitlab::PrometheusClient::Error, "Network connection error")
+ .to raise_error(Gitlab::PrometheusClient::ConnectionError, "Network connection error")
expect(req_stub).to have_been_requested
end
end
@@ -400,9 +400,9 @@ describe Gitlab::PrometheusClient do
context "without response code" do
let(:response_error) { Gitlab::HTTP::ResponseError }
- it 'raises PrometheusClient::Error' do
+ it 'raises PrometheusClient::ConnectionError' do
expect { subject.proxy('query', { query: prometheus_query }) }.to(
- raise_error(Gitlab::PrometheusClient::Error, 'Network connection error')
+ raise_error(Gitlab::PrometheusClient::ConnectionError, 'Network connection error')
)
end
end
diff --git a/spec/lib/gitlab/puma_logging/json_formatter_spec.rb b/spec/lib/gitlab/puma_logging/json_formatter_spec.rb
index f7f5b99d5e5..64ace09e01b 100644
--- a/spec/lib/gitlab/puma_logging/json_formatter_spec.rb
+++ b/spec/lib/gitlab/puma_logging/json_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PumaLogging::JSONFormatter do
+RSpec.describe Gitlab::PumaLogging::JSONFormatter do
it "generate json format with timestamp and pid" do
Timecop.freeze( Time.utc(2019, 12, 04, 9, 10, 11, 123456)) do
expect(subject.call('log message')).to eq "{\"timestamp\":\"2019-12-04T09:10:11.123Z\",\"pid\":#{Process.pid},\"message\":\"log message\"}"
diff --git a/spec/lib/gitlab/push_options_spec.rb b/spec/lib/gitlab/push_options_spec.rb
index fc9e421bea6..8f43943e2d1 100644
--- a/spec/lib/gitlab/push_options_spec.rb
+++ b/spec/lib/gitlab/push_options_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::PushOptions do
+RSpec.describe Gitlab::PushOptions do
describe 'namespace and key validation' do
it 'ignores unrecognised namespaces' do
options = described_class.new(['invalid.key=value'])
diff --git a/spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb b/spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb
index 2db6d2fb60f..a8dd482c7b8 100644
--- a/spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb
+++ b/spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::QueryLimiting::ActiveSupportSubscriber do
+RSpec.describe Gitlab::QueryLimiting::ActiveSupportSubscriber do
let(:transaction) { instance_double(Gitlab::QueryLimiting::Transaction, increment: true) }
before do
diff --git a/spec/lib/gitlab/query_limiting/middleware_spec.rb b/spec/lib/gitlab/query_limiting/middleware_spec.rb
index f397843df54..cae6df8cdd5 100644
--- a/spec/lib/gitlab/query_limiting/middleware_spec.rb
+++ b/spec/lib/gitlab/query_limiting/middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::QueryLimiting::Middleware do
+RSpec.describe Gitlab::QueryLimiting::Middleware do
describe '#call' do
it 'runs the application with query limiting in place' do
middleware = described_class.new(-> (env) { env })
diff --git a/spec/lib/gitlab/query_limiting/transaction_spec.rb b/spec/lib/gitlab/query_limiting/transaction_spec.rb
index 4e906314b5a..331c3c1d8b0 100644
--- a/spec/lib/gitlab/query_limiting/transaction_spec.rb
+++ b/spec/lib/gitlab/query_limiting/transaction_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::QueryLimiting::Transaction do
+RSpec.describe Gitlab::QueryLimiting::Transaction do
after do
Thread.current[described_class::THREAD_KEY] = nil
end
diff --git a/spec/lib/gitlab/query_limiting_spec.rb b/spec/lib/gitlab/query_limiting_spec.rb
index e9c6bbc35a3..0fcd865567d 100644
--- a/spec/lib/gitlab/query_limiting_spec.rb
+++ b/spec/lib/gitlab/query_limiting_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::QueryLimiting do
+RSpec.describe Gitlab::QueryLimiting do
describe '.enable?' do
it 'returns true in a test environment' do
expect(described_class.enable?).to eq(true)
diff --git a/spec/lib/gitlab/quick_actions/command_definition_spec.rb b/spec/lib/gitlab/quick_actions/command_definition_spec.rb
index 45b710adf07..d63c21954f2 100644
--- a/spec/lib/gitlab/quick_actions/command_definition_spec.rb
+++ b/spec/lib/gitlab/quick_actions/command_definition_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::QuickActions::CommandDefinition do
+RSpec.describe Gitlab::QuickActions::CommandDefinition do
subject { described_class.new(:command) }
describe "#all_names" do
diff --git a/spec/lib/gitlab/quick_actions/dsl_spec.rb b/spec/lib/gitlab/quick_actions/dsl_spec.rb
index 1145a7edc85..f990abfb253 100644
--- a/spec/lib/gitlab/quick_actions/dsl_spec.rb
+++ b/spec/lib/gitlab/quick_actions/dsl_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::QuickActions::Dsl do
+RSpec.describe Gitlab::QuickActions::Dsl do
before :all do
DummyClass = Struct.new(:project) do
include Gitlab::QuickActions::Dsl
diff --git a/spec/lib/gitlab/quick_actions/extractor_spec.rb b/spec/lib/gitlab/quick_actions/extractor_spec.rb
index 6ea597bf01e..f4104b78d5c 100644
--- a/spec/lib/gitlab/quick_actions/extractor_spec.rb
+++ b/spec/lib/gitlab/quick_actions/extractor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::QuickActions::Extractor do
+RSpec.describe Gitlab::QuickActions::Extractor do
let(:definitions) do
Class.new do
include Gitlab::QuickActions::Dsl
diff --git a/spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb b/spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb
index fd149cd1114..0b012bfd970 100644
--- a/spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb
+++ b/spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::QuickActions::SpendTimeAndDateSeparator do
+RSpec.describe Gitlab::QuickActions::SpendTimeAndDateSeparator do
subject { described_class }
shared_examples 'arg line with invalid parameters' do
diff --git a/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb b/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb
index d1a44e2feeb..b28ac49b4ea 100644
--- a/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb
+++ b/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::QuickActions::SubstitutionDefinition do
+RSpec.describe Gitlab::QuickActions::SubstitutionDefinition do
let(:content) do
<<EOF
Hello! Let's do this!
diff --git a/spec/lib/gitlab/rate_limit_helpers_spec.rb b/spec/lib/gitlab/rate_limit_helpers_spec.rb
index 5ab79a2bbfe..e7d4c69d47b 100644
--- a/spec/lib/gitlab/rate_limit_helpers_spec.rb
+++ b/spec/lib/gitlab/rate_limit_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RateLimitHelpers, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::RateLimitHelpers, :clean_gitlab_redis_shared_state do
let(:limiter_class) do
Class.new do
include ::Gitlab::RateLimitHelpers
diff --git a/spec/lib/gitlab/reactive_cache_set_cache_spec.rb b/spec/lib/gitlab/reactive_cache_set_cache_spec.rb
index a0d8f15ba1b..19fb2ada476 100644
--- a/spec/lib/gitlab/reactive_cache_set_cache_spec.rb
+++ b/spec/lib/gitlab/reactive_cache_set_cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ReactiveCacheSetCache, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::ReactiveCacheSetCache, :clean_gitlab_redis_cache do
let_it_be(:project) { create(:project) }
let(:cache_prefix) { 'cache_prefix' }
let(:expires_in) { 10.minutes }
diff --git a/spec/lib/gitlab/redis/boolean_spec.rb b/spec/lib/gitlab/redis/boolean_spec.rb
index bfacf0c448b..9c233ba089f 100644
--- a/spec/lib/gitlab/redis/boolean_spec.rb
+++ b/spec/lib/gitlab/redis/boolean_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::Redis::Boolean do
+RSpec.describe Gitlab::Redis::Boolean do
subject(:redis_boolean) { described_class.new(bool) }
let(:bool) { true }
diff --git a/spec/lib/gitlab/redis/cache_spec.rb b/spec/lib/gitlab/redis/cache_spec.rb
index 0718998f981..5f73b84288d 100644
--- a/spec/lib/gitlab/redis/cache_spec.rb
+++ b/spec/lib/gitlab/redis/cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Redis::Cache do
+RSpec.describe Gitlab::Redis::Cache do
let(:config_file_name) { "config/redis.cache.yml" }
let(:environment_config_file_name) { "GITLAB_REDIS_CACHE_CONFIG_FILE" }
let(:config_old_format_socket) { "spec/fixtures/config/redis_cache_old_format_socket.yml" }
diff --git a/spec/lib/gitlab/redis/queues_spec.rb b/spec/lib/gitlab/redis/queues_spec.rb
index 93207b6f469..8a32c991943 100644
--- a/spec/lib/gitlab/redis/queues_spec.rb
+++ b/spec/lib/gitlab/redis/queues_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Redis::Queues do
+RSpec.describe Gitlab::Redis::Queues do
let(:config_file_name) { "config/redis.queues.yml" }
let(:environment_config_file_name) { "GITLAB_REDIS_QUEUES_CONFIG_FILE" }
let(:config_old_format_socket) { "spec/fixtures/config/redis_queues_old_format_socket.yml" }
diff --git a/spec/lib/gitlab/redis/shared_state_spec.rb b/spec/lib/gitlab/redis/shared_state_spec.rb
index aa61fd99eb5..bd90e4c750d 100644
--- a/spec/lib/gitlab/redis/shared_state_spec.rb
+++ b/spec/lib/gitlab/redis/shared_state_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Redis::SharedState do
+RSpec.describe Gitlab::Redis::SharedState do
let(:config_file_name) { "config/redis.shared_state.yml" }
let(:environment_config_file_name) { "GITLAB_REDIS_SHARED_STATE_CONFIG_FILE" }
let(:config_old_format_socket) { "spec/fixtures/config/redis_shared_state_old_format_socket.yml" }
diff --git a/spec/lib/gitlab/redis/wrapper_spec.rb b/spec/lib/gitlab/redis/wrapper_spec.rb
index 51a36eb062c..283853ee863 100644
--- a/spec/lib/gitlab/redis/wrapper_spec.rb
+++ b/spec/lib/gitlab/redis/wrapper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Redis::Wrapper do
+RSpec.describe Gitlab::Redis::Wrapper do
let(:config_file_name) { "config/resque.yml" }
let(:environment_config_file_name) { "GITLAB_REDIS_CONFIG_FILE" }
let(:config_old_format_socket) { "spec/fixtures/config/redis_old_format_socket.yml" }
diff --git a/spec/lib/gitlab/reference_counter_spec.rb b/spec/lib/gitlab/reference_counter_spec.rb
index ae7b18ca007..0d0ac75ee22 100644
--- a/spec/lib/gitlab/reference_counter_spec.rb
+++ b/spec/lib/gitlab/reference_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ReferenceCounter, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::ReferenceCounter, :clean_gitlab_redis_shared_state do
let(:reference_counter) { described_class.new('project-1') }
describe '#increase' do
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index dd16f3c6035..0172defc75d 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ReferenceExtractor do
+RSpec.describe Gitlab::ReferenceExtractor do
let_it_be(:project) { create(:project) }
before do
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index 2f220272651..7aece6fe697 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Regex do
+RSpec.describe Gitlab::Regex do
shared_examples_for 'project/group name regex' do
it { is_expected.to match('gitlab-ce') }
it { is_expected.to match('GitLab CE') }
@@ -262,6 +262,39 @@ describe Gitlab::Regex do
it { is_expected.not_to match('!!()()') }
end
+ describe '.maven_version_regex' do
+ subject { described_class.maven_version_regex }
+
+ it { is_expected.to match('0')}
+ it { is_expected.to match('1') }
+ it { is_expected.to match('03') }
+ it { is_expected.to match('2.0') }
+ it { is_expected.to match('01.2') }
+ it { is_expected.to match('10.2.3-beta')}
+ it { is_expected.to match('1.2-SNAPSHOT') }
+ it { is_expected.to match('20') }
+ it { is_expected.to match('20.3') }
+ it { is_expected.to match('1.2.1') }
+ it { is_expected.to match('1.4.2-12') }
+ it { is_expected.to match('1.2-beta-2') }
+ it { is_expected.to match('12.1.2-2-1') }
+ it { is_expected.to match('1.1-beta-2') }
+ it { is_expected.to match('1.3.350.v20200505-1744') }
+ it { is_expected.to match('2.0.0.v200706041905-7C78EK9E_EkMNfNOd2d8qq') }
+ it { is_expected.to match('1.2-alpha-1-20050205.060708-1') }
+ it { is_expected.to match('703220b4e2cea9592caeb9f3013f6b1e5335c293') }
+ it { is_expected.to match('RELEASE') }
+ it { is_expected.not_to match('..1.2.3') }
+ it { is_expected.not_to match(' 1.2.3') }
+ it { is_expected.not_to match("1.2.3 \r\t") }
+ it { is_expected.not_to match("\r\t 1.2.3") }
+ it { is_expected.not_to match('1./2.3') }
+ it { is_expected.not_to match('1.2.3-4/../../') }
+ it { is_expected.not_to match('1.2.3-4%2e%2e%') }
+ it { is_expected.not_to match('../../../../../1.2.3') }
+ it { is_expected.not_to match('%2e%2e%2f1.2.3') }
+ end
+
describe '.semver_regex' do
subject { described_class.semver_regex }
diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb
index 68571b9de20..05f32459164 100644
--- a/spec/lib/gitlab/repo_path_spec.rb
+++ b/spec/lib/gitlab/repo_path_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::RepoPath do
+RSpec.describe ::Gitlab::RepoPath do
include Gitlab::Routing
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/repository_cache_adapter_spec.rb b/spec/lib/gitlab/repository_cache_adapter_spec.rb
index dba5ffc84c5..3727217203e 100644
--- a/spec/lib/gitlab/repository_cache_adapter_spec.rb
+++ b/spec/lib/gitlab/repository_cache_adapter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RepositoryCacheAdapter do
+RSpec.describe Gitlab::RepositoryCacheAdapter do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:cache) { repository.send(:cache) }
diff --git a/spec/lib/gitlab/repository_cache_spec.rb b/spec/lib/gitlab/repository_cache_spec.rb
index be31be761ad..80285a6c732 100644
--- a/spec/lib/gitlab/repository_cache_spec.rb
+++ b/spec/lib/gitlab/repository_cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RepositoryCache do
+RSpec.describe Gitlab::RepositoryCache do
let_it_be(:project) { create(:project) }
let(:backend) { double('backend').as_null_object }
let(:repository) { project.repository }
diff --git a/spec/lib/gitlab/repository_hash_cache_spec.rb b/spec/lib/gitlab/repository_hash_cache_spec.rb
index 014a2f235b9..ea856c14a77 100644
--- a/spec/lib/gitlab/repository_hash_cache_spec.rb
+++ b/spec/lib/gitlab/repository_hash_cache_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Gitlab::RepositoryHashCache, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::RepositoryHashCache, :clean_gitlab_redis_cache do
let_it_be(:project) { create(:project) }
let(:repository) { project.repository }
let(:namespace) { "#{repository.full_path}:#{project.id}" }
diff --git a/spec/lib/gitlab/repository_set_cache_spec.rb b/spec/lib/gitlab/repository_set_cache_spec.rb
index b09194e7d0b..024aae49b04 100644
--- a/spec/lib/gitlab/repository_set_cache_spec.rb
+++ b/spec/lib/gitlab/repository_set_cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
let_it_be(:project) { create(:project) }
let(:repository) { project.repository }
let(:namespace) { "#{repository.full_path}:#{project.id}" }
diff --git a/spec/lib/gitlab/repository_size_checker_spec.rb b/spec/lib/gitlab/repository_size_checker_spec.rb
index 61f76d716e5..9b2c02b1190 100644
--- a/spec/lib/gitlab/repository_size_checker_spec.rb
+++ b/spec/lib/gitlab/repository_size_checker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RepositorySizeChecker do
+RSpec.describe Gitlab::RepositorySizeChecker do
let(:current_size) { 0 }
let(:limit) { 50 }
let(:enabled) { true }
diff --git a/spec/lib/gitlab/repository_size_error_message_spec.rb b/spec/lib/gitlab/repository_size_error_message_spec.rb
index 9e4d19cc572..b6b975143c9 100644
--- a/spec/lib/gitlab/repository_size_error_message_spec.rb
+++ b/spec/lib/gitlab/repository_size_error_message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RepositorySizeErrorMessage do
+RSpec.describe Gitlab::RepositorySizeErrorMessage do
let(:checker) do
Gitlab::RepositorySizeChecker.new(
current_size_proc: -> { 15.megabytes },
diff --git a/spec/lib/gitlab/repository_url_builder_spec.rb b/spec/lib/gitlab/repository_url_builder_spec.rb
index a5797146cc5..63cb1d7a9ac 100644
--- a/spec/lib/gitlab/repository_url_builder_spec.rb
+++ b/spec/lib/gitlab/repository_url_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RepositoryUrlBuilder do
+RSpec.describe Gitlab::RepositoryUrlBuilder do
describe '.build' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/lib/gitlab/request_context_spec.rb b/spec/lib/gitlab/request_context_spec.rb
index d7af0765d53..b9acfa4a841 100644
--- a/spec/lib/gitlab/request_context_spec.rb
+++ b/spec/lib/gitlab/request_context_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RequestContext, :request_store do
+RSpec.describe Gitlab::RequestContext, :request_store do
subject { described_class.instance }
before do
diff --git a/spec/lib/gitlab/request_forgery_protection_spec.rb b/spec/lib/gitlab/request_forgery_protection_spec.rb
index b7a3dc16eff..20996dd44b8 100644
--- a/spec/lib/gitlab/request_forgery_protection_spec.rb
+++ b/spec/lib/gitlab/request_forgery_protection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RequestForgeryProtection, :allow_forgery_protection do
+RSpec.describe Gitlab::RequestForgeryProtection, :allow_forgery_protection do
let(:csrf_token) { SecureRandom.base64(ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH) }
let(:env) do
{
diff --git a/spec/lib/gitlab/request_profiler/profile_spec.rb b/spec/lib/gitlab/request_profiler/profile_spec.rb
index a75f3c66156..2e9c75dde87 100644
--- a/spec/lib/gitlab/request_profiler/profile_spec.rb
+++ b/spec/lib/gitlab/request_profiler/profile_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::RequestProfiler::Profile do
+RSpec.describe Gitlab::RequestProfiler::Profile do
let(:profile) { described_class.new(filename) }
describe '.new' do
diff --git a/spec/lib/gitlab/request_profiler_spec.rb b/spec/lib/gitlab/request_profiler_spec.rb
index f157189a72d..4d3b361efcb 100644
--- a/spec/lib/gitlab/request_profiler_spec.rb
+++ b/spec/lib/gitlab/request_profiler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RequestProfiler do
+RSpec.describe Gitlab::RequestProfiler do
describe '.profile_token' do
it 'returns a token' do
expect(described_class.profile_token).to be_present
diff --git a/spec/lib/gitlab/route_map_spec.rb b/spec/lib/gitlab/route_map_spec.rb
index d5e70b91fb4..e2c71980068 100644
--- a/spec/lib/gitlab/route_map_spec.rb
+++ b/spec/lib/gitlab/route_map_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RouteMap do
+RSpec.describe Gitlab::RouteMap do
describe '#initialize' do
context 'when the data is not YAML' do
it 'raises an error' do
diff --git a/spec/lib/gitlab/routing_spec.rb b/spec/lib/gitlab/routing_spec.rb
index 5446d6559fe..304ce57aee3 100644
--- a/spec/lib/gitlab/routing_spec.rb
+++ b/spec/lib/gitlab/routing_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Routing do
+RSpec.describe Gitlab::Routing do
context 'when module is included' do
subject do
Class.new.include(described_class).new
diff --git a/spec/lib/gitlab/rugged_instrumentation_spec.rb b/spec/lib/gitlab/rugged_instrumentation_spec.rb
index d6f3fb9be55..393bb957aba 100644
--- a/spec/lib/gitlab/rugged_instrumentation_spec.rb
+++ b/spec/lib/gitlab/rugged_instrumentation_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::RuggedInstrumentation, :request_store do
+RSpec.describe Gitlab::RuggedInstrumentation, :request_store do
subject { described_class }
describe '.query_time' do
diff --git a/spec/lib/gitlab/runtime_spec.rb b/spec/lib/gitlab/runtime_spec.rb
index 8f920bb2e01..8ed7cc141cd 100644
--- a/spec/lib/gitlab/runtime_spec.rb
+++ b/spec/lib/gitlab/runtime_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Runtime do
+RSpec.describe Gitlab::Runtime do
shared_examples "valid runtime" do |runtime, max_threads|
it "identifies itself" do
expect(subject.identify).to eq(runtime)
@@ -48,18 +48,47 @@ describe Gitlab::Runtime do
before do
stub_const('::Puma', puma_type)
allow(puma_type).to receive_message_chain(:cli_config, :options).and_return(max_threads: 2)
+ stub_env('ACTION_CABLE_IN_APP', 'false')
end
it_behaves_like "valid runtime", :puma, 3
+
+ context "when ActionCable in-app mode is enabled" do
+ before do
+ stub_env('ACTION_CABLE_IN_APP', 'true')
+ stub_env('ACTION_CABLE_WORKER_POOL_SIZE', '3')
+ end
+
+ it_behaves_like "valid runtime", :puma, 6
+ end
+
+ context "when ActionCable standalone is run" do
+ before do
+ stub_const('ACTION_CABLE_SERVER', true)
+ stub_env('ACTION_CABLE_WORKER_POOL_SIZE', '8')
+ end
+
+ it_behaves_like "valid runtime", :puma, 11
+ end
end
context "unicorn" do
before do
stub_const('::Unicorn', Module.new)
stub_const('::Unicorn::HttpServer', Class.new)
+ stub_env('ACTION_CABLE_IN_APP', 'false')
end
it_behaves_like "valid runtime", :unicorn, 1
+
+ context "when ActionCable in-app mode is enabled" do
+ before do
+ stub_env('ACTION_CABLE_IN_APP', 'true')
+ stub_env('ACTION_CABLE_WORKER_POOL_SIZE', '3')
+ end
+
+ it_behaves_like "valid runtime", :unicorn, 4
+ end
end
context "sidekiq" do
@@ -105,17 +134,4 @@ describe Gitlab::Runtime do
it_behaves_like "valid runtime", :rails_runner, 1
end
-
- context "action_cable" do
- before do
- stub_const('ACTION_CABLE_SERVER', true)
- stub_const('::Puma', Module.new)
-
- allow(Gitlab::Application).to receive_message_chain(:config, :action_cable, :worker_pool_size).and_return(8)
- end
-
- it "reports its maximum concurrency based on ActionCable's worker pool size" do
- expect(subject.max_threads).to eq(9)
- end
- end
end
diff --git a/spec/lib/gitlab/safe_request_store_spec.rb b/spec/lib/gitlab/safe_request_store_spec.rb
index def05a3f285..704102ccaee 100644
--- a/spec/lib/gitlab/safe_request_store_spec.rb
+++ b/spec/lib/gitlab/safe_request_store_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SafeRequestStore do
+RSpec.describe Gitlab::SafeRequestStore do
describe '.store' do
context 'when RequestStore is active', :request_store do
it 'uses RequestStore' do
diff --git a/spec/lib/gitlab/sanitizers/exif_spec.rb b/spec/lib/gitlab/sanitizers/exif_spec.rb
index 58fba673f8e..88ef3ce6aa5 100644
--- a/spec/lib/gitlab/sanitizers/exif_spec.rb
+++ b/spec/lib/gitlab/sanitizers/exif_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Sanitizers::Exif do
+RSpec.describe Gitlab::Sanitizers::Exif do
let(:sanitizer) { described_class.new }
describe '#batch_clean' do
diff --git a/spec/lib/gitlab/sanitizers/svg_spec.rb b/spec/lib/gitlab/sanitizers/svg_spec.rb
index 18fa96a2914..88fd7b78abe 100644
--- a/spec/lib/gitlab/sanitizers/svg_spec.rb
+++ b/spec/lib/gitlab/sanitizers/svg_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Sanitizers::SVG do
+RSpec.describe Gitlab::Sanitizers::SVG do
let(:scrubber) { Gitlab::Sanitizers::SVG::Scrubber.new }
let(:namespace) { double(Nokogiri::XML::Namespace, prefix: 'xlink', href: 'http://www.w3.org/1999/xlink') }
let(:namespaced_attr) { double(Nokogiri::XML::Attr, name: 'href', namespace: namespace, value: '#awesome_id') }
diff --git a/spec/lib/gitlab/search/found_blob_spec.rb b/spec/lib/gitlab/search/found_blob_spec.rb
index ce6a54100a5..8b1c91f689d 100644
--- a/spec/lib/gitlab/search/found_blob_spec.rb
+++ b/spec/lib/gitlab/search/found_blob_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Search::FoundBlob do
+RSpec.describe Gitlab::Search::FoundBlob do
let(:project) { create(:project, :public, :repository) }
describe 'parsing content results' do
diff --git a/spec/lib/gitlab/search/found_wiki_page_spec.rb b/spec/lib/gitlab/search/found_wiki_page_spec.rb
index e8b6728aba5..fc166ad3851 100644
--- a/spec/lib/gitlab/search/found_wiki_page_spec.rb
+++ b/spec/lib/gitlab/search/found_wiki_page_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Search::FoundWikiPage do
+RSpec.describe Gitlab::Search::FoundWikiPage do
let(:project) { create(:project, :public, :repository) }
describe 'policy' do
diff --git a/spec/lib/gitlab/search/query_spec.rb b/spec/lib/gitlab/search/query_spec.rb
index 112e9a59f04..e9601002922 100644
--- a/spec/lib/gitlab/search/query_spec.rb
+++ b/spec/lib/gitlab/search/query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Search::Query do
+RSpec.describe Gitlab::Search::Query do
let(:query) { 'base filter:wow anotherfilter:noway name:maybe other:mmm leftover' }
let(:subject) do
described_class.new(query) do
diff --git a/spec/lib/gitlab/search_context/builder_spec.rb b/spec/lib/gitlab/search_context/builder_spec.rb
index 1707b54b273..5b4190fc67e 100644
--- a/spec/lib/gitlab/search_context/builder_spec.rb
+++ b/spec/lib/gitlab/search_context/builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SearchContext::Builder, type: :controller do
+RSpec.describe Gitlab::SearchContext::Builder, type: :controller do
controller(ApplicationController) { }
subject(:builder) { described_class.new(controller.view_context) }
diff --git a/spec/lib/gitlab/search_context/controller_concern_spec.rb b/spec/lib/gitlab/search_context/controller_concern_spec.rb
index 16784cafb76..9cf6b8d49aa 100644
--- a/spec/lib/gitlab/search_context/controller_concern_spec.rb
+++ b/spec/lib/gitlab/search_context/controller_concern_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SearchContext::ControllerConcern, type: :controller do
+RSpec.describe Gitlab::SearchContext::ControllerConcern, type: :controller do
controller(ApplicationController) do
include Gitlab::SearchContext::ControllerConcern
end
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index ab14602a468..61fa61566cd 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SearchResults do
+RSpec.describe Gitlab::SearchResults do
include ProjectForksHelper
include SearchHelpers
diff --git a/spec/lib/gitlab/serializer/ci/variables_spec.rb b/spec/lib/gitlab/serializer/ci/variables_spec.rb
index 900508420c9..9b0475259fe 100644
--- a/spec/lib/gitlab/serializer/ci/variables_spec.rb
+++ b/spec/lib/gitlab/serializer/ci/variables_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Serializer::Ci::Variables do
+RSpec.describe Gitlab::Serializer::Ci::Variables do
subject do
described_class.load(described_class.dump(object))
end
diff --git a/spec/lib/gitlab/serializer/pagination_spec.rb b/spec/lib/gitlab/serializer/pagination_spec.rb
index 1e7f441f258..69b5e181769 100644
--- a/spec/lib/gitlab/serializer/pagination_spec.rb
+++ b/spec/lib/gitlab/serializer/pagination_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Serializer::Pagination do
+RSpec.describe Gitlab::Serializer::Pagination do
let(:request) { double(url: "#{Gitlab.config.gitlab.url}:8080/api/v4/projects?#{query.to_query}", query_parameters: query) }
let(:response) { spy('response') }
let(:headers) { spy('headers') }
diff --git a/spec/lib/gitlab/serverless/service_spec.rb b/spec/lib/gitlab/serverless/service_spec.rb
index 6db8b9cd0ba..3400be5b48e 100644
--- a/spec/lib/gitlab/serverless/service_spec.rb
+++ b/spec/lib/gitlab/serverless/service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Serverless::Service do
+RSpec.describe Gitlab::Serverless::Service do
let(:cluster) { create(:cluster) }
let(:environment) { create(:environment) }
let(:attributes) do
diff --git a/spec/lib/gitlab/service_desk_email_spec.rb b/spec/lib/gitlab/service_desk_email_spec.rb
new file mode 100644
index 00000000000..23e2b2ff3cf
--- /dev/null
+++ b/spec/lib/gitlab/service_desk_email_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::ServiceDeskEmail do
+ describe '.enabled?' do
+ context 'when service_desk_email is enabled and address is set' do
+ before do
+ stub_service_desk_email_setting(enabled: true, address: 'foo')
+ end
+
+ it 'returns true' do
+ expect(described_class.enabled?).to be_truthy
+ end
+ end
+
+ context 'when service_desk_email is disabled' do
+ before do
+ stub_service_desk_email_setting(enabled: false, address: 'foo')
+ end
+
+ it 'returns false' do
+ expect(described_class.enabled?).to be_falsey
+ end
+ end
+
+ context 'when service desk address is not set' do
+ before do
+ stub_service_desk_email_setting(enabled: true, address: nil)
+ end
+
+ it 'returns false' do
+ expect(described_class.enabled?).to be_falsey
+ end
+ end
+ end
+
+ describe '.key_from_address' do
+ context 'when service desk address is set' do
+ before do
+ stub_service_desk_email_setting(address: 'address+%{key}@example.com')
+ end
+
+ it 'returns key' do
+ expect(described_class.key_from_address('address+key@example.com')).to eq('key')
+ end
+ end
+
+ context 'when service desk address is not set' do
+ before do
+ stub_service_desk_email_setting(address: nil)
+ end
+
+ it 'returns nil' do
+ expect(described_class.key_from_address('address+key@example.com')).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/service_desk_spec.rb b/spec/lib/gitlab/service_desk_spec.rb
new file mode 100644
index 00000000000..f554840ec78
--- /dev/null
+++ b/spec/lib/gitlab/service_desk_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::ServiceDesk do
+ before do
+ allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(true)
+ allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(true)
+ end
+
+ describe 'enabled?' do
+ let_it_be(:project) { create(:project) }
+
+ subject { described_class.enabled?(project: project) }
+
+ it { is_expected.to be_truthy }
+
+ context 'when service desk is not supported' do
+ before do
+ allow(described_class).to receive(:supported?).and_return(false)
+ end
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when service desk is disabled for project' do
+ before do
+ project.update!(service_desk_enabled: false)
+ end
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe 'supported?' do
+ subject { described_class.supported? }
+
+ it { is_expected.to be_truthy }
+
+ context 'when incoming emails are disabled' do
+ before do
+ allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(false)
+ end
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when email key is not supported' do
+ before do
+ allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(false)
+ end
+
+ it { is_expected.to be_falsy }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/session_spec.rb b/spec/lib/gitlab/session_spec.rb
index 8db73f0ec7b..de680e8425e 100644
--- a/spec/lib/gitlab/session_spec.rb
+++ b/spec/lib/gitlab/session_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Session do
+RSpec.describe Gitlab::Session do
it 'uses the current thread as a data store' do
Thread.current[:session_storage] = { a: :b }
diff --git a/spec/lib/gitlab/shard_health_cache_spec.rb b/spec/lib/gitlab/shard_health_cache_spec.rb
index f747849b5e9..5c47ac7e9a0 100644
--- a/spec/lib/gitlab/shard_health_cache_spec.rb
+++ b/spec/lib/gitlab/shard_health_cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ShardHealthCache, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::ShardHealthCache, :clean_gitlab_redis_cache do
let(:shards) { %w(foo bar) }
before do
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index 1f515cffdbf..b0dc34e8abf 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'stringio'
-describe Gitlab::Shell do
+RSpec.describe Gitlab::Shell do
let_it_be(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:gitlab_shell) { described_class.new }
diff --git a/spec/lib/gitlab/sherlock/collection_spec.rb b/spec/lib/gitlab/sherlock/collection_spec.rb
index bdc89c3d3cf..fcf8e6638f8 100644
--- a/spec/lib/gitlab/sherlock/collection_spec.rb
+++ b/spec/lib/gitlab/sherlock/collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Sherlock::Collection do
+RSpec.describe Gitlab::Sherlock::Collection do
let(:collection) { described_class.new }
let(:transaction) do
diff --git a/spec/lib/gitlab/sherlock/file_sample_spec.rb b/spec/lib/gitlab/sherlock/file_sample_spec.rb
index b09ba5c62dc..8a1aa51e2d4 100644
--- a/spec/lib/gitlab/sherlock/file_sample_spec.rb
+++ b/spec/lib/gitlab/sherlock/file_sample_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Sherlock::FileSample do
+RSpec.describe Gitlab::Sherlock::FileSample do
let(:sample) { described_class.new(__FILE__, [], 150.4, 2) }
describe '#id' do
diff --git a/spec/lib/gitlab/sherlock/line_profiler_spec.rb b/spec/lib/gitlab/sherlock/line_profiler_spec.rb
index c1997606839..2220a2cafc8 100644
--- a/spec/lib/gitlab/sherlock/line_profiler_spec.rb
+++ b/spec/lib/gitlab/sherlock/line_profiler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Sherlock::LineProfiler do
+RSpec.describe Gitlab::Sherlock::LineProfiler do
let(:profiler) { described_class.new }
describe '#profile' do
diff --git a/spec/lib/gitlab/sherlock/line_sample_spec.rb b/spec/lib/gitlab/sherlock/line_sample_spec.rb
index b68e8cc0266..db031377787 100644
--- a/spec/lib/gitlab/sherlock/line_sample_spec.rb
+++ b/spec/lib/gitlab/sherlock/line_sample_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Sherlock::LineSample do
+RSpec.describe Gitlab::Sherlock::LineSample do
let(:sample) { described_class.new(150.0, 4) }
describe '#duration' do
diff --git a/spec/lib/gitlab/sherlock/location_spec.rb b/spec/lib/gitlab/sherlock/location_spec.rb
index 7b40c84c2d1..4a8b5dffba2 100644
--- a/spec/lib/gitlab/sherlock/location_spec.rb
+++ b/spec/lib/gitlab/sherlock/location_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Sherlock::Location do
+RSpec.describe Gitlab::Sherlock::Location do
let(:location) { described_class.new(__FILE__, 1) }
describe 'from_ruby_location' do
diff --git a/spec/lib/gitlab/sherlock/middleware_spec.rb b/spec/lib/gitlab/sherlock/middleware_spec.rb
index 8d6e362f622..645bde6681d 100644
--- a/spec/lib/gitlab/sherlock/middleware_spec.rb
+++ b/spec/lib/gitlab/sherlock/middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Sherlock::Middleware do
+RSpec.describe Gitlab::Sherlock::Middleware do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
diff --git a/spec/lib/gitlab/sherlock/query_spec.rb b/spec/lib/gitlab/sherlock/query_spec.rb
index 13c7e6f8f8b..b8dfd082c37 100644
--- a/spec/lib/gitlab/sherlock/query_spec.rb
+++ b/spec/lib/gitlab/sherlock/query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Sherlock::Query do
+RSpec.describe Gitlab::Sherlock::Query do
let(:started_at) { Time.utc(2015, 1, 1) }
let(:finished_at) { started_at + 5 }
diff --git a/spec/lib/gitlab/sherlock/transaction_spec.rb b/spec/lib/gitlab/sherlock/transaction_spec.rb
index 728c44df4f3..535b0ad4d8a 100644
--- a/spec/lib/gitlab/sherlock/transaction_spec.rb
+++ b/spec/lib/gitlab/sherlock/transaction_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Sherlock::Transaction do
+RSpec.describe Gitlab::Sherlock::Transaction do
let(:transaction) { described_class.new('POST', '/cat_pictures') }
describe '#id' do
diff --git a/spec/lib/gitlab/sidekiq_cluster/cli_spec.rb b/spec/lib/gitlab/sidekiq_cluster/cli_spec.rb
index fa23bf8958c..cf165d1770b 100644
--- a/spec/lib/gitlab/sidekiq_cluster/cli_spec.rb
+++ b/spec/lib/gitlab/sidekiq_cluster/cli_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require 'rspec-parameterized'
-describe Gitlab::SidekiqCluster::CLI do
+RSpec.describe Gitlab::SidekiqCluster::CLI do
let(:cli) { described_class.new('/dev/null') }
let(:timeout) { described_class::DEFAULT_SOFT_TIMEOUT_SECONDS }
let(:default_options) do
diff --git a/spec/lib/gitlab/sidekiq_cluster_spec.rb b/spec/lib/gitlab/sidekiq_cluster_spec.rb
index 3f466e9f2a3..d625a2a2185 100644
--- a/spec/lib/gitlab/sidekiq_cluster_spec.rb
+++ b/spec/lib/gitlab/sidekiq_cluster_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require 'rspec-parameterized'
-describe Gitlab::SidekiqCluster do
+RSpec.describe Gitlab::SidekiqCluster do
describe '.trap_signals' do
it 'traps the given signals' do
expect(described_class).to receive(:trap).ordered.with(:INT)
diff --git a/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb b/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb
index 7a8aba2d396..01e7c06249a 100644
--- a/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb
+++ b/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require 'rspec-parameterized'
-describe Gitlab::SidekiqConfig::CliMethods do
+RSpec.describe Gitlab::SidekiqConfig::CliMethods do
let(:dummy_root) { '/tmp/' }
describe '.worker_queues' do
diff --git a/spec/lib/gitlab/sidekiq_config/worker_spec.rb b/spec/lib/gitlab/sidekiq_config/worker_spec.rb
index 00343a0264d..05987f95b33 100644
--- a/spec/lib/gitlab/sidekiq_config/worker_spec.rb
+++ b/spec/lib/gitlab/sidekiq_config/worker_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::SidekiqConfig::Worker do
+RSpec.describe Gitlab::SidekiqConfig::Worker do
def create_worker(queue:, **attributes)
namespace = queue.include?(':') && queue.split(':').first
inner_worker = double(
diff --git a/spec/lib/gitlab/sidekiq_config_spec.rb b/spec/lib/gitlab/sidekiq_config_spec.rb
index 66744d07aaa..d216b9d0c18 100644
--- a/spec/lib/gitlab/sidekiq_config_spec.rb
+++ b/spec/lib/gitlab/sidekiq_config_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqConfig do
+RSpec.describe Gitlab::SidekiqConfig do
describe '.workers' do
it 'includes all workers' do
worker_classes = described_class.workers.map(&:klass)
diff --git a/spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb b/spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb
index 45bcc71dfcb..0ff2dbb234a 100644
--- a/spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb
+++ b/spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqDaemon::MemoryKiller do
+RSpec.describe Gitlab::SidekiqDaemon::MemoryKiller do
let(:memory_killer) { described_class.new }
let(:pid) { 12345 }
diff --git a/spec/lib/gitlab/sidekiq_daemon/monitor_spec.rb b/spec/lib/gitlab/sidekiq_daemon/monitor_spec.rb
index 3f49ef0e9a7..749c7af6f59 100644
--- a/spec/lib/gitlab/sidekiq_daemon/monitor_spec.rb
+++ b/spec/lib/gitlab/sidekiq_daemon/monitor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqDaemon::Monitor do
+RSpec.describe Gitlab::SidekiqDaemon::Monitor do
let(:monitor) { described_class.new }
describe '#within_job' do
diff --git a/spec/lib/gitlab/sidekiq_logging/deduplication_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/deduplication_logger_spec.rb
index 3cc5c0bed1b..82f927fe481 100644
--- a/spec/lib/gitlab/sidekiq_logging/deduplication_logger_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/deduplication_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqLogging::DeduplicationLogger do
+RSpec.describe Gitlab::SidekiqLogging::DeduplicationLogger do
describe '#log_deduplication' do
let(:job) do
{
@@ -18,11 +18,12 @@ describe Gitlab::SidekiqLogging::DeduplicationLogger do
expected_payload = {
'job_status' => 'deduplicated',
'message' => "#{job['class']} JID-#{job['jid']}: deduplicated: a fancy strategy",
- 'deduplication_type' => 'a fancy strategy'
+ 'deduplication.type' => 'a fancy strategy',
+ 'deduplication.options.foo' => :bar
}
expect(Sidekiq.logger).to receive(:info).with(a_hash_including(expected_payload)).and_call_original
- described_class.instance.log(job, "a fancy strategy")
+ described_class.instance.log(job, "a fancy strategy", { foo: :bar })
end
it "does not modify the job" do
diff --git a/spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb b/spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb
index a79a0678e2b..5a68b88c02d 100644
--- a/spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqLogging::ExceptionHandler do
+RSpec.describe Gitlab::SidekiqLogging::ExceptionHandler do
describe '#call' do
let(:job) do
{
diff --git a/spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb b/spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb
index 10354147cf9..c879fdea3ad 100644
--- a/spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqLogging::JSONFormatter do
+RSpec.describe Gitlab::SidekiqLogging::JSONFormatter do
let(:message) { 'This is a test' }
let(:now) { Time.now }
let(:timestamp) { now.utc.to_f }
diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
index a456f814e78..03ace9a01c7 100644
--- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqLogging::StructuredLogger do
+RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
describe '#call' do
let(:timestamp) { Time.iso8601('2018-01-01T12:00:00.000Z') }
let(:created_at) { timestamp - 1.second }
diff --git a/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb b/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb
index f6449bae8c3..3d9ffb11ae2 100644
--- a/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::AdminMode::Client, :do_not_mock_admin_mode, :request_store do
+RSpec.describe Gitlab::SidekiqMiddleware::AdminMode::Client, :do_not_mock_admin_mode, :request_store do
include AdminModeHelper
let(:worker) do
diff --git a/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb
index 60475f0e403..20f1e88bcf4 100644
--- a/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::AdminMode::Server, :do_not_mock_admin_mode, :request_store do
+RSpec.describe Gitlab::SidekiqMiddleware::AdminMode::Server, :do_not_mock_admin_mode, :request_store do
include AdminModeHelper
let(:worker) do
diff --git a/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb
index 1d45b70ec3e..f7010b2001a 100644
--- a/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::ClientMetrics do
+RSpec.describe Gitlab::SidekiqMiddleware::ClientMetrics do
context "with worker attribution" do
subject { described_class.new }
diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb
index a1e4cbb1e31..98350fb9b8e 100644
--- a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::DuplicateJobs::Client, :clean_gitlab_redis_queues do
+RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::Client, :clean_gitlab_redis_queues do
let(:worker_class) do
Class.new do
def self.name
diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb
index 13c86563be7..8ef61d4eae9 100644
--- a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, :clean_gitlab_redis_queues do
+RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, :clean_gitlab_redis_queues do
using RSpec::Parameterized::TableSyntax
subject(:duplicate_job) do
diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb
index 312ebd30a76..3f75d867936 100644
--- a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::DuplicateJobs::Server, :clean_gitlab_redis_queues do
+RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::Server, :clean_gitlab_redis_queues do
let(:worker_class) do
Class.new do
def self.name
diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing_spec.rb
index eb8b0a951a8..77d760d1ae3 100644
--- a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require 'timecop'
-describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting do
+RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting do
let(:fake_duplicate_job) do
instance_double(Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob)
end
@@ -40,6 +40,7 @@ describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting do
allow(fake_duplicate_job).to receive(:scheduled?).and_return(false)
allow(fake_duplicate_job).to receive(:check!).and_return('the jid')
allow(fake_duplicate_job).to receive(:droppable?).and_return(true)
+ allow(fake_duplicate_job).to receive(:options).and_return({})
job_hash = {}
expect(fake_duplicate_job).to receive(:duplicate?).and_return(true)
@@ -102,6 +103,7 @@ describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting do
allow(fake_duplicate_job).to receive(:scheduled?).and_return(false)
allow(fake_duplicate_job).to receive(:check!).and_return('the jid')
allow(fake_duplicate_job).to receive(:duplicate?).and_return(true)
+ allow(fake_duplicate_job).to receive(:options).and_return({})
allow(fake_duplicate_job).to receive(:existing_jid).and_return('the jid')
allow(fake_duplicate_job).to receive(:droppable?).and_return(true)
end
@@ -119,7 +121,17 @@ describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting do
fake_logger = instance_double(Gitlab::SidekiqLogging::DeduplicationLogger)
expect(Gitlab::SidekiqLogging::DeduplicationLogger).to receive(:instance).and_return(fake_logger)
- expect(fake_logger).to receive(:log).with(a_hash_including({ 'jid' => 'new jid' }), 'dropped until executing')
+ expect(fake_logger).to receive(:log).with(a_hash_including({ 'jid' => 'new jid' }), 'dropped until executing', {})
+
+ strategy.schedule({ 'jid' => 'new jid' }) {}
+ end
+
+ it 'logs the deduplication options of the worker' do
+ fake_logger = instance_double(Gitlab::SidekiqLogging::DeduplicationLogger)
+
+ expect(Gitlab::SidekiqLogging::DeduplicationLogger).to receive(:instance).and_return(fake_logger)
+ allow(fake_duplicate_job).to receive(:options).and_return({ foo: :bar })
+ expect(fake_logger).to receive(:log).with(a_hash_including({ 'jid' => 'new jid' }), 'dropped until executing', { foo: :bar })
strategy.schedule({ 'jid' => 'new jid' }) {}
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb
index 6ecc2a3a5f8..5d37e3cb1ae 100644
--- a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies do
+RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies do
describe '.for' do
it 'returns the right class for `until_executing`' do
expect(described_class.for(:until_executing)).to eq(described_class::UntilExecuting)
diff --git a/spec/lib/gitlab/sidekiq_middleware/extra_done_log_metadata_spec.rb b/spec/lib/gitlab/sidekiq_middleware/extra_done_log_metadata_spec.rb
index 98847885e62..dbab67f5996 100644
--- a/spec/lib/gitlab/sidekiq_middleware/extra_done_log_metadata_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/extra_done_log_metadata_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::ExtraDoneLogMetadata do
+RSpec.describe Gitlab::SidekiqMiddleware::ExtraDoneLogMetadata do
# Cannot use Class.new for this as ApplicationWorker will need the class to
# have a name during `included do`.
let(:worker) { AdminEmailWorker.new }
diff --git a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
index b5be43ec96c..0d1616c4aed 100644
--- a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::MemoryKiller do
+RSpec.describe Gitlab::SidekiqMiddleware::MemoryKiller do
subject { described_class.new }
let(:pid) { 999 }
diff --git a/spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb b/spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb
index 398144025ea..85cddfa7bf1 100644
--- a/spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::Monitor do
+RSpec.describe Gitlab::SidekiqMiddleware::Monitor do
let(:monitor) { described_class.new }
describe '#call' do
diff --git a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
index 4b7baea25e8..1b3b108d9ea 100644
--- a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::ServerMetrics do
+RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
context "with worker attribution" do
subject { described_class.new }
diff --git a/spec/lib/gitlab/sidekiq_middleware/worker_context/client_spec.rb b/spec/lib/gitlab/sidekiq_middleware/worker_context/client_spec.rb
index 9cb89b1bc10..fff925f8532 100644
--- a/spec/lib/gitlab/sidekiq_middleware/worker_context/client_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/worker_context/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::WorkerContext::Client do
+RSpec.describe Gitlab::SidekiqMiddleware::WorkerContext::Client do
let(:worker_class) do
Class.new do
def self.name
diff --git a/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb
index fdf643a8ad1..bde19fa7552 100644
--- a/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqMiddleware::WorkerContext::Server do
+RSpec.describe Gitlab::SidekiqMiddleware::WorkerContext::Server do
let(:worker_class) do
Class.new do
def self.name
diff --git a/spec/lib/gitlab/sidekiq_middleware_spec.rb b/spec/lib/gitlab/sidekiq_middleware_spec.rb
index 5ca0abeb132..018821e6c5e 100644
--- a/spec/lib/gitlab/sidekiq_middleware_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'sidekiq/testing'
-describe Gitlab::SidekiqMiddleware do
+RSpec.describe Gitlab::SidekiqMiddleware do
before do
stub_const('TestWorker', Class.new)
diff --git a/spec/lib/gitlab/sidekiq_queue_spec.rb b/spec/lib/gitlab/sidekiq_queue_spec.rb
index f5be8d9bfed..44ac89c0816 100644
--- a/spec/lib/gitlab/sidekiq_queue_spec.rb
+++ b/spec/lib/gitlab/sidekiq_queue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqQueue, :clean_gitlab_redis_queues do
+RSpec.describe Gitlab::SidekiqQueue, :clean_gitlab_redis_queues do
around do |example|
Sidekiq::Queue.new('authorized_projects').clear
Sidekiq::Testing.disable!(&example)
diff --git a/spec/lib/gitlab/sidekiq_signals_spec.rb b/spec/lib/gitlab/sidekiq_signals_spec.rb
index 10f1bad32cd..2f751839f6a 100644
--- a/spec/lib/gitlab/sidekiq_signals_spec.rb
+++ b/spec/lib/gitlab/sidekiq_signals_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqSignals do
+RSpec.describe Gitlab::SidekiqSignals do
describe '.install' do
let(:result) { Hash.new { |h, k| h[k] = 0 } }
let(:int_handler) { -> (_) { result['INT'] += 1 } }
diff --git a/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb
index 1ca8cea66fc..0cf05fb0a5c 100644
--- a/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb
+++ b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqStatus::ClientMiddleware do
+RSpec.describe Gitlab::SidekiqStatus::ClientMiddleware do
describe '#call' do
it 'tracks the job in Redis' do
expect(Gitlab::SidekiqStatus).to receive(:set).with('123', Gitlab::SidekiqStatus::DEFAULT_EXPIRATION)
diff --git a/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb b/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb
index 40bcb49d1d3..5a0c4cbd1b5 100644
--- a/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb
+++ b/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqStatus::ServerMiddleware do
+RSpec.describe Gitlab::SidekiqStatus::ServerMiddleware do
describe '#call' do
it 'stops tracking of a job upon completion' do
expect(Gitlab::SidekiqStatus).to receive(:unset).with('123')
diff --git a/spec/lib/gitlab/sidekiq_status_spec.rb b/spec/lib/gitlab/sidekiq_status_spec.rb
index 7b5c75b2f3b..dd5b8856ccd 100644
--- a/spec/lib/gitlab/sidekiq_status_spec.rb
+++ b/spec/lib/gitlab/sidekiq_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqStatus do
+RSpec.describe Gitlab::SidekiqStatus do
describe '.set', :clean_gitlab_redis_shared_state do
it 'stores the job ID' do
described_class.set('123')
diff --git a/spec/lib/gitlab/sidekiq_versioning/manager_spec.rb b/spec/lib/gitlab/sidekiq_versioning/manager_spec.rb
index a528ce201a2..84161d9236e 100644
--- a/spec/lib/gitlab/sidekiq_versioning/manager_spec.rb
+++ b/spec/lib/gitlab/sidekiq_versioning/manager_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqVersioning::Manager do
+RSpec.describe Gitlab::SidekiqVersioning::Manager do
before do
Sidekiq::Manager.prepend described_class
end
diff --git a/spec/lib/gitlab/sidekiq_versioning_spec.rb b/spec/lib/gitlab/sidekiq_versioning_spec.rb
index 11c866894c2..ed9650fc166 100644
--- a/spec/lib/gitlab/sidekiq_versioning_spec.rb
+++ b/spec/lib/gitlab/sidekiq_versioning_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SidekiqVersioning, :redis do
+RSpec.describe Gitlab::SidekiqVersioning, :redis do
let(:foo_worker) do
Class.new do
def self.name
diff --git a/spec/lib/gitlab/slash_commands/application_help_spec.rb b/spec/lib/gitlab/slash_commands/application_help_spec.rb
index afa63c21584..b82121bf3a8 100644
--- a/spec/lib/gitlab/slash_commands/application_help_spec.rb
+++ b/spec/lib/gitlab/slash_commands/application_help_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::ApplicationHelp do
+RSpec.describe Gitlab::SlashCommands::ApplicationHelp do
let(:params) { { command: '/gitlab', text: 'help' } }
let(:project) { build(:project) }
diff --git a/spec/lib/gitlab/slash_commands/command_spec.rb b/spec/lib/gitlab/slash_commands/command_spec.rb
index 9849cf78b2f..069577b3846 100644
--- a/spec/lib/gitlab/slash_commands/command_spec.rb
+++ b/spec/lib/gitlab/slash_commands/command_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Command do
+RSpec.describe Gitlab::SlashCommands::Command do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:chat_name) { double(:chat_name, user: user) }
diff --git a/spec/lib/gitlab/slash_commands/deploy_spec.rb b/spec/lib/gitlab/slash_commands/deploy_spec.rb
index fb9969800a2..36f47c711bc 100644
--- a/spec/lib/gitlab/slash_commands/deploy_spec.rb
+++ b/spec/lib/gitlab/slash_commands/deploy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Deploy do
+RSpec.describe Gitlab::SlashCommands::Deploy do
describe '#execute' do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/slash_commands/issue_close_spec.rb b/spec/lib/gitlab/slash_commands/issue_close_spec.rb
index c0760ce0ba6..cf6ac2c5267 100644
--- a/spec/lib/gitlab/slash_commands/issue_close_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_close_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::IssueClose do
+RSpec.describe Gitlab::SlashCommands::IssueClose do
describe '#execute' do
let(:issue) { create(:issue, project: project) }
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/slash_commands/issue_comment_spec.rb b/spec/lib/gitlab/slash_commands/issue_comment_spec.rb
index c6f56d10d1f..f3c3bccfade 100644
--- a/spec/lib/gitlab/slash_commands/issue_comment_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_comment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::IssueComment do
+RSpec.describe Gitlab::SlashCommands::IssueComment do
describe '#execute' do
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project) }
diff --git a/spec/lib/gitlab/slash_commands/issue_move_spec.rb b/spec/lib/gitlab/slash_commands/issue_move_spec.rb
index 1a45b0e06ba..5fffbb2d4cc 100644
--- a/spec/lib/gitlab/slash_commands/issue_move_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_move_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::IssueMove, service: true do
+RSpec.describe Gitlab::SlashCommands::IssueMove, service: true do
describe '#match' do
shared_examples_for 'move command' do |text_command|
it 'can be parsed to extract the needed fields' do
diff --git a/spec/lib/gitlab/slash_commands/issue_new_spec.rb b/spec/lib/gitlab/slash_commands/issue_new_spec.rb
index 90f0518a63e..c17cee887ee 100644
--- a/spec/lib/gitlab/slash_commands/issue_new_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_new_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::IssueNew do
+RSpec.describe Gitlab::SlashCommands::IssueNew do
describe '#execute' do
let(:project) { create(:project) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/slash_commands/issue_search_spec.rb b/spec/lib/gitlab/slash_commands/issue_search_spec.rb
index a142c8e4c92..8a86aaf67e3 100644
--- a/spec/lib/gitlab/slash_commands/issue_search_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_search_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::IssueSearch do
+RSpec.describe Gitlab::SlashCommands::IssueSearch do
describe '#execute' do
let!(:issue) { create(:issue, project: project, title: 'find me') }
let!(:confidential) { create(:issue, :confidential, project: project, title: 'mepmep find') }
diff --git a/spec/lib/gitlab/slash_commands/issue_show_spec.rb b/spec/lib/gitlab/slash_commands/issue_show_spec.rb
index e53f79dcd86..62628daf24d 100644
--- a/spec/lib/gitlab/slash_commands/issue_show_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_show_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::IssueShow do
+RSpec.describe Gitlab::SlashCommands::IssueShow do
describe '#execute' do
let(:issue) { create(:issue, project: project) }
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/slash_commands/presenters/access_spec.rb b/spec/lib/gitlab/slash_commands/presenters/access_spec.rb
index 804184a7173..5d62e96971b 100644
--- a/spec/lib/gitlab/slash_commands/presenters/access_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Presenters::Access do
+RSpec.describe Gitlab::SlashCommands::Presenters::Access do
shared_examples_for 'displays an error message' do
it do
expect(subject[:text]).to match(error_message)
diff --git a/spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb b/spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb
index 9c2e9ab982f..c6f188de576 100644
--- a/spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Presenters::Deploy do
+RSpec.describe Gitlab::SlashCommands::Presenters::Deploy do
let(:build) { create(:ci_build) }
describe '#present' do
diff --git a/spec/lib/gitlab/slash_commands/presenters/error_spec.rb b/spec/lib/gitlab/slash_commands/presenters/error_spec.rb
index 30ff81510c1..24d6aad1726 100644
--- a/spec/lib/gitlab/slash_commands/presenters/error_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/error_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Presenters::Error do
+RSpec.describe Gitlab::SlashCommands::Presenters::Error do
subject { described_class.new('Error').message }
it { is_expected.to be_a(Hash) }
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_close_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_close_spec.rb
index adc13b4ee56..daa859d4de7 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_close_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_close_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Presenters::IssueClose do
+RSpec.describe Gitlab::SlashCommands::Presenters::IssueClose do
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project) }
let(:attachment) { subject[:attachments].first }
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb
index 3741563a744..109b4b8fee1 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Presenters::IssueComment do
+RSpec.describe Gitlab::SlashCommands::Presenters::IssueComment do
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:note) { create(:note, project: project, noteable: issue) }
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
index 7726c3b6a87..df949154d4c 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Presenters::IssueMove do
+RSpec.describe Gitlab::SlashCommands::Presenters::IssueMove do
let_it_be(:admin) { create(:admin) }
let_it_be(:project, reload: true) { create(:project) }
let_it_be(:other_project) { create(:project) }
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb
index f926783fbea..03a94ea5e29 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Presenters::IssueNew do
+RSpec.describe Gitlab::SlashCommands::Presenters::IssueNew do
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project) }
let(:attachment) { subject[:attachments].first }
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb
index e1c011133c4..c12e7fa7ee8 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Presenters::IssueSearch do
+RSpec.describe Gitlab::SlashCommands::Presenters::IssueSearch do
let(:project) { create(:project) }
let(:message) { subject[:text] }
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb
index 47b9a67f54f..91fd32d2b55 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Presenters::IssueShow do
+RSpec.describe Gitlab::SlashCommands::Presenters::IssueShow do
let(:user) { create(:user, :with_avatar) }
let(:project) { create(:project, creator: user) }
let(:issue) { create(:issue, project: project) }
diff --git a/spec/lib/gitlab/slash_commands/presenters/run_spec.rb b/spec/lib/gitlab/slash_commands/presenters/run_spec.rb
index f3ab01ef6bb..3a775e8f101 100644
--- a/spec/lib/gitlab/slash_commands/presenters/run_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/run_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Presenters::Run do
+RSpec.describe Gitlab::SlashCommands::Presenters::Run do
let(:presenter) { described_class.new }
describe '#present' do
diff --git a/spec/lib/gitlab/slash_commands/run_spec.rb b/spec/lib/gitlab/slash_commands/run_spec.rb
index 32a23129e3c..c9ff580d586 100644
--- a/spec/lib/gitlab/slash_commands/run_spec.rb
+++ b/spec/lib/gitlab/slash_commands/run_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Run do
+RSpec.describe Gitlab::SlashCommands::Run do
describe '.available?' do
it 'returns true when builds are enabled for the project' do
project = double(:project, builds_enabled?: true)
diff --git a/spec/lib/gitlab/slug/environment_spec.rb b/spec/lib/gitlab/slug/environment_spec.rb
index 7dc583a94b8..f516322b937 100644
--- a/spec/lib/gitlab/slug/environment_spec.rb
+++ b/spec/lib/gitlab/slug/environment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Slug::Environment do
+RSpec.describe Gitlab::Slug::Environment do
describe '#generate' do
{
"staging-12345678901234567" => "staging-123456789-q517sa",
diff --git a/spec/lib/gitlab/snippet_search_results_spec.rb b/spec/lib/gitlab/snippet_search_results_spec.rb
index a41be0eaa95..e1ae26a4d9e 100644
--- a/spec/lib/gitlab/snippet_search_results_spec.rb
+++ b/spec/lib/gitlab/snippet_search_results_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SnippetSearchResults do
+RSpec.describe Gitlab::SnippetSearchResults do
include SearchHelpers
let_it_be(:snippet) { create(:snippet, content: 'foo', file_name: 'foo') }
diff --git a/spec/lib/gitlab/sourcegraph_spec.rb b/spec/lib/gitlab/sourcegraph_spec.rb
index ef4008960a9..ad947475f06 100644
--- a/spec/lib/gitlab/sourcegraph_spec.rb
+++ b/spec/lib/gitlab/sourcegraph_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Sourcegraph do
+RSpec.describe Gitlab::Sourcegraph do
let_it_be(:user) { create(:user) }
let(:feature_scope) { true }
diff --git a/spec/lib/gitlab/sql/cte_spec.rb b/spec/lib/gitlab/sql/cte_spec.rb
index e6194924f5a..fdc150cd4b9 100644
--- a/spec/lib/gitlab/sql/cte_spec.rb
+++ b/spec/lib/gitlab/sql/cte_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SQL::CTE do
+RSpec.describe Gitlab::SQL::CTE do
describe '#to_arel' do
it 'generates an Arel relation for the CTE body' do
relation = User.where(id: 1)
diff --git a/spec/lib/gitlab/sql/glob_spec.rb b/spec/lib/gitlab/sql/glob_spec.rb
index 83eed309ecc..8e2b842add6 100644
--- a/spec/lib/gitlab/sql/glob_spec.rb
+++ b/spec/lib/gitlab/sql/glob_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SQL::Glob do
+RSpec.describe Gitlab::SQL::Glob do
describe '.to_like' do
it 'matches * as %' do
expect(glob('apple', '*')).to be(true)
diff --git a/spec/lib/gitlab/sql/pattern_spec.rb b/spec/lib/gitlab/sql/pattern_spec.rb
index 38b93913f6d..220ac2ff6da 100644
--- a/spec/lib/gitlab/sql/pattern_spec.rb
+++ b/spec/lib/gitlab/sql/pattern_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SQL::Pattern do
+RSpec.describe Gitlab::SQL::Pattern do
describe '.to_pattern' do
subject(:to_pattern) { User.to_pattern(query) }
diff --git a/spec/lib/gitlab/sql/recursive_cte_spec.rb b/spec/lib/gitlab/sql/recursive_cte_spec.rb
index b15be56dd6d..02611620989 100644
--- a/spec/lib/gitlab/sql/recursive_cte_spec.rb
+++ b/spec/lib/gitlab/sql/recursive_cte_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SQL::RecursiveCTE do
+RSpec.describe Gitlab::SQL::RecursiveCTE do
let(:cte) { described_class.new(:cte_name) }
describe '#to_arel' do
diff --git a/spec/lib/gitlab/sql/union_spec.rb b/spec/lib/gitlab/sql/union_spec.rb
index f736614ae53..c8be83c093d 100644
--- a/spec/lib/gitlab/sql/union_spec.rb
+++ b/spec/lib/gitlab/sql/union_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SQL::Union do
+RSpec.describe Gitlab::SQL::Union do
let(:relation_1) { User.where(email: 'alice@example.com').select(:id) }
let(:relation_2) { User.where(email: 'bob@example.com').select(:id) }
diff --git a/spec/lib/gitlab/ssh_public_key_spec.rb b/spec/lib/gitlab/ssh_public_key_spec.rb
index 08e008c82d9..e1a588a4b7d 100644
--- a/spec/lib/gitlab/ssh_public_key_spec.rb
+++ b/spec/lib/gitlab/ssh_public_key_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SSHPublicKey, lib: true do
+RSpec.describe Gitlab::SSHPublicKey, lib: true do
let(:key) { attributes_for(:rsa_key_2048)[:key] }
let(:public_key) { described_class.new(key) }
diff --git a/spec/lib/gitlab/static_site_editor/config_spec.rb b/spec/lib/gitlab/static_site_editor/config_spec.rb
index 4cfda83b8f6..b60a6a9b006 100644
--- a/spec/lib/gitlab/static_site_editor/config_spec.rb
+++ b/spec/lib/gitlab/static_site_editor/config_spec.rb
@@ -2,11 +2,14 @@
require 'spec_helper'
-describe Gitlab::StaticSiteEditor::Config do
+RSpec.describe Gitlab::StaticSiteEditor::Config do
subject(:config) { described_class.new(repository, ref, file_path, return_url) }
let_it_be(:namespace) { create(:namespace, name: 'namespace') }
+ let_it_be(:root_group) { create(:group, name: 'group') }
+ let_it_be(:subgroup) { create(:group, name: 'subgroup', parent: root_group) }
let_it_be(:project) { create(:project, :public, :repository, name: 'project', namespace: namespace) }
+ let_it_be(:project_with_subgroup) { create(:project, :public, :repository, name: 'project', group: subgroup) }
let_it_be(:repository) { project.repository }
let(:ref) { 'master' }
@@ -30,6 +33,34 @@ describe Gitlab::StaticSiteEditor::Config do
)
end
+ context 'when namespace is a subgroup' do
+ let(:repository) { project_with_subgroup.repository }
+
+ it 'returns data for the frontend component' do
+ is_expected.to include(
+ namespace: 'group/subgroup',
+ project: 'project',
+ base_url: '/group/subgroup/project/-/sse/master%2FREADME.md'
+ )
+ end
+ end
+
+ context 'when file has .md.erb extension' do
+ let(:file_path) { 'README.md.erb' }
+
+ before do
+ repository.create_file(
+ project.creator,
+ file_path,
+ '',
+ message: 'message',
+ branch_name: 'master'
+ )
+ end
+
+ it { is_expected.to include(is_supported_content: 'true') }
+ end
+
context 'when file path is nested' do
let(:file_path) { 'lib/README.md' }
diff --git a/spec/lib/gitlab/string_placeholder_replacer_spec.rb b/spec/lib/gitlab/string_placeholder_replacer_spec.rb
index 0295bf1265f..8f17bf64005 100644
--- a/spec/lib/gitlab/string_placeholder_replacer_spec.rb
+++ b/spec/lib/gitlab/string_placeholder_replacer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::StringPlaceholderReplacer do
+RSpec.describe Gitlab::StringPlaceholderReplacer do
describe '.render_url' do
it 'returns the nil if the string is blank' do
expect(described_class.replace_string_placeholders(nil, /whatever/)).to be_blank
diff --git a/spec/lib/gitlab/string_range_marker_spec.rb b/spec/lib/gitlab/string_range_marker_spec.rb
index ef9be7cd992..52fab6e3109 100644
--- a/spec/lib/gitlab/string_range_marker_spec.rb
+++ b/spec/lib/gitlab/string_range_marker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::StringRangeMarker do
+RSpec.describe Gitlab::StringRangeMarker do
describe '#mark' do
def mark_diff(rich = nil)
raw = 'abc <def>'
diff --git a/spec/lib/gitlab/string_regex_marker_spec.rb b/spec/lib/gitlab/string_regex_marker_spec.rb
index 2ab1ccc447b..2dadd222820 100644
--- a/spec/lib/gitlab/string_regex_marker_spec.rb
+++ b/spec/lib/gitlab/string_regex_marker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::StringRegexMarker do
+RSpec.describe Gitlab::StringRegexMarker do
describe '#mark' do
context 'with a single occurrence' do
let(:raw) { %{"name": "AFNetworking"} }
diff --git a/spec/lib/gitlab/submodule_links_spec.rb b/spec/lib/gitlab/submodule_links_spec.rb
index 1f2848a29e9..c69326e12be 100644
--- a/spec/lib/gitlab/submodule_links_spec.rb
+++ b/spec/lib/gitlab/submodule_links_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::SubmoduleLinks do
+RSpec.describe Gitlab::SubmoduleLinks do
let(:submodule_item) { double(id: 'hash', path: 'gitlab-foss') }
let(:repo) { double }
let(:links) { described_class.new(repo) }
diff --git a/spec/lib/gitlab/suggestions/commit_message_spec.rb b/spec/lib/gitlab/suggestions/commit_message_spec.rb
index 0774fc80528..1411f64f8b7 100644
--- a/spec/lib/gitlab/suggestions/commit_message_spec.rb
+++ b/spec/lib/gitlab/suggestions/commit_message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Suggestions::CommitMessage do
+RSpec.describe Gitlab::Suggestions::CommitMessage do
def create_suggestion(file_path, new_line, to_content)
position = Gitlab::Diff::Position.new(old_path: file_path,
new_path: file_path,
diff --git a/spec/lib/gitlab/suggestions/file_suggestion_spec.rb b/spec/lib/gitlab/suggestions/file_suggestion_spec.rb
index 6fbbad017c5..1d25bf6edbd 100644
--- a/spec/lib/gitlab/suggestions/file_suggestion_spec.rb
+++ b/spec/lib/gitlab/suggestions/file_suggestion_spec.rb
@@ -2,8 +2,8 @@
require 'spec_helper'
-describe Gitlab::Suggestions::FileSuggestion do
- def create_suggestion(new_line, to_content)
+RSpec.describe Gitlab::Suggestions::FileSuggestion do
+ def create_suggestion(new_line, to_content, lines_above = 0, lines_below = 0)
position = Gitlab::Diff::Position.new(old_path: file_path,
new_path: file_path,
old_line: nil,
@@ -18,6 +18,8 @@ describe Gitlab::Suggestions::FileSuggestion do
create(:suggestion,
:content_from_repo,
note: diff_note,
+ lines_above: lines_above,
+ lines_below: lines_below,
to_content: to_content)
end
@@ -39,27 +41,9 @@ describe Gitlab::Suggestions::FileSuggestion do
create_suggestion(15, " *** SUGGESTION 2 ***\n")
end
- let(:file_suggestion) { described_class.new }
+ let(:suggestions) { [suggestion1, suggestion2] }
- describe '#add_suggestion' do
- it 'succeeds when adding a suggestion for the same file as the original' do
- file_suggestion.add_suggestion(suggestion1)
-
- expect { file_suggestion.add_suggestion(suggestion2) }.not_to raise_error
- end
-
- it 'raises an error when adding a suggestion for a different file' do
- allow(suggestion2)
- .to(receive_message_chain(:diff_file, :file_path)
- .and_return('path/to/different/file'))
-
- file_suggestion.add_suggestion(suggestion1)
-
- expect { file_suggestion.add_suggestion(suggestion2) }.to(
- raise_error(described_class::SuggestionForDifferentFileError)
- )
- end
- end
+ let(:file_suggestion) { described_class.new(file_path, suggestions) }
describe '#line_conflict' do
def stub_suggestions(line_index_spans)
@@ -175,67 +159,296 @@ describe Gitlab::Suggestions::FileSuggestion do
end
describe '#new_content' do
- it 'returns a blob with the suggestions applied to it' do
- file_suggestion.add_suggestion(suggestion1)
- file_suggestion.add_suggestion(suggestion2)
+ context 'with two suggestions' do
+ let(:suggestions) { [suggestion1, suggestion2] }
- expected_content = <<-CONTENT.strip_heredoc
- require 'fileutils'
- require 'open3'
+ it 'returns a blob with the suggestions applied to it' do
+ expected_content = <<-CONTENT.strip_heredoc
+ require 'fileutils'
+ require 'open3'
- module Popen
- extend self
+ module Popen
+ extend self
- def popen(cmd, path=nil)
- unless cmd.is_a?(Array)
- *** SUGGESTION 1 ***
+ def popen(cmd, path=nil)
+ unless cmd.is_a?(Array)
+ *** SUGGESTION 1 ***
+ end
+
+ path ||= Dir.pwd
+
+ vars = {
+ *** SUGGESTION 2 ***
+ }
+
+ options = {
+ chdir: path
+ }
+
+ unless File.directory?(path)
+ FileUtils.mkdir_p(path)
+ end
+
+ @cmd_output = ""
+ @cmd_status = 0
+
+ Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ @cmd_output << stdout.read
+ @cmd_output << stderr.read
+ @cmd_status = wait_thr.value.exitstatus
+ end
+
+ return @cmd_output, @cmd_status
end
+ end
+ CONTENT
- path ||= Dir.pwd
+ expect(file_suggestion.new_content).to eq(expected_content)
+ end
+ end
- vars = {
- *** SUGGESTION 2 ***
- }
+ context 'when no suggestions have been added' do
+ let(:suggestions) { [] }
- options = {
- chdir: path
- }
+ it 'returns an empty string' do
+ expect(file_suggestion.new_content).to eq('')
+ end
+ end
+
+ context 'with multiline suggestions' do
+ let(:suggestions) { [multi_suggestion1, multi_suggestion2, multi_suggestion3] }
+
+ context 'when the previous suggestion increases the line count' do
+ let!(:multi_suggestion1) do
+ create_suggestion(9, " *** SUGGESTION 1 ***\n *** SECOND LINE ***\n *** THIRD LINE ***\n")
+ end
- unless File.directory?(path)
- FileUtils.mkdir_p(path)
+ let!(:multi_suggestion2) do
+ create_suggestion(15, " *** SUGGESTION 2 ***\n *** SECOND LINE ***\n")
+ end
+
+ let!(:multi_suggestion3) do
+ create_suggestion(19, " chdir: *** SUGGESTION 3 ***\n")
+ end
+
+ it 'returns a blob with the suggestions applied to it' do
+ expected_content = <<-CONTENT.strip_heredoc
+ require 'fileutils'
+ require 'open3'
+
+ module Popen
+ extend self
+
+ def popen(cmd, path=nil)
+ unless cmd.is_a?(Array)
+ *** SUGGESTION 1 ***
+ *** SECOND LINE ***
+ *** THIRD LINE ***
+ end
+
+ path ||= Dir.pwd
+
+ vars = {
+ *** SUGGESTION 2 ***
+ *** SECOND LINE ***
+ }
+
+ options = {
+ chdir: *** SUGGESTION 3 ***
+ }
+
+ unless File.directory?(path)
+ FileUtils.mkdir_p(path)
+ end
+
+ @cmd_output = ""
+ @cmd_status = 0
+
+ Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ @cmd_output << stdout.read
+ @cmd_output << stderr.read
+ @cmd_status = wait_thr.value.exitstatus
+ end
+
+ return @cmd_output, @cmd_status
end
+ end
+ CONTENT
+
+ expect(file_suggestion.new_content).to eq(expected_content)
+ end
+ end
- @cmd_output = ""
- @cmd_status = 0
+ context 'when the previous suggestion decreases and increases the line count' do
+ let!(:multi_suggestion1) do
+ create_suggestion(9, " *** SUGGESTION 1 ***\n", 1, 1)
+ end
+
+ let!(:multi_suggestion2) do
+ create_suggestion(15, " *** SUGGESTION 2 ***\n *** SECOND LINE ***\n")
+ end
+
+ let!(:multi_suggestion3) do
+ create_suggestion(19, " chdir: *** SUGGESTION 3 ***\n")
+ end
+
+ it 'returns a blob with the suggestions applied to it' do
+ expected_content = <<-CONTENT.strip_heredoc
+ require 'fileutils'
+ require 'open3'
+
+ module Popen
+ extend self
+
+ def popen(cmd, path=nil)
+ *** SUGGESTION 1 ***
- Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
- @cmd_output << stdout.read
- @cmd_output << stderr.read
- @cmd_status = wait_thr.value.exitstatus
+ path ||= Dir.pwd
+
+ vars = {
+ *** SUGGESTION 2 ***
+ *** SECOND LINE ***
+ }
+
+ options = {
+ chdir: *** SUGGESTION 3 ***
+ }
+
+ unless File.directory?(path)
+ FileUtils.mkdir_p(path)
+ end
+
+ @cmd_output = ""
+ @cmd_status = 0
+
+ Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ @cmd_output << stdout.read
+ @cmd_output << stderr.read
+ @cmd_status = wait_thr.value.exitstatus
+ end
+
+ return @cmd_output, @cmd_status
end
+ end
+ CONTENT
+
+ expect(file_suggestion.new_content).to eq(expected_content)
+ end
+ end
+
+ context 'when the previous suggestion replaces with the same number of lines' do
+ let!(:multi_suggestion1) do
+ create_suggestion(9, " *** SUGGESTION 1 ***\n *** SECOND LINE ***\n *** THIRD LINE ***\n", 1, 1)
+ end
+
+ let!(:multi_suggestion2) do
+ create_suggestion(15, " *** SUGGESTION 2 ***\n")
+ end
+
+ let!(:multi_suggestion3) do
+ create_suggestion(19, " chdir: *** SUGGESTION 3 ***\n")
+ end
+
+ it 'returns a blob with the suggestions applied to it' do
+ expected_content = <<-CONTENT.strip_heredoc
+ require 'fileutils'
+ require 'open3'
+
+ module Popen
+ extend self
+
+ def popen(cmd, path=nil)
+ *** SUGGESTION 1 ***
+ *** SECOND LINE ***
+ *** THIRD LINE ***
+
+ path ||= Dir.pwd
+
+ vars = {
+ *** SUGGESTION 2 ***
+ }
+
+ options = {
+ chdir: *** SUGGESTION 3 ***
+ }
+
+ unless File.directory?(path)
+ FileUtils.mkdir_p(path)
+ end
+
+ @cmd_output = ""
+ @cmd_status = 0
+
+ Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ @cmd_output << stdout.read
+ @cmd_output << stderr.read
+ @cmd_status = wait_thr.value.exitstatus
+ end
- return @cmd_output, @cmd_status
+ return @cmd_output, @cmd_status
+ end
end
+ CONTENT
+
+ expect(file_suggestion.new_content).to eq(expected_content)
end
- CONTENT
+ end
- expect(file_suggestion.new_content).to eq(expected_content)
- end
+ context 'when the previous suggestion replaces multiple lines and the suggestions were applied out of order' do
+ let(:suggestions) { [multi_suggestion1, multi_suggestion3, multi_suggestion2] }
- it 'returns an empty string when no suggestions have been added' do
- expect(file_suggestion.new_content).to eq('')
- end
- end
+ let!(:multi_suggestion1) do
+ create_suggestion(9, " *** SUGGESTION 1 ***\n *** SECOND LINE ***\n *** THIRD LINE ***\n", 1, 1)
+ end
- describe '#file_path' do
- it 'returns the path of the file associated with the suggestions' do
- file_suggestion.add_suggestion(suggestion1)
+ let!(:multi_suggestion3) do
+ create_suggestion(19, " *** SUGGESTION 3 ***\n", 1, 1)
+ end
- expect(file_suggestion.file_path).to eq(file_path)
- end
+ let!(:multi_suggestion2) do
+ create_suggestion(15, " *** SUGGESTION 2 ***\n", 1, 1)
+ end
+
+ it 'returns a blob with the suggestions applied to it' do
+ expected_content = <<-CONTENT.strip_heredoc
+ require 'fileutils'
+ require 'open3'
+
+ module Popen
+ extend self
+
+ def popen(cmd, path=nil)
+ *** SUGGESTION 1 ***
+ *** SECOND LINE ***
+ *** THIRD LINE ***
+
+ path ||= Dir.pwd
+
+ *** SUGGESTION 2 ***
+
+ *** SUGGESTION 3 ***
+
+ unless File.directory?(path)
+ FileUtils.mkdir_p(path)
+ end
+
+ @cmd_output = ""
+ @cmd_status = 0
- it 'returns nil if no suggestions have been added' do
- expect(file_suggestion.file_path).to be(nil)
+ Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ @cmd_output << stdout.read
+ @cmd_output << stderr.read
+ @cmd_status = wait_thr.value.exitstatus
+ end
+
+ return @cmd_output, @cmd_status
+ end
+ end
+ CONTENT
+
+ expect(file_suggestion.new_content).to eq(expected_content)
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/suggestions/suggestion_set_spec.rb b/spec/lib/gitlab/suggestions/suggestion_set_spec.rb
index 8c61e6c42a6..54d79a9d4ba 100644
--- a/spec/lib/gitlab/suggestions/suggestion_set_spec.rb
+++ b/spec/lib/gitlab/suggestions/suggestion_set_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Suggestions::SuggestionSet do
+RSpec.describe Gitlab::Suggestions::SuggestionSet do
def create_suggestion(file_path, new_line, to_content)
position = Gitlab::Diff::Position.new(old_path: file_path,
new_path: file_path,
@@ -87,11 +87,10 @@ describe Gitlab::Suggestions::SuggestionSet do
it 'returns an array of hashes with proper key/value pairs' do
first_action = suggestion_set.actions.first
- file_path, file_suggestion = suggestion_set
- .send(:suggestions_per_file).first
+ file_suggestion = suggestion_set.send(:suggestions_per_file).first
expect(first_action[:action]).to be('update')
- expect(first_action[:file_path]).to eq(file_path)
+ expect(first_action[:file_path]).to eq(file_suggestion.file_path)
expect(first_action[:content]).to eq(file_suggestion.new_content)
end
end
diff --git a/spec/lib/gitlab/tab_width_spec.rb b/spec/lib/gitlab/tab_width_spec.rb
index 3b5014d27e4..f0efb6ec4a7 100644
--- a/spec/lib/gitlab/tab_width_spec.rb
+++ b/spec/lib/gitlab/tab_width_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::TabWidth, lib: true do
+RSpec.describe Gitlab::TabWidth, lib: true do
describe '.css_class_for_user' do
it 'returns default CSS class when user is nil' do
css_class = described_class.css_class_for_user(nil)
diff --git a/spec/lib/gitlab/tcp_checker_spec.rb b/spec/lib/gitlab/tcp_checker_spec.rb
index 9474e79cc5d..12149576de0 100644
--- a/spec/lib/gitlab/tcp_checker_spec.rb
+++ b/spec/lib/gitlab/tcp_checker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::TcpChecker, :permit_dns do
+RSpec.describe Gitlab::TcpChecker, :permit_dns do
before do
@server = TCPServer.new('localhost', 0)
_, @port, _, @ip = @server.addr
diff --git a/spec/lib/gitlab/template/finders/global_template_finder_spec.rb b/spec/lib/gitlab/template/finders/global_template_finder_spec.rb
index 580da497944..e776284b3e8 100644
--- a/spec/lib/gitlab/template/finders/global_template_finder_spec.rb
+++ b/spec/lib/gitlab/template/finders/global_template_finder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Template::Finders::GlobalTemplateFinder do
+RSpec.describe Gitlab::Template::Finders::GlobalTemplateFinder do
let(:base_dir) { Dir.mktmpdir }
def create_template!(name_with_category)
diff --git a/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb b/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
index cd5cde76b73..05f351be702 100644
--- a/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
+++ b/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Template::Finders::RepoTemplateFinder do
+RSpec.describe Gitlab::Template::Finders::RepoTemplateFinder do
let_it_be(:project) { create(:project, :repository) }
let(:categories) { { 'HTML' => 'html' } }
diff --git a/spec/lib/gitlab/template/gitignore_template_spec.rb b/spec/lib/gitlab/template/gitignore_template_spec.rb
index e8f632889ad..365b579d08e 100644
--- a/spec/lib/gitlab/template/gitignore_template_spec.rb
+++ b/spec/lib/gitlab/template/gitignore_template_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Template::GitignoreTemplate do
+RSpec.describe Gitlab::Template::GitignoreTemplate do
subject { described_class }
describe '.all' do
diff --git a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
index 52e100768a7..3004de6fe08 100644
--- a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
+++ b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Template::GitlabCiYmlTemplate do
+RSpec.describe Gitlab::Template::GitlabCiYmlTemplate do
subject { described_class }
describe '.all' do
diff --git a/spec/lib/gitlab/template/issue_template_spec.rb b/spec/lib/gitlab/template/issue_template_spec.rb
index 54e46d3a9ec..4acbdcc4ba6 100644
--- a/spec/lib/gitlab/template/issue_template_spec.rb
+++ b/spec/lib/gitlab/template/issue_template_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Template::IssueTemplate do
+RSpec.describe Gitlab::Template::IssueTemplate do
let(:project) { create(:project, :repository, create_templates: :issue) }
describe '.all' do
diff --git a/spec/lib/gitlab/template/merge_request_template_spec.rb b/spec/lib/gitlab/template/merge_request_template_spec.rb
index bbc184d4dfc..abac0a10e18 100644
--- a/spec/lib/gitlab/template/merge_request_template_spec.rb
+++ b/spec/lib/gitlab/template/merge_request_template_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Template::MergeRequestTemplate do
+RSpec.describe Gitlab::Template::MergeRequestTemplate do
let(:project) { create(:project, :repository, create_templates: :merge_request) }
describe '.all' do
diff --git a/spec/lib/gitlab/themes_spec.rb b/spec/lib/gitlab/themes_spec.rb
index e0278eb9c7f..68ff28becfa 100644
--- a/spec/lib/gitlab/themes_spec.rb
+++ b/spec/lib/gitlab/themes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Themes, lib: true do
+RSpec.describe Gitlab::Themes, lib: true do
describe '.body_classes' do
it 'returns a space-separated list of class names' do
css = described_class.body_classes
diff --git a/spec/lib/gitlab/throttle_spec.rb b/spec/lib/gitlab/throttle_spec.rb
index e3679a1a721..ca2abe94ad2 100644
--- a/spec/lib/gitlab/throttle_spec.rb
+++ b/spec/lib/gitlab/throttle_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Throttle do
+RSpec.describe Gitlab::Throttle do
describe '.protected_paths_enabled?' do
subject { described_class.protected_paths_enabled? }
diff --git a/spec/lib/gitlab/time_tracking_formatter_spec.rb b/spec/lib/gitlab/time_tracking_formatter_spec.rb
index cfc804c13a7..8bbd1263057 100644
--- a/spec/lib/gitlab/time_tracking_formatter_spec.rb
+++ b/spec/lib/gitlab/time_tracking_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::TimeTrackingFormatter do
+RSpec.describe Gitlab::TimeTrackingFormatter do
describe '#parse' do
subject { described_class.parse(duration_string) }
diff --git a/spec/lib/gitlab/tracking/incident_management_spec.rb b/spec/lib/gitlab/tracking/incident_management_spec.rb
index 6f7e04b7c16..e8131b4eeee 100644
--- a/spec/lib/gitlab/tracking/incident_management_spec.rb
+++ b/spec/lib/gitlab/tracking/incident_management_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Tracking::IncidentManagement do
+RSpec.describe Gitlab::Tracking::IncidentManagement do
describe '.track_from_params' do
shared_examples 'a tracked event' do |label, value = nil|
it 'creates the tracking event with the correct details' do
diff --git a/spec/lib/gitlab/tracking_spec.rb b/spec/lib/gitlab/tracking_spec.rb
index 82828c2dcce..65b6d9c8899 100644
--- a/spec/lib/gitlab/tracking_spec.rb
+++ b/spec/lib/gitlab/tracking_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::Tracking do
+RSpec.describe Gitlab::Tracking do
let(:timestamp) { Time.utc(2017, 3, 22) }
before do
diff --git a/spec/lib/gitlab/tree_summary_spec.rb b/spec/lib/gitlab/tree_summary_spec.rb
index 593b8655e80..4bd08fab60a 100644
--- a/spec/lib/gitlab/tree_summary_spec.rb
+++ b/spec/lib/gitlab/tree_summary_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::TreeSummary do
+RSpec.describe Gitlab::TreeSummary do
using RSpec::Parameterized::TableSyntax
let(:project) { create(:project, :empty_repo) }
@@ -47,15 +47,17 @@ describe Gitlab::TreeSummary do
end
describe '#summarize (entries)' do
- let(:limit) { 2 }
+ let(:limit) { 4 }
custom_files = {
'a.txt' => '',
'b.txt' => '',
- 'directory/c.txt' => ''
+ 'directory/c.txt' => '',
+ ':dir/test.txt' => '',
+ ':file' => ''
}
- let(:project) { create(:project, :custom_repo, files: custom_files) }
+ let!(:project) { create(:project, :custom_repo, files: custom_files) }
let(:commit) { repo.head_commit }
subject(:entries) { summary.summarize.first }
@@ -63,13 +65,16 @@ describe Gitlab::TreeSummary do
it 'summarizes the entries within the window' do
is_expected.to contain_exactly(
a_hash_including(type: :tree, file_name: 'directory'),
- a_hash_including(type: :blob, file_name: 'a.txt')
+ a_hash_including(type: :blob, file_name: 'a.txt'),
+ a_hash_including(type: :blob, file_name: ':file'),
+ a_hash_including(type: :tree, file_name: ':dir')
# b.txt is excluded by the limit
)
end
it 'references the commit and commit path in entries' do
- entry = entries.first
+ # There are 2 trees and the summary is not ordered
+ entry = entries.find { |entry| entry[:commit].id == commit.id }
expected_commit_path = Gitlab::Routing.url_helpers.project_commit_path(project, commit)
expect(entry[:commit]).to be_a(::Commit)
@@ -85,6 +90,14 @@ describe Gitlab::TreeSummary do
end
end
+ context 'in a subdirectory with a pathspec character' do
+ let(:path) { ':dir' }
+
+ it 'summarizes the entries in the subdirectory' do
+ is_expected.to contain_exactly(a_hash_including(type: :blob, file_name: 'test.txt'))
+ end
+ end
+
context 'in a non-existent subdirectory' do
let(:path) { 'tmp' }
@@ -92,7 +105,7 @@ describe Gitlab::TreeSummary do
end
context 'custom offset and limit' do
- let(:offset) { 2 }
+ let(:offset) { 4 }
it 'returns entries from the offset' do
is_expected.to contain_exactly(a_hash_including(type: :blob, file_name: 'b.txt'))
diff --git a/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb b/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb
index cdd681a9345..42fc84cf076 100644
--- a/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb
+++ b/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb
@@ -4,7 +4,7 @@ require 'fast_spec_helper'
require 'support/shared_examples/lib/gitlab/malicious_regexp_shared_examples'
require 'support/helpers/stub_feature_flags'
-describe Gitlab::UntrustedRegexp::RubySyntax do
+RSpec.describe Gitlab::UntrustedRegexp::RubySyntax do
describe '.matches_syntax?' do
it 'returns true if regexp is valid' do
expect(described_class.matches_syntax?('/some .* thing/'))
diff --git a/spec/lib/gitlab/untrusted_regexp_spec.rb b/spec/lib/gitlab/untrusted_regexp_spec.rb
index 60f14d0277b..aac3d5e27f5 100644
--- a/spec/lib/gitlab/untrusted_regexp_spec.rb
+++ b/spec/lib/gitlab/untrusted_regexp_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require 'support/shared_examples/lib/gitlab/malicious_regexp_shared_examples'
-describe Gitlab::UntrustedRegexp do
+RSpec.describe Gitlab::UntrustedRegexp do
describe '#initialize' do
subject { described_class.new(pattern) }
diff --git a/spec/lib/gitlab/updated_notes_paginator_spec.rb b/spec/lib/gitlab/updated_notes_paginator_spec.rb
new file mode 100644
index 00000000000..eedc11777d4
--- /dev/null
+++ b/spec/lib/gitlab/updated_notes_paginator_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::UpdatedNotesPaginator do
+ let(:issue) { create(:issue) }
+
+ let(:project) { issue.project }
+ let(:finder) { NotesFinder.new(user, target: issue, last_fetched_at: last_fetched_at) }
+ let(:user) { issue.author }
+
+ let!(:page_1) { create_list(:note, 2, noteable: issue, project: project, updated_at: 2.days.ago) }
+ let!(:page_2) { [create(:note, noteable: issue, project: project, updated_at: 1.day.ago)] }
+
+ let(:page_1_boundary) { page_1.last.updated_at + NotesFinder::FETCH_OVERLAP }
+
+ around do |example|
+ Timecop.freeze do
+ example.run
+ end
+ end
+
+ before do
+ stub_const("Gitlab::UpdatedNotesPaginator::LIMIT", 2)
+ end
+
+ subject(:paginator) { described_class.new(finder.execute, last_fetched_at: last_fetched_at) }
+
+ describe 'last_fetched_at: start of time' do
+ let(:last_fetched_at) { Time.at(0) }
+
+ it 'calculates the first page of notes', :aggregate_failures do
+ expect(paginator.notes).to match_array(page_1)
+ expect(paginator.metadata).to match(
+ more: true,
+ last_fetched_at: microseconds(page_1_boundary)
+ )
+ end
+ end
+
+ describe 'last_fetched_at: start of final page' do
+ let(:last_fetched_at) { page_1_boundary }
+
+ it 'calculates a final page', :aggregate_failures do
+ expect(paginator.notes).to match_array(page_2)
+ expect(paginator.metadata).to match(
+ more: false,
+ last_fetched_at: microseconds(Time.zone.now)
+ )
+ end
+ end
+
+ # Convert a time to an integer number of microseconds
+ def microseconds(time)
+ (time.to_i * 1_000_000) + time.usec
+ end
+end
diff --git a/spec/lib/gitlab/uploads_transfer_spec.rb b/spec/lib/gitlab/uploads_transfer_spec.rb
index 16560fc8f12..80bbf68e98a 100644
--- a/spec/lib/gitlab/uploads_transfer_spec.rb
+++ b/spec/lib/gitlab/uploads_transfer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UploadsTransfer do
+RSpec.describe Gitlab::UploadsTransfer do
it 'leaves avatar uploads where they are' do
project_with_avatar = create(:project, :with_avatar)
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index 08678de87c9..7edfde09864 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
+RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
include StubRequests
describe '#validate!' do
diff --git a/spec/lib/gitlab/url_blockers/domain_whitelist_entry_spec.rb b/spec/lib/gitlab/url_blockers/domain_whitelist_entry_spec.rb
index 34ea6c328e6..58bae109146 100644
--- a/spec/lib/gitlab/url_blockers/domain_whitelist_entry_spec.rb
+++ b/spec/lib/gitlab/url_blockers/domain_whitelist_entry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UrlBlockers::DomainWhitelistEntry do
+RSpec.describe Gitlab::UrlBlockers::DomainWhitelistEntry do
let(:domain) { 'www.example.com' }
describe '#initialize' do
diff --git a/spec/lib/gitlab/url_blockers/ip_whitelist_entry_spec.rb b/spec/lib/gitlab/url_blockers/ip_whitelist_entry_spec.rb
index 042d135d265..52f9b31165a 100644
--- a/spec/lib/gitlab/url_blockers/ip_whitelist_entry_spec.rb
+++ b/spec/lib/gitlab/url_blockers/ip_whitelist_entry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UrlBlockers::IpWhitelistEntry do
+RSpec.describe Gitlab::UrlBlockers::IpWhitelistEntry do
let(:ipv4) { IPAddr.new('192.168.1.1') }
describe '#initialize' do
diff --git a/spec/lib/gitlab/url_blockers/url_whitelist_spec.rb b/spec/lib/gitlab/url_blockers/url_whitelist_spec.rb
index e43cd819838..7a65516be3c 100644
--- a/spec/lib/gitlab/url_blockers/url_whitelist_spec.rb
+++ b/spec/lib/gitlab/url_blockers/url_whitelist_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UrlBlockers::UrlWhitelist do
+RSpec.describe Gitlab::UrlBlockers::UrlWhitelist do
include StubRequests
let(:whitelist) { [] }
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index e91d17bfbe8..a16ff252bc1 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UrlBuilder do
+RSpec.describe Gitlab::UrlBuilder do
subject { described_class }
describe '#build' do
@@ -87,12 +87,41 @@ describe Gitlab::UrlBuilder do
end
context 'when passing a Snippet' do
- let(:snippet) { build_stubbed(:personal_snippet) }
+ let_it_be(:personal_snippet) { create(:personal_snippet, :repository) }
+ let_it_be(:project_snippet) { create(:project_snippet, :repository) }
+ let(:blob) { snippet.blobs.first }
+ let(:ref) { blob.repository.root_ref }
- it 'returns a raw snippet URL if requested' do
- url = subject.build(snippet, raw: true)
+ context 'for a PersonalSnippet' do
+ let(:snippet) { personal_snippet }
- expect(url).to eq "#{Gitlab.config.gitlab.url}/snippets/#{snippet.id}/raw"
+ it 'returns a raw snippet URL if requested' do
+ url = subject.build(snippet, raw: true)
+
+ expect(url).to eq "#{Gitlab.config.gitlab.url}/snippets/#{snippet.id}/raw"
+ end
+
+ it 'returns a raw snippet blob URL if requested' do
+ url = subject.build(snippet, file: blob.path, ref: ref)
+
+ expect(url).to eq "#{Gitlab.config.gitlab.url}/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}"
+ end
+ end
+
+ context 'for a ProjectSnippet' do
+ let(:snippet) { project_snippet }
+
+ it 'returns a raw snippet URL if requested' do
+ url = subject.build(snippet, raw: true)
+
+ expect(url).to eq "#{Gitlab.config.gitlab.url}/#{snippet.project.full_path}/snippets/#{snippet.id}/raw"
+ end
+
+ it 'returns a raw snippet blob URL if requested' do
+ url = subject.build(snippet, file: blob.path, ref: ref)
+
+ expect(url).to eq "#{Gitlab.config.gitlab.url}/#{snippet.project.full_path}/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}"
+ end
end
end
diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb
index caca22eb98b..aba4ca109a9 100644
--- a/spec/lib/gitlab/url_sanitizer_spec.rb
+++ b/spec/lib/gitlab/url_sanitizer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UrlSanitizer do
+RSpec.describe Gitlab::UrlSanitizer do
using RSpec::Parameterized::TableSyntax
describe '.sanitize' do
diff --git a/spec/lib/gitlab/usage_data/topology_spec.rb b/spec/lib/gitlab/usage_data/topology_spec.rb
new file mode 100644
index 00000000000..2a7adea261d
--- /dev/null
+++ b/spec/lib/gitlab/usage_data/topology_spec.rb
@@ -0,0 +1,562 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::UsageData::Topology do
+ include UsageDataHelpers
+
+ describe '#topology_usage_data' do
+ subject { described_class.new.topology_usage_data }
+
+ before do
+ # this pins down time shifts when benchmarking durations
+ allow(Process).to receive(:clock_gettime).and_return(0)
+ end
+
+ context 'when embedded Prometheus server is enabled' do
+ before do
+ expect(Gitlab::Prometheus::Internal).to receive(:prometheus_enabled?).and_return(true)
+ expect(Gitlab::Prometheus::Internal).to receive(:uri).and_return('http://prom:9090')
+ end
+
+ context 'tracking node metrics' do
+ it 'contains node level metrics for each instance' do
+ expect_prometheus_api_to(
+ receive_app_request_volume_query,
+ receive_node_memory_query,
+ receive_node_cpu_count_query,
+ receive_node_uname_info_query,
+ receive_node_service_memory_rss_query,
+ receive_node_service_memory_uss_query,
+ receive_node_service_memory_pss_query,
+ receive_node_service_process_count_query,
+ receive_node_service_app_server_workers_query
+ )
+
+ expect(subject[:topology]).to eq({
+ duration_s: 0,
+ application_requests_per_hour: 36,
+ failures: [],
+ nodes: [
+ {
+ node_memory_total_bytes: 512,
+ node_cpus: 8,
+ node_uname_info: {
+ machine: 'x86_64',
+ sysname: 'Linux',
+ release: '4.19.76-linuxkit'
+ },
+ node_services: [
+ {
+ name: 'web',
+ process_count: 10,
+ process_memory_rss: 300,
+ process_memory_uss: 301,
+ process_memory_pss: 302,
+ server: 'puma'
+ },
+ {
+ name: 'sidekiq',
+ process_count: 5,
+ process_memory_rss: 303
+ }
+ ]
+ },
+ {
+ node_memory_total_bytes: 1024,
+ node_cpus: 16,
+ node_uname_info: {
+ machine: 'x86_64',
+ sysname: 'Linux',
+ release: '4.15.0-101-generic'
+ },
+ node_services: [
+ {
+ name: 'sidekiq',
+ process_count: 15,
+ process_memory_rss: 400,
+ process_memory_pss: 401
+ },
+ {
+ name: 'redis',
+ process_count: 1,
+ process_memory_rss: 402
+ },
+ {
+ name: 'registry',
+ process_count: 1
+ },
+ {
+ name: 'web',
+ server: 'unicorn'
+ }
+ ]
+ }
+ ]
+ })
+ end
+ end
+
+ context 'and some node memory metrics are missing' do
+ it 'removes the respective entries and includes the failures' do
+ expect_prometheus_api_to(
+ receive_app_request_volume_query(result: []),
+ receive_node_memory_query(result: []),
+ receive_node_cpu_count_query,
+ receive_node_uname_info_query,
+ receive_node_service_memory_rss_query(result: []),
+ receive_node_service_memory_uss_query(result: []),
+ receive_node_service_memory_pss_query,
+ receive_node_service_process_count_query,
+ receive_node_service_app_server_workers_query(result: [])
+ )
+
+ expect(subject[:topology]).to eq({
+ duration_s: 0,
+ failures: [
+ { 'app_requests' => 'empty_result' },
+ { 'node_memory' => 'empty_result' },
+ { 'service_rss' => 'empty_result' },
+ { 'service_uss' => 'empty_result' },
+ { 'service_workers' => 'empty_result' }
+ ],
+ nodes: [
+ {
+ node_cpus: 16,
+ node_uname_info: {
+ machine: 'x86_64',
+ release: '4.15.0-101-generic',
+ sysname: 'Linux'
+ },
+ node_services: [
+ {
+ name: 'sidekiq',
+ process_count: 15,
+ process_memory_pss: 401
+ },
+ {
+ name: 'redis',
+ process_count: 1
+ },
+ {
+ name: 'registry',
+ process_count: 1
+ }
+ ]
+ },
+ {
+ node_cpus: 8,
+ node_uname_info: {
+ machine: 'x86_64',
+ release: '4.19.76-linuxkit',
+ sysname: 'Linux'
+ },
+ node_services: [
+ {
+ name: 'web',
+ process_count: 10,
+ process_memory_pss: 302
+ },
+ {
+ name: 'sidekiq',
+ process_count: 5
+ }
+ ]
+ }
+ ]
+ })
+ end
+ end
+
+ context 'and services run on the same node but report different instance values' do
+ let(:node_memory_response) do
+ [
+ {
+ 'metric' => { 'instance' => 'localhost:9100' },
+ 'value' => [1000, '512']
+ }
+ ]
+ end
+ let(:node_uname_info_response) do
+ [
+ {
+ "metric" => {
+ "__name__" => "node_uname_info",
+ "domainname" => "(none)",
+ "instance" => "127.0.0.1:9100",
+ "job" => "node_exporter",
+ "machine" => "x86_64",
+ "nodename" => "127.0.0.1",
+ "release" => "4.19.76-linuxkit",
+ "sysname" => "Linux"
+ },
+ "value" => [1592463033.359, "1"]
+ }
+ ]
+ end
+ # The services in this response should all be mapped to localhost i.e. the same node
+ let(:service_memory_response) do
+ [
+ {
+ 'metric' => { 'instance' => 'localhost:8080', 'job' => 'gitlab-rails' },
+ 'value' => [1000, '10']
+ },
+ {
+ 'metric' => { 'instance' => '127.0.0.1:8090', 'job' => 'gitlab-sidekiq' },
+ 'value' => [1000, '11']
+ },
+ {
+ 'metric' => { 'instance' => '0.0.0.0:9090', 'job' => 'prometheus' },
+ 'value' => [1000, '12']
+ },
+ {
+ 'metric' => { 'instance' => '[::1]:1234', 'job' => 'redis' },
+ 'value' => [1000, '13']
+ },
+ {
+ 'metric' => { 'instance' => '[::]:1234', 'job' => 'postgres' },
+ 'value' => [1000, '14']
+ }
+ ]
+ end
+
+ it 'normalizes equivalent instance values and maps them to the same node' do
+ expect_prometheus_api_to(
+ receive_app_request_volume_query(result: []),
+ receive_node_memory_query(result: node_memory_response),
+ receive_node_cpu_count_query(result: []),
+ receive_node_uname_info_query(result: node_uname_info_response),
+ receive_node_service_memory_rss_query(result: service_memory_response),
+ receive_node_service_memory_uss_query(result: []),
+ receive_node_service_memory_pss_query(result: []),
+ receive_node_service_process_count_query(result: []),
+ receive_node_service_app_server_workers_query(result: [])
+ )
+
+ expect(subject[:topology]).to eq({
+ duration_s: 0,
+ failures: [
+ { 'app_requests' => 'empty_result' },
+ { 'node_cpus' => 'empty_result' },
+ { 'service_uss' => 'empty_result' },
+ { 'service_pss' => 'empty_result' },
+ { 'service_process_count' => 'empty_result' },
+ { 'service_workers' => 'empty_result' }
+ ],
+ nodes: [
+ {
+ node_memory_total_bytes: 512,
+ node_uname_info: {
+ machine: 'x86_64',
+ sysname: 'Linux',
+ release: '4.19.76-linuxkit'
+ },
+ node_services: [
+ {
+ name: 'web',
+ process_memory_rss: 10
+ },
+ {
+ name: 'sidekiq',
+ process_memory_rss: 11
+ },
+ {
+ name: 'prometheus',
+ process_memory_rss: 12
+ },
+ {
+ name: 'redis',
+ process_memory_rss: 13
+ },
+ {
+ name: 'postgres',
+ process_memory_rss: 14
+ }
+ ]
+ }
+ ]
+ })
+ end
+ end
+
+ context 'and node metrics are missing but service metrics exist' do
+ it 'still reports service metrics' do
+ expect_prometheus_api_to(
+ receive_app_request_volume_query(result: []),
+ receive_node_memory_query(result: []),
+ receive_node_cpu_count_query(result: []),
+ receive_node_uname_info_query(result: []),
+ receive_node_service_memory_rss_query,
+ receive_node_service_memory_uss_query(result: []),
+ receive_node_service_memory_pss_query(result: []),
+ receive_node_service_process_count_query(result: []),
+ receive_node_service_app_server_workers_query(result: [])
+ )
+
+ expect(subject[:topology]).to eq({
+ duration_s: 0,
+ failures: [
+ { 'app_requests' => 'empty_result' },
+ { 'node_memory' => 'empty_result' },
+ { 'node_cpus' => 'empty_result' },
+ { 'node_uname_info' => 'empty_result' },
+ { 'service_uss' => 'empty_result' },
+ { 'service_pss' => 'empty_result' },
+ { 'service_process_count' => 'empty_result' },
+ { 'service_workers' => 'empty_result' }
+ ],
+ nodes: [
+ {
+ node_services: [
+ {
+ name: 'web',
+ process_memory_rss: 300
+ },
+ {
+ name: 'sidekiq',
+ process_memory_rss: 303
+ }
+ ]
+ },
+ {
+ node_services: [
+ {
+ name: 'sidekiq',
+ process_memory_rss: 400
+ },
+ {
+ name: 'redis',
+ process_memory_rss: 402
+ }
+ ]
+ }
+ ]
+ })
+ end
+ end
+
+ context 'and an error is raised when querying Prometheus' do
+ it 'returns empty result with failures' do
+ expect_prometheus_api_to receive(:query)
+ .at_least(:once)
+ .and_raise(Gitlab::PrometheusClient::ConnectionError)
+
+ expect(subject[:topology]).to eq({
+ duration_s: 0,
+ failures: [
+ { 'app_requests' => 'Gitlab::PrometheusClient::ConnectionError' },
+ { 'node_memory' => 'Gitlab::PrometheusClient::ConnectionError' },
+ { 'node_cpus' => 'Gitlab::PrometheusClient::ConnectionError' },
+ { 'node_uname_info' => 'Gitlab::PrometheusClient::ConnectionError' },
+ { 'service_rss' => 'Gitlab::PrometheusClient::ConnectionError' },
+ { 'service_uss' => 'Gitlab::PrometheusClient::ConnectionError' },
+ { 'service_pss' => 'Gitlab::PrometheusClient::ConnectionError' },
+ { 'service_process_count' => 'Gitlab::PrometheusClient::ConnectionError' },
+ { 'service_workers' => 'Gitlab::PrometheusClient::ConnectionError' }
+ ],
+ nodes: []
+ })
+ end
+ end
+ end
+
+ context 'when embedded Prometheus server is disabled' do
+ it 'returns empty result with no failures' do
+ expect(Gitlab::Prometheus::Internal).to receive(:prometheus_enabled?).and_return(false)
+
+ expect(subject[:topology]).to eq({
+ duration_s: 0,
+ failures: []
+ })
+ end
+ end
+
+ context 'when top-level function raises error' do
+ it 'returns empty result with generic failure' do
+ allow(Gitlab::Prometheus::Internal).to receive(:prometheus_enabled?).and_raise(RuntimeError)
+
+ expect(subject[:topology]).to eq({
+ duration_s: 0,
+ failures: [
+ { 'other' => 'RuntimeError' }
+ ]
+ })
+ end
+ end
+ end
+
+ def receive_app_request_volume_query(result: nil)
+ receive(:query)
+ .with(/gitlab_usage_ping:ops:rate/)
+ .and_return(result || [
+ {
+ 'metric' => { 'component' => 'http_requests', 'service' => 'workhorse' },
+ 'value' => [1000, '0.01']
+ }
+ ])
+ end
+
+ def receive_node_memory_query(result: nil)
+ receive(:query)
+ .with(/node_memory_total_bytes/, an_instance_of(Hash))
+ .and_return(result || [
+ {
+ 'metric' => { 'instance' => 'instance1:8080' },
+ 'value' => [1000, '512']
+ },
+ {
+ 'metric' => { 'instance' => 'instance2:8090' },
+ 'value' => [1000, '1024']
+ }
+ ])
+ end
+
+ def receive_node_cpu_count_query(result: nil)
+ receive(:query)
+ .with(/node_cpus/, an_instance_of(Hash))
+ .and_return(result || [
+ {
+ 'metric' => { 'instance' => 'instance2:8090' },
+ 'value' => [1000, '16']
+ },
+ {
+ 'metric' => { 'instance' => 'instance1:8080' },
+ 'value' => [1000, '8']
+ }
+ ])
+ end
+
+ def receive_node_uname_info_query(result: nil)
+ receive(:query)
+ .with('node_uname_info')
+ .and_return(result || [
+ {
+ "metric" => {
+ "__name__" => "node_uname_info",
+ "domainname" => "(none)",
+ "instance" => "instance1:9100",
+ "job" => "node_exporter",
+ "machine" => "x86_64",
+ "nodename" => "instance1",
+ "release" => "4.19.76-linuxkit",
+ "sysname" => "Linux"
+ },
+ "value" => [1592463033.359, "1"]
+ },
+ {
+ "metric" => {
+ "__name__" => "node_uname_info",
+ "domainname" => "(none)",
+ "instance" => "instance2:9100",
+ "job" => "node_exporter",
+ "machine" => "x86_64",
+ "nodename" => "instance2",
+ "release" => "4.15.0-101-generic",
+ "sysname" => "Linux"
+ },
+ "value" => [1592463033.359, "1"]
+ }
+ ])
+ end
+
+ def receive_node_service_memory_rss_query(result: nil)
+ receive(:query)
+ .with(/process_resident_memory_bytes/, an_instance_of(Hash))
+ .and_return(result || [
+ {
+ 'metric' => { 'instance' => 'instance1:8080', 'job' => 'gitlab-rails' },
+ 'value' => [1000, '300']
+ },
+ {
+ 'metric' => { 'instance' => 'instance1:8090', 'job' => 'gitlab-sidekiq' },
+ 'value' => [1000, '303']
+ },
+ # instance 2: runs a dedicated Sidekiq + Redis (which uses a different metric name)
+ {
+ 'metric' => { 'instance' => 'instance2:8090', 'job' => 'gitlab-sidekiq' },
+ 'value' => [1000, '400']
+ },
+ {
+ 'metric' => { 'instance' => 'instance2:9121', 'job' => 'redis' },
+ 'value' => [1000, '402']
+ }
+ ])
+ end
+
+ def receive_node_service_memory_uss_query(result: nil)
+ receive(:query)
+ .with(/process_unique_memory_bytes/, an_instance_of(Hash))
+ .and_return(result || [
+ {
+ 'metric' => { 'instance' => 'instance1:8080', 'job' => 'gitlab-rails' },
+ 'value' => [1000, '301']
+ }
+ ])
+ end
+
+ def receive_node_service_memory_pss_query(result: nil)
+ receive(:query)
+ .with(/process_proportional_memory_bytes/, an_instance_of(Hash))
+ .and_return(result || [
+ {
+ 'metric' => { 'instance' => 'instance1:8080', 'job' => 'gitlab-rails' },
+ 'value' => [1000, '302']
+ },
+ {
+ 'metric' => { 'instance' => 'instance2:8090', 'job' => 'gitlab-sidekiq' },
+ 'value' => [1000, '401']
+ }
+ ])
+ end
+
+ def receive_node_service_process_count_query(result: nil)
+ receive(:query)
+ .with(/service_process:count/, an_instance_of(Hash))
+ .and_return(result || [
+ # instance 1
+ {
+ 'metric' => { 'instance' => 'instance1:8080', 'job' => 'gitlab-rails' },
+ 'value' => [1000, '10']
+ },
+ {
+ 'metric' => { 'instance' => 'instance1:8090', 'job' => 'gitlab-sidekiq' },
+ 'value' => [1000, '5']
+ },
+ # instance 2
+ {
+ 'metric' => { 'instance' => 'instance2:8090', 'job' => 'gitlab-sidekiq' },
+ 'value' => [1000, '15']
+ },
+ {
+ 'metric' => { 'instance' => 'instance2:9121', 'job' => 'redis' },
+ 'value' => [1000, '1']
+ },
+ {
+ 'metric' => { 'instance' => 'instance2:8080', 'job' => 'registry' },
+ 'value' => [1000, '1']
+ },
+ # unknown service => should be stripped out
+ {
+ 'metric' => { 'instance' => 'instance2:9000', 'job' => 'not-a-gitlab-service' },
+ 'value' => [1000, '42']
+ }
+ ])
+ end
+
+ def receive_node_service_app_server_workers_query(result: nil)
+ receive(:query)
+ .with(/app_server_workers/, an_instance_of(Hash))
+ .and_return(result || [
+ # instance 1
+ {
+ 'metric' => { 'instance' => 'instance1:8080', 'job' => 'gitlab-rails', 'server' => 'puma' },
+ 'value' => [1000, '2']
+ },
+ # instance 2
+ {
+ 'metric' => { 'instance' => 'instance2:8080', 'job' => 'gitlab-rails', 'server' => 'unicorn' },
+ 'value' => [1000, '1']
+ }
+ ])
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_concerns/topology_spec.rb b/spec/lib/gitlab/usage_data_concerns/topology_spec.rb
deleted file mode 100644
index 0428900690c..00000000000
--- a/spec/lib/gitlab/usage_data_concerns/topology_spec.rb
+++ /dev/null
@@ -1,220 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::UsageDataConcerns::Topology do
- include UsageDataHelpers
-
- describe '#topology_usage_data' do
- subject { Class.new.extend(described_class).topology_usage_data }
-
- before do
- # this pins down time shifts when benchmarking durations
- allow(Process).to receive(:clock_gettime).and_return(0)
- end
-
- context 'when embedded Prometheus server is enabled' do
- before do
- expect(Gitlab::Prometheus::Internal).to receive(:prometheus_enabled?).and_return(true)
- expect(Gitlab::Prometheus::Internal).to receive(:uri).and_return('http://prom:9090')
- end
-
- it 'contains a topology element' do
- allow_prometheus_queries
-
- expect(subject).to have_key(:topology)
- end
-
- context 'tracking node metrics' do
- it 'contains node level metrics for each instance' do
- expect_prometheus_api_to(
- receive_node_memory_query,
- receive_node_cpu_count_query,
- receive_node_service_memory_query,
- receive_node_service_process_count_query
- )
-
- expect(subject[:topology]).to eq({
- duration_s: 0,
- nodes: [
- {
- node_memory_total_bytes: 512,
- node_cpus: 8,
- node_services: [
- {
- name: 'web',
- process_count: 10,
- process_memory_rss: 300,
- process_memory_uss: 301,
- process_memory_pss: 302
- },
- {
- name: 'sidekiq',
- process_count: 5,
- process_memory_rss: 303
- }
- ]
- },
- {
- node_memory_total_bytes: 1024,
- node_cpus: 16,
- node_services: [
- {
- name: 'sidekiq',
- process_count: 15,
- process_memory_rss: 400,
- process_memory_pss: 401
- },
- {
- name: 'redis',
- process_count: 1,
- process_memory_rss: 402
- }
- ]
- }
- ]
- })
- end
- end
-
- context 'and some node memory metrics are missing' do
- it 'removes the respective entries' do
- expect_prometheus_api_to(
- receive_node_memory_query(result: []),
- receive_node_cpu_count_query,
- receive_node_service_memory_query,
- receive_node_service_process_count_query
- )
-
- keys = subject[:topology][:nodes].flat_map(&:keys)
- expect(keys).not_to include(:node_memory_total_bytes)
- expect(keys).to include(:node_cpus, :node_services)
- end
- end
-
- context 'and no results are found' do
- it 'does not report anything' do
- expect_prometheus_api_to receive(:aggregate).at_least(:once).and_return({})
-
- expect(subject[:topology]).to eq({
- duration_s: 0,
- nodes: []
- })
- end
- end
-
- context 'and a connection error is raised' do
- it 'does not report anything' do
- expect_prometheus_api_to receive(:aggregate).and_raise('Connection failed')
-
- expect(subject[:topology]).to eq({ duration_s: 0 })
- end
- end
- end
-
- context 'when embedded Prometheus server is disabled' do
- it 'does not report anything' do
- expect(Gitlab::Prometheus::Internal).to receive(:prometheus_enabled?).and_return(false)
-
- expect(subject[:topology]).to eq({ duration_s: 0 })
- end
- end
- end
-
- def receive_node_memory_query(result: nil)
- receive(:query)
- .with(/node_memory_MemTotal_bytes/, an_instance_of(Hash))
- .and_return(result || [
- {
- 'metric' => { 'instance' => 'instance1:8080' },
- 'value' => [1000, '512']
- },
- {
- 'metric' => { 'instance' => 'instance2:8090' },
- 'value' => [1000, '1024']
- }
- ])
- end
-
- def receive_node_cpu_count_query(result: nil)
- receive(:query)
- .with(/node_cpu_seconds_total/, an_instance_of(Hash))
- .and_return(result || [
- {
- 'metric' => { 'instance' => 'instance2:8090' },
- 'value' => [1000, '16']
- },
- {
- 'metric' => { 'instance' => 'instance1:8080' },
- 'value' => [1000, '8']
- }
- ])
- end
-
- def receive_node_service_memory_query(result: nil)
- receive(:query)
- .with(/process_.+_memory_bytes/, an_instance_of(Hash))
- .and_return(result || [
- # instance 1: runs Puma + a small Sidekiq
- {
- 'metric' => { 'instance' => 'instance1:8080', 'job' => 'gitlab-rails', '__name__' => 'ruby_process_resident_memory_bytes' },
- 'value' => [1000, '300']
- },
- {
- 'metric' => { 'instance' => 'instance1:8080', 'job' => 'gitlab-rails', '__name__' => 'ruby_process_unique_memory_bytes' },
- 'value' => [1000, '301']
- },
- {
- 'metric' => { 'instance' => 'instance1:8080', 'job' => 'gitlab-rails', '__name__' => 'ruby_process_proportional_memory_bytes' },
- 'value' => [1000, '302']
- },
- {
- 'metric' => { 'instance' => 'instance1:8090', 'job' => 'gitlab-sidekiq', '__name__' => 'ruby_process_resident_memory_bytes' },
- 'value' => [1000, '303']
- },
- # instance 2: runs a dedicated Sidekiq + Redis (which uses a different metric name)
- {
- 'metric' => { 'instance' => 'instance2:8090', 'job' => 'gitlab-sidekiq', '__name__' => 'ruby_process_resident_memory_bytes' },
- 'value' => [1000, '400']
- },
- {
- 'metric' => { 'instance' => 'instance2:8090', 'job' => 'gitlab-sidekiq', '__name__' => 'ruby_process_proportional_memory_bytes' },
- 'value' => [1000, '401']
- },
- {
- 'metric' => { 'instance' => 'instance2:9121', 'job' => 'redis', '__name__' => 'process_resident_memory_bytes' },
- 'value' => [1000, '402']
- }
- ])
- end
-
- def receive_node_service_process_count_query(result: nil)
- receive(:query)
- .with(/process_start_time_seconds/, an_instance_of(Hash))
- .and_return(result || [
- # instance 1
- {
- 'metric' => { 'instance' => 'instance1:8080', 'job' => 'gitlab-rails' },
- 'value' => [1000, '10']
- },
- {
- 'metric' => { 'instance' => 'instance1:8090', 'job' => 'gitlab-sidekiq' },
- 'value' => [1000, '5']
- },
- # instance 2
- {
- 'metric' => { 'instance' => 'instance2:8090', 'job' => 'gitlab-sidekiq' },
- 'value' => [1000, '15']
- },
- {
- 'metric' => { 'instance' => 'instance2:9121', 'job' => 'redis' },
- 'value' => [1000, '1']
- },
- # unknown service => should be stripped out
- {
- 'metric' => { 'instance' => 'instance2:9000', 'job' => 'not-a-gitlab-service' },
- 'value' => [1000, '42']
- }
- ])
- end
-end
diff --git a/spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb
index 71be37692e2..18c21ef7eba 100644
--- a/spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageDataCounters::CycleAnalyticsCounter do
+RSpec.describe Gitlab::UsageDataCounters::CycleAnalyticsCounter do
it_behaves_like 'a redis usage counter', 'CycleAnalytics', :views
it_behaves_like 'a redis usage counter with totals', :cycle_analytics, views: 3
diff --git a/spec/lib/gitlab/usage_data_counters/designs_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/designs_counter_spec.rb
index deaf7ebc7f3..10a09e737f9 100644
--- a/spec/lib/gitlab/usage_data_counters/designs_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/designs_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageDataCounters::DesignsCounter do
+RSpec.describe Gitlab::UsageDataCounters::DesignsCounter do
it_behaves_like 'a redis usage counter', 'Designs', :create
it_behaves_like 'a redis usage counter', 'Designs', :update
it_behaves_like 'a redis usage counter', 'Designs', :delete
diff --git a/spec/lib/gitlab/usage_data_counters/merge_request_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/merge_request_counter_spec.rb
index 4be4a661260..d16c73e9312 100644
--- a/spec/lib/gitlab/usage_data_counters/merge_request_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/merge_request_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageDataCounters::MergeRequestCounter do
+RSpec.describe Gitlab::UsageDataCounters::MergeRequestCounter do
it_behaves_like 'a redis usage counter', 'Merge Request', :create
it_behaves_like 'a redis usage counter with totals', :merge_request, create: 5
diff --git a/spec/lib/gitlab/usage_data_counters/note_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/note_counter_spec.rb
index b385d1b07c7..7e8f0172e06 100644
--- a/spec/lib/gitlab/usage_data_counters/note_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/note_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageDataCounters::NoteCounter, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::UsageDataCounters::NoteCounter, :clean_gitlab_redis_shared_state do
shared_examples 'a note usage counter' do |event, noteable_type|
describe ".count(#{event})" do
it "increments the Note #{event} counter by 1" do
diff --git a/spec/lib/gitlab/usage_data_counters/productivity_analytics_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/productivity_analytics_counter_spec.rb
index f4e92791728..34b2cfcf1de 100644
--- a/spec/lib/gitlab/usage_data_counters/productivity_analytics_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/productivity_analytics_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageDataCounters::ProductivityAnalyticsCounter do
+RSpec.describe Gitlab::UsageDataCounters::ProductivityAnalyticsCounter do
it_behaves_like 'a redis usage counter', 'ProductivityAnalytics', :views
it_behaves_like 'a redis usage counter with totals', :productivity_analytics, views: 3
diff --git a/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb
index c34ac7867ab..be528b081c5 100644
--- a/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageDataCounters::RedisCounter, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::UsageDataCounters::RedisCounter, :clean_gitlab_redis_shared_state do
let(:redis_key) { 'foobar' }
subject { Class.new.extend(described_class) }
diff --git a/spec/lib/gitlab/usage_data_counters/search_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/search_counter_spec.rb
index 35b0f9a67f4..b55e20ba555 100644
--- a/spec/lib/gitlab/usage_data_counters/search_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/search_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageDataCounters::SearchCounter, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::UsageDataCounters::SearchCounter, :clean_gitlab_redis_shared_state do
shared_examples_for 'usage counter with totals' do |counter|
it 'increments counter and returns total count' do
expect(described_class.read(counter)).to eq(0)
diff --git a/spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb
index 65381ed36d1..6109437da77 100644
--- a/spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageDataCounters::SnippetCounter do
+RSpec.describe Gitlab::UsageDataCounters::SnippetCounter do
it_behaves_like 'a redis usage counter', 'Snippet', :create
it_behaves_like 'a redis usage counter', 'Snippet', :update
diff --git a/spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb
index 47077345e0c..73d5b4038f8 100644
--- a/spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageDataCounters::SourceCodeCounter do
+RSpec.describe Gitlab::UsageDataCounters::SourceCodeCounter do
it_behaves_like 'a redis usage counter', 'Source Code', :pushes
it_behaves_like 'a redis usage counter with totals', :source_code, pushes: 5
diff --git a/spec/lib/gitlab/usage_data_counters/track_unique_actions_spec.rb b/spec/lib/gitlab/usage_data_counters/track_unique_actions_spec.rb
new file mode 100644
index 00000000000..584d8407e79
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/track_unique_actions_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::UsageDataCounters::TrackUniqueActions, :clean_gitlab_redis_shared_state do
+ subject(:track_unique_events) { described_class }
+
+ let(:time) { Time.zone.now }
+
+ def track_action(params)
+ track_unique_events.track_action(params)
+ end
+
+ def count_unique_events(params)
+ track_unique_events.count_unique_events(params)
+ end
+
+ context 'tracking an event' do
+ context 'when tracking successfully' do
+ context 'when the feature flag and the application setting is enabled' do
+ context 'when the target and the action is valid' do
+ before do
+ stub_feature_flags(described_class::FEATURE_FLAG => true)
+ stub_application_setting(usage_ping_enabled: true)
+ end
+
+ it 'tracks and counts the events as expected' do
+ project = Event::TARGET_TYPES[:project]
+ design = Event::TARGET_TYPES[:design]
+ wiki = Event::TARGET_TYPES[:wiki]
+
+ expect(track_action(event_action: :pushed, event_target: project, author_id: 1)).to be_truthy
+ expect(track_action(event_action: :pushed, event_target: project, author_id: 1)).to be_truthy
+ expect(track_action(event_action: :pushed, event_target: project, author_id: 2)).to be_truthy
+ expect(track_action(event_action: :pushed, event_target: project, author_id: 3)).to be_truthy
+ expect(track_action(event_action: :pushed, event_target: project, author_id: 4, time: time - 3.days)).to be_truthy
+ expect(track_action(event_action: :created, event_target: project, author_id: 5, time: time - 3.days)).to be_truthy
+
+ expect(track_action(event_action: :destroyed, event_target: design, author_id: 3)).to be_truthy
+ expect(track_action(event_action: :created, event_target: design, author_id: 4)).to be_truthy
+ expect(track_action(event_action: :updated, event_target: design, author_id: 5)).to be_truthy
+ expect(track_action(event_action: :pushed, event_target: design, author_id: 6)).to be_truthy
+
+ expect(track_action(event_action: :destroyed, event_target: wiki, author_id: 5)).to be_truthy
+ expect(track_action(event_action: :created, event_target: wiki, author_id: 3)).to be_truthy
+ expect(track_action(event_action: :updated, event_target: wiki, author_id: 4)).to be_truthy
+ expect(track_action(event_action: :pushed, event_target: wiki, author_id: 6)).to be_truthy
+
+ expect(count_unique_events(event_action: described_class::PUSH_ACTION, date_from: time, date_to: Date.today)).to eq(3)
+ expect(count_unique_events(event_action: described_class::PUSH_ACTION, date_from: time - 5.days, date_to: Date.tomorrow)).to eq(4)
+ expect(count_unique_events(event_action: described_class::DESIGN_ACTION, date_from: time - 5.days, date_to: Date.today)).to eq(3)
+ expect(count_unique_events(event_action: described_class::WIKI_ACTION, date_from: time - 5.days, date_to: Date.today)).to eq(3)
+ expect(count_unique_events(event_action: described_class::PUSH_ACTION, date_from: time - 5.days, date_to: time - 2.days)).to eq(1)
+ end
+ end
+ end
+ end
+
+ context 'when tracking unsuccessfully' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:feature_flag, :application_setting, :target, :action) do
+ true | true | Project | :invalid_action
+ false | true | Project | :pushed
+ true | false | Project | :pushed
+ true | true | :invalid_target | :pushed
+ end
+
+ with_them do
+ before do
+ stub_application_setting(usage_ping_enabled: application_setting)
+ stub_feature_flags(described_class::FEATURE_FLAG => feature_flag)
+ end
+
+ it 'returns the expected values' do
+ expect(track_action(event_action: action, event_target: target, author_id: 2)).to be_nil
+ expect(count_unique_events(event_action: described_class::PUSH_ACTION, date_from: time, date_to: Date.today)).to eq(0)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb
index 42abbecead0..b0e5bd18b66 100644
--- a/spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageDataCounters::WebIdeCounter, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::UsageDataCounters::WebIdeCounter, :clean_gitlab_redis_shared_state do
shared_examples 'counter examples' do |event|
it 'increments counter and return the total count' do
expect(described_class.public_send(:total_count, event)).to eq(0)
diff --git a/spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb
index 4e8ae35187e..685f6ea0a85 100644
--- a/spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageDataCounters::WikiPageCounter do
+RSpec.describe Gitlab::UsageDataCounters::WikiPageCounter do
it_behaves_like 'a redis usage counter', 'Wiki Page', :create
it_behaves_like 'a redis usage counter', 'Wiki Page', :update
it_behaves_like 'a redis usage counter', 'Wiki Page', :delete
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 31176999333..bca2f49eb33 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UsageData, :aggregate_failures do
+RSpec.describe Gitlab::UsageData, :aggregate_failures do
include UsageDataHelpers
before do
@@ -10,7 +10,282 @@ describe Gitlab::UsageData, :aggregate_failures do
stub_object_store_settings
end
- describe '#uncached_data' do
+ describe '.uncached_data' do
+ describe '.usage_activity_by_stage' do
+ it 'includes usage_activity_by_stage data' do
+ expect(described_class.uncached_data).to include(:usage_activity_by_stage)
+ expect(described_class.uncached_data).to include(:usage_activity_by_stage_monthly)
+ end
+
+ it 'clears memoized values' do
+ values = %i(issue_minimum_id issue_maximum_id
+ user_minimum_id user_maximum_id unique_visit_service
+ deployment_minimum_id deployment_maximum_id
+ approval_merge_request_rule_minimum_id
+ approval_merge_request_rule_maximum_id)
+ values.each do |key|
+ expect(described_class).to receive(:clear_memoization).with(key)
+ end
+
+ described_class.uncached_data
+ end
+
+ context 'for configure' do
+ it 'includes accurate usage_activity_by_stage data' do
+ for_defined_days_back do
+ user = create(:user)
+ cluster = create(:cluster, user: user)
+ create(:clusters_applications_cert_manager, :installed, cluster: cluster)
+ create(:clusters_applications_helm, :installed, cluster: cluster)
+ create(:clusters_applications_ingress, :installed, cluster: cluster)
+ create(:clusters_applications_knative, :installed, cluster: cluster)
+ create(:cluster, :disabled, user: user)
+ create(:cluster_provider_gcp, :created)
+ create(:cluster_provider_aws, :created)
+ create(:cluster_platform_kubernetes)
+ create(:cluster, :group, :disabled, user: user)
+ create(:cluster, :group, user: user)
+ create(:cluster, :instance, :disabled, :production_environment)
+ create(:cluster, :instance, :production_environment)
+ create(:cluster, :management_project)
+ end
+
+ expect(described_class.uncached_data[:usage_activity_by_stage][:configure]).to include(
+ clusters_applications_cert_managers: 2,
+ clusters_applications_helm: 2,
+ clusters_applications_ingress: 2,
+ clusters_applications_knative: 2,
+ clusters_management_project: 2,
+ clusters_disabled: 4,
+ clusters_enabled: 12,
+ clusters_platforms_gke: 2,
+ clusters_platforms_eks: 2,
+ clusters_platforms_user: 2,
+ instance_clusters_disabled: 2,
+ instance_clusters_enabled: 2,
+ group_clusters_disabled: 2,
+ group_clusters_enabled: 2,
+ project_clusters_disabled: 2,
+ project_clusters_enabled: 10
+ )
+ expect(described_class.uncached_data[:usage_activity_by_stage_monthly][:configure]).to include(
+ clusters_applications_cert_managers: 1,
+ clusters_applications_helm: 1,
+ clusters_applications_ingress: 1,
+ clusters_applications_knative: 1,
+ clusters_management_project: 1,
+ clusters_disabled: 2,
+ clusters_enabled: 6,
+ clusters_platforms_gke: 1,
+ clusters_platforms_eks: 1,
+ clusters_platforms_user: 1,
+ instance_clusters_disabled: 1,
+ instance_clusters_enabled: 1,
+ group_clusters_disabled: 1,
+ group_clusters_enabled: 1,
+ project_clusters_disabled: 1,
+ project_clusters_enabled: 5
+ )
+ end
+ end
+
+ context 'for create' do
+ it 'include usage_activity_by_stage data' do
+ expect(described_class.uncached_data[:usage_activity_by_stage][:create])
+ .not_to include(
+ :merge_requests_users
+ )
+ end
+
+ it 'includes monthly usage_activity_by_stage data' do
+ expect(described_class.uncached_data[:usage_activity_by_stage_monthly][:create])
+ .to include(
+ :merge_requests_users
+ )
+ end
+
+ it 'includes accurate usage_activity_by_stage data' do
+ for_defined_days_back do
+ user = create(:user)
+ project = create(:project, :repository_private,
+ :test_repo, :remote_mirror, creator: user)
+ create(:merge_request, source_project: project)
+ create(:deploy_key, user: user)
+ create(:key, user: user)
+ create(:project, creator: user, disable_overriding_approvers_per_merge_request: true)
+ create(:project, creator: user, disable_overriding_approvers_per_merge_request: false)
+ create(:remote_mirror, project: project)
+ create(:snippet, author: user)
+ end
+
+ expect(described_class.uncached_data[:usage_activity_by_stage][:create]).to include(
+ deploy_keys: 2,
+ keys: 2,
+ merge_requests: 2,
+ projects_with_disable_overriding_approvers_per_merge_request: 2,
+ projects_without_disable_overriding_approvers_per_merge_request: 4,
+ remote_mirrors: 2,
+ snippets: 2
+ )
+ expect(described_class.uncached_data[:usage_activity_by_stage_monthly][:create]).to include(
+ deploy_keys: 1,
+ keys: 1,
+ merge_requests: 1,
+ projects_with_disable_overriding_approvers_per_merge_request: 1,
+ projects_without_disable_overriding_approvers_per_merge_request: 2,
+ remote_mirrors: 1,
+ snippets: 1
+ )
+ end
+ end
+
+ context 'for manage' do
+ it 'includes accurate usage_activity_by_stage data' do
+ stub_config(
+ omniauth:
+ { providers: omniauth_providers }
+ )
+
+ for_defined_days_back do
+ user = create(:user)
+ create(:event, author: user)
+ create(:group_member, user: user)
+ end
+
+ expect(described_class.uncached_data[:usage_activity_by_stage][:manage]).to include(
+ events: 2,
+ groups: 2,
+ users_created: Gitlab.ee? ? 6 : 5,
+ omniauth_providers: ['google_oauth2']
+ )
+ expect(described_class.uncached_data[:usage_activity_by_stage_monthly][:manage]).to include(
+ events: 1,
+ groups: 1,
+ users_created: Gitlab.ee? ? 4 : 3,
+ omniauth_providers: ['google_oauth2']
+ )
+ end
+
+ def omniauth_providers
+ [
+ OpenStruct.new(name: 'google_oauth2'),
+ OpenStruct.new(name: 'ldapmain'),
+ OpenStruct.new(name: 'group_saml')
+ ]
+ end
+ end
+
+ context 'for monitor' do
+ it 'includes accurate usage_activity_by_stage data' do
+ for_defined_days_back do
+ user = create(:user, dashboard: 'operations')
+ cluster = create(:cluster, user: user)
+ create(:project, creator: user)
+ create(:clusters_applications_prometheus, :installed, cluster: cluster)
+ end
+
+ expect(described_class.uncached_data[:usage_activity_by_stage][:monitor]).to include(
+ clusters: 2,
+ clusters_applications_prometheus: 2,
+ operations_dashboard_default_dashboard: 2
+ )
+ expect(described_class.uncached_data[:usage_activity_by_stage_monthly][:monitor]).to include(
+ clusters: 1,
+ clusters_applications_prometheus: 1,
+ operations_dashboard_default_dashboard: 1
+ )
+ end
+ end
+
+ context 'for plan' do
+ it 'includes accurate usage_activity_by_stage data' do
+ for_defined_days_back do
+ user = create(:user)
+ project = create(:project, creator: user)
+ issue = create(:issue, project: project, author: user)
+ create(:note, project: project, noteable: issue, author: user)
+ create(:todo, project: project, target: issue, author: user)
+ end
+
+ expect(described_class.uncached_data[:usage_activity_by_stage][:plan]).to include(
+ issues: 2,
+ notes: 2,
+ projects: 2,
+ todos: 2
+ )
+ expect(described_class.uncached_data[:usage_activity_by_stage_monthly][:plan]).to include(
+ issues: 1,
+ notes: 1,
+ projects: 1,
+ todos: 1
+ )
+ end
+ end
+
+ context 'for release' do
+ it 'includes accurate usage_activity_by_stage data' do
+ for_defined_days_back do
+ user = create(:user)
+ create(:deployment, :failed, user: user)
+ create(:release, author: user)
+ create(:deployment, :success, user: user)
+ end
+
+ expect(described_class.uncached_data[:usage_activity_by_stage][:release]).to include(
+ deployments: 2,
+ failed_deployments: 2,
+ releases: 2,
+ successful_deployments: 2
+ )
+ expect(described_class.uncached_data[:usage_activity_by_stage_monthly][:release]).to include(
+ deployments: 1,
+ failed_deployments: 1,
+ releases: 1,
+ successful_deployments: 1
+ )
+ end
+ end
+
+ context 'for verify' do
+ it 'includes accurate usage_activity_by_stage data' do
+ for_defined_days_back do
+ user = create(:user)
+ create(:ci_build, user: user)
+ create(:ci_empty_pipeline, source: :external, user: user)
+ create(:ci_empty_pipeline, user: user)
+ create(:ci_pipeline, :auto_devops_source, user: user)
+ create(:ci_pipeline, :repository_source, user: user)
+ create(:ci_pipeline_schedule, owner: user)
+ create(:ci_trigger, owner: user)
+ create(:clusters_applications_runner, :installed)
+ end
+
+ expect(described_class.uncached_data[:usage_activity_by_stage][:verify]).to include(
+ ci_builds: 2,
+ ci_external_pipelines: 2,
+ ci_internal_pipelines: 2,
+ ci_pipeline_config_auto_devops: 2,
+ ci_pipeline_config_repository: 2,
+ ci_pipeline_schedules: 2,
+ ci_pipelines: 2,
+ ci_triggers: 2,
+ clusters_applications_runner: 2
+ )
+ expect(described_class.uncached_data[:usage_activity_by_stage_monthly][:verify]).to include(
+ ci_builds: 1,
+ ci_external_pipelines: 1,
+ ci_internal_pipelines: 1,
+ ci_pipeline_config_auto_devops: 1,
+ ci_pipeline_config_repository: 1,
+ ci_pipeline_schedules: 1,
+ ci_pipelines: 1,
+ ci_triggers: 1,
+ clusters_applications_runner: 1
+ )
+ end
+ end
+ end
+
it 'ensures recorded_at is set before any other usage data calculation' do
%i(alt_usage_data redis_usage_data distinct_count count).each do |method|
expect(described_class).not_to receive(method)
@@ -21,7 +296,7 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '#data' do
+ describe '.data' do
let!(:ud) { build(:usage_data) }
before do
@@ -44,7 +319,11 @@ describe Gitlab::UsageData, :aggregate_failures do
expect(UsageDataHelpers::COUNTS_KEYS - count_data.keys).to be_empty
end
- it 'gathers projects data correctly' do
+ it 'gathers usage counts monthly hash' do
+ expect(subject[:counts_monthly]).to be_an(Hash)
+ end
+
+ it 'gathers usage counts correctly' do
count_data = subject[:counts]
expect(count_data[:projects]).to eq(4)
@@ -56,8 +335,6 @@ describe Gitlab::UsageData, :aggregate_failures do
expect(count_data[:jira_imports_projects_count]).to eq(2)
expect(count_data[:jira_imports_total_imported_count]).to eq(3)
expect(count_data[:jira_imports_total_imported_issues_count]).to eq(13)
- expect(count_data[:projects_slack_notifications_active]).to eq(2)
- expect(count_data[:projects_slack_slash_active]).to eq(1)
expect(count_data[:projects_slack_active]).to eq(2)
expect(count_data[:projects_slack_slash_commands_active]).to eq(1)
expect(count_data[:projects_custom_issue_tracker_active]).to eq(1)
@@ -102,7 +379,15 @@ describe Gitlab::UsageData, :aggregate_failures do
expect(count_data[:clusters_applications_elastic_stack]).to eq(1)
expect(count_data[:grafana_integrated_projects]).to eq(2)
expect(count_data[:clusters_applications_jupyter]).to eq(1)
+ expect(count_data[:clusters_applications_cilium]).to eq(1)
expect(count_data[:clusters_management_project]).to eq(1)
+
+ expect(count_data[:deployments]).to eq(4)
+ expect(count_data[:successful_deployments]).to eq(2)
+ expect(count_data[:failed_deployments]).to eq(2)
+ expect(count_data[:snippets]).to eq(6)
+ expect(count_data[:personal_snippets]).to eq(2)
+ expect(count_data[:project_snippets]).to eq(4)
end
it 'gathers object store usage correctly' do
@@ -169,6 +454,10 @@ describe Gitlab::UsageData, :aggregate_failures do
expect { subject }.not_to raise_error
end
+ it 'includes a recording_ce_finished_at timestamp' do
+ expect(subject[:recording_ce_finished_at]).to be_a(Time)
+ end
+
it 'jira usage works when queries time out' do
allow_any_instance_of(ActiveRecord::Relation)
.to receive(:find_in_batches).and_raise(ActiveRecord::StatementInvalid.new(''))
@@ -177,7 +466,24 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '#usage_data_counters' do
+ describe '.system_usage_data_monthly' do
+ let!(:ud) { build(:usage_data) }
+
+ subject { described_class.system_usage_data_monthly }
+
+ it 'gathers monthly usage counts correctly' do
+ counts_monthly = subject[:counts_monthly]
+
+ expect(counts_monthly[:deployments]).to eq(2)
+ expect(counts_monthly[:successful_deployments]).to eq(1)
+ expect(counts_monthly[:failed_deployments]).to eq(1)
+ expect(counts_monthly[:snippets]).to eq(3)
+ expect(counts_monthly[:personal_snippets]).to eq(1)
+ expect(counts_monthly[:project_snippets]).to eq(2)
+ end
+ end
+
+ describe '.usage_data_counters' do
subject { described_class.usage_data_counters }
it { is_expected.to all(respond_to :totals) }
@@ -204,7 +510,7 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '#license_usage_data' do
+ describe '.license_usage_data' do
subject { described_class.license_usage_data }
it 'gathers license data' do
@@ -216,16 +522,8 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '.recording_ce_finished_at' do
- subject { described_class.recording_ce_finish_data }
-
- it 'gathers time ce recording finishes at' do
- expect(subject[:recording_ce_finished_at]).to be_a(Time)
- end
- end
-
context 'when not relying on database records' do
- describe '#features_usage_data_ce' do
+ describe '.features_usage_data_ce' do
subject { described_class.features_usage_data_ce }
it 'gathers feature usage data', :aggregate_failures do
@@ -243,6 +541,20 @@ describe Gitlab::UsageData, :aggregate_failures do
expect(subject[:grafana_link_enabled]).to eq(Gitlab::CurrentSettings.grafana_enabled?)
end
+ context 'with embedded Prometheus' do
+ it 'returns true when embedded Prometheus is enabled' do
+ allow(Gitlab::Prometheus::Internal).to receive(:prometheus_enabled?).and_return(true)
+
+ expect(subject[:prometheus_enabled]).to eq(true)
+ end
+
+ it 'returns false when embedded Prometheus is disabled' do
+ allow(Gitlab::Prometheus::Internal).to receive(:prometheus_enabled?).and_return(false)
+
+ expect(subject[:prometheus_enabled]).to eq(false)
+ end
+ end
+
context 'with embedded grafana' do
it 'returns true when embedded grafana is enabled' do
stub_application_setting(grafana_enabled: true)
@@ -258,7 +570,7 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '#components_usage_data' do
+ describe '.components_usage_data' do
subject { described_class.components_usage_data }
it 'gathers basic components usage data' do
@@ -282,7 +594,7 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '#app_server_type' do
+ describe '.app_server_type' do
subject { described_class.app_server_type }
it 'successfully identifies runtime and returns the identifier' do
@@ -304,7 +616,7 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '#object_store_config' do
+ describe '.object_store_config' do
let(:component) { 'lfs' }
subject { described_class.object_store_config(component) }
@@ -345,7 +657,7 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '#object_store_usage_data' do
+ describe '.object_store_usage_data' do
subject { described_class.object_store_usage_data }
it 'fetches object store config of five components' do
@@ -364,7 +676,7 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '#cycle_analytics_usage_data' do
+ describe '.cycle_analytics_usage_data' do
subject { described_class.cycle_analytics_usage_data }
it 'works when queries time out in new' do
@@ -382,7 +694,7 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '#ingress_modsecurity_usage' do
+ describe '.ingress_modsecurity_usage' do
subject { described_class.ingress_modsecurity_usage }
let(:environment) { create(:environment) }
@@ -514,7 +826,7 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '#grafana_embed_usage_data' do
+ describe '.grafana_embed_usage_data' do
subject { described_class.grafana_embed_usage_data }
let(:project) { create(:project) }
@@ -580,7 +892,7 @@ describe Gitlab::UsageData, :aggregate_failures do
end
end
- describe '#merge_requests_usage' do
+ describe '.merge_requests_users' do
let(:time_period) { { created_at: 2.days.ago..Time.current } }
let(:merge_request) { create(:merge_request) }
let(:other_user) { create(:user) }
@@ -597,9 +909,94 @@ describe Gitlab::UsageData, :aggregate_failures do
end
it 'returns the distinct count of users using merge requests (via events table) within the specified time period' do
- expect(described_class.merge_requests_usage(time_period)).to eq(
- merge_requests_users: 2
- )
+ expect(described_class.merge_requests_users(time_period)).to eq(2)
+ end
+ end
+
+ def for_defined_days_back(days: [29, 2])
+ days.each do |n|
+ Timecop.travel(n.days.ago) do
+ yield
+ end
+ end
+ end
+
+ describe '#action_monthly_active_users', :clean_gitlab_redis_shared_state do
+ let(:time_period) { { created_at: 2.days.ago..time } }
+ let(:time) { Time.zone.now }
+
+ before do
+ stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => feature_flag)
+ end
+
+ context 'when the feature flag is enabled' do
+ let(:feature_flag) { true }
+
+ before do
+ counter = Gitlab::UsageDataCounters::TrackUniqueActions
+ project = Event::TARGET_TYPES[:project]
+ wiki = Event::TARGET_TYPES[:wiki]
+ design = Event::TARGET_TYPES[:design]
+
+ counter.track_action(event_action: :pushed, event_target: project, author_id: 1)
+ counter.track_action(event_action: :pushed, event_target: project, author_id: 1)
+ counter.track_action(event_action: :pushed, event_target: project, author_id: 2)
+ counter.track_action(event_action: :pushed, event_target: project, author_id: 3)
+ counter.track_action(event_action: :pushed, event_target: project, author_id: 4, time: time - 3.days)
+ counter.track_action(event_action: :created, event_target: project, author_id: 5, time: time - 3.days)
+ counter.track_action(event_action: :created, event_target: wiki, author_id: 3)
+ counter.track_action(event_action: :created, event_target: design, author_id: 3)
+ end
+
+ it 'returns the distinct count of user actions within the specified time period' do
+ expect(described_class.action_monthly_active_users(time_period)).to eq(
+ {
+ action_monthly_active_users_design_management: 1,
+ action_monthly_active_users_project_repo: 3,
+ action_monthly_active_users_wiki_repo: 1
+ }
+ )
+ end
+ end
+
+ context 'when the feature flag is disabled' do
+ let(:feature_flag) { false }
+
+ it 'returns an empty hash' do
+ expect(described_class.action_monthly_active_users(time_period)).to eq({})
+ end
+ end
+ end
+
+ describe '.analytics_unique_visits_data' do
+ subject { described_class.analytics_unique_visits_data }
+
+ it 'returns the number of unique visits to pages with analytics features' do
+ ::Gitlab::Analytics::UniqueVisits::TARGET_IDS.each do |target_id|
+ expect_any_instance_of(::Gitlab::Analytics::UniqueVisits).to receive(:weekly_unique_visits_for_target).with(target_id).and_return(123)
+ end
+
+ expect_any_instance_of(::Gitlab::Analytics::UniqueVisits).to receive(:weekly_unique_visits_for_any_target).and_return(543)
+
+ expect(subject).to eq({
+ analytics_unique_visits: {
+ 'g_analytics_contribution' => 123,
+ 'g_analytics_insights' => 123,
+ 'g_analytics_issues' => 123,
+ 'g_analytics_productivity' => 123,
+ 'g_analytics_valuestream' => 123,
+ 'p_analytics_pipelines' => 123,
+ 'p_analytics_code_reviews' => 123,
+ 'p_analytics_valuestream' => 123,
+ 'p_analytics_insights' => 123,
+ 'p_analytics_issues' => 123,
+ 'p_analytics_repo' => 123,
+ 'u_analytics_todos' => 123,
+ 'i_analytics_cohorts' => 123,
+ 'i_analytics_dev_ops_score' => 123,
+ 'analytics_unique_visits_for_any_target' => 543
+ }
+ })
end
end
end
diff --git a/spec/lib/gitlab/user_access_snippet_spec.rb b/spec/lib/gitlab/user_access_snippet_spec.rb
index 2e8a0a49a76..4143a3017e8 100644
--- a/spec/lib/gitlab/user_access_snippet_spec.rb
+++ b/spec/lib/gitlab/user_access_snippet_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UserAccessSnippet do
+RSpec.describe Gitlab::UserAccessSnippet do
subject(:access) { described_class.new(user, snippet: snippet) }
let_it_be(:project) { create(:project, :private) }
diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb
index 78370f0136c..1a81d0127dc 100644
--- a/spec/lib/gitlab/user_access_spec.rb
+++ b/spec/lib/gitlab/user_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::UserAccess do
+RSpec.describe Gitlab::UserAccess do
include ProjectForksHelper
let(:access) { described_class.new(user, project: project) }
diff --git a/spec/lib/gitlab/utils/deep_size_spec.rb b/spec/lib/gitlab/utils/deep_size_spec.rb
index 5a155fb6c80..7595fb2c1b0 100644
--- a/spec/lib/gitlab/utils/deep_size_spec.rb
+++ b/spec/lib/gitlab/utils/deep_size_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Utils::DeepSize do
+RSpec.describe Gitlab::Utils::DeepSize do
let(:data) do
{
a: [1, 2, 3],
diff --git a/spec/lib/gitlab/utils/inline_hash_spec.rb b/spec/lib/gitlab/utils/inline_hash_spec.rb
index 867db0b92a5..d1354a7ae0a 100644
--- a/spec/lib/gitlab/utils/inline_hash_spec.rb
+++ b/spec/lib/gitlab/utils/inline_hash_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Utils::InlineHash do
+RSpec.describe Gitlab::Utils::InlineHash do
describe '.merge_keys' do
subject { described_class.merge_keys(source) }
diff --git a/spec/lib/gitlab/utils/json_size_estimator_spec.rb b/spec/lib/gitlab/utils/json_size_estimator_spec.rb
index ae24e25558a..5fd66caa5e9 100644
--- a/spec/lib/gitlab/utils/json_size_estimator_spec.rb
+++ b/spec/lib/gitlab/utils/json_size_estimator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Utils::JsonSizeEstimator do
+RSpec.describe Gitlab::Utils::JsonSizeEstimator do
RSpec::Matchers.define :match_json_bytesize_of do |expected|
match do |actual|
actual == expected.to_json.bytesize
diff --git a/spec/lib/gitlab/utils/lazy_attributes_spec.rb b/spec/lib/gitlab/utils/lazy_attributes_spec.rb
index c0005c194c4..dfffe70defb 100644
--- a/spec/lib/gitlab/utils/lazy_attributes_spec.rb
+++ b/spec/lib/gitlab/utils/lazy_attributes_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
require 'active_support/concern'
-describe Gitlab::Utils::LazyAttributes do
+RSpec.describe Gitlab::Utils::LazyAttributes do
subject(:klass) do
Class.new do
include Gitlab::Utils::LazyAttributes
diff --git a/spec/lib/gitlab/utils/log_limited_array_spec.rb b/spec/lib/gitlab/utils/log_limited_array_spec.rb
index a236ab37614..a55a176be48 100644
--- a/spec/lib/gitlab/utils/log_limited_array_spec.rb
+++ b/spec/lib/gitlab/utils/log_limited_array_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Utils::LogLimitedArray do
+RSpec.describe Gitlab::Utils::LogLimitedArray do
describe '.log_limited_array' do
context 'when the argument is not an array' do
it 'returns an empty array' do
diff --git a/spec/lib/gitlab/utils/markdown_spec.rb b/spec/lib/gitlab/utils/markdown_spec.rb
new file mode 100644
index 00000000000..001ff5bc487
--- /dev/null
+++ b/spec/lib/gitlab/utils/markdown_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Utils::Markdown do
+ let(:klass) do
+ Class.new do
+ include Gitlab::Utils::Markdown
+ end
+ end
+
+ subject(:object) { klass.new }
+
+ describe '#string_to_anchor' do
+ subject { object.string_to_anchor(string) }
+
+ let(:string) { 'My Header' }
+
+ it 'converts string to anchor' do
+ is_expected.to eq 'my-header'
+ end
+
+ context 'when string has punctuation' do
+ let(:string) { 'My, Header!' }
+
+ it 'removes punctuation' do
+ is_expected.to eq 'my-header'
+ end
+ end
+
+ context 'when string starts and ends with spaces' do
+ let(:string) { ' My Header ' }
+
+ it 'removes extra spaces' do
+ is_expected.to eq 'my-header'
+ end
+ end
+
+ context 'when string has multiple spaces and dashes in the middle' do
+ let(:string) { 'My - - - Header' }
+
+ it 'removes consecutive dashes' do
+ is_expected.to eq 'my-header'
+ end
+ end
+
+ context 'when string contains only digits' do
+ let(:string) { '123' }
+
+ it 'adds anchor prefix' do
+ is_expected.to eq 'anchor-123'
+ end
+ end
+
+ context 'when string is empty' do
+ let(:string) { '' }
+
+ it 'returns an empty string' do
+ is_expected.to eq ''
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/utils/measuring_spec.rb b/spec/lib/gitlab/utils/measuring_spec.rb
index 254f53f7da3..4931ebf26f0 100644
--- a/spec/lib/gitlab/utils/measuring_spec.rb
+++ b/spec/lib/gitlab/utils/measuring_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Utils::Measuring do
+RSpec.describe Gitlab::Utils::Measuring do
describe '#with_measuring' do
let(:base_log_data) { {} }
let(:result) { "result" }
diff --git a/spec/lib/gitlab/utils/merge_hash_spec.rb b/spec/lib/gitlab/utils/merge_hash_spec.rb
index 72620e549a9..11daa05c9ee 100644
--- a/spec/lib/gitlab/utils/merge_hash_spec.rb
+++ b/spec/lib/gitlab/utils/merge_hash_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::Utils::MergeHash do
+RSpec.describe Gitlab::Utils::MergeHash do
describe '.crush' do
it 'can flatten a hash to each element' do
input = { hello: "world", this: { crushes: ["an entire", "hash"] } }
diff --git a/spec/lib/gitlab/utils/override_spec.rb b/spec/lib/gitlab/utils/override_spec.rb
index e2776efac85..7ba7392df0f 100644
--- a/spec/lib/gitlab/utils/override_spec.rb
+++ b/spec/lib/gitlab/utils/override_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Utils::Override do
+RSpec.describe Gitlab::Utils::Override do
let(:base) do
Struct.new(:good) do
def self.good
diff --git a/spec/lib/gitlab/utils/safe_inline_hash_spec.rb b/spec/lib/gitlab/utils/safe_inline_hash_spec.rb
index 617845332bc..f7c50140cf7 100644
--- a/spec/lib/gitlab/utils/safe_inline_hash_spec.rb
+++ b/spec/lib/gitlab/utils/safe_inline_hash_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Gitlab::Utils::SafeInlineHash do
+RSpec.describe Gitlab::Utils::SafeInlineHash do
describe '.merge_keys!' do
let(:source) { { 'foo' => { 'bar' => 'baz' } } }
let(:validator) { instance_double(Gitlab::Utils::DeepSize, valid?: valid) }
diff --git a/spec/lib/gitlab/utils/sanitize_node_link_spec.rb b/spec/lib/gitlab/utils/sanitize_node_link_spec.rb
index dd379f2fe1f..514051b1cc0 100644
--- a/spec/lib/gitlab/utils/sanitize_node_link_spec.rb
+++ b/spec/lib/gitlab/utils/sanitize_node_link_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Utils::SanitizeNodeLink do
+RSpec.describe Gitlab::Utils::SanitizeNodeLink do
let(:klass) do
struct = Struct.new(:value)
struct.include(described_class)
diff --git a/spec/lib/gitlab/utils/strong_memoize_spec.rb b/spec/lib/gitlab/utils/strong_memoize_spec.rb
index 624e799c5e9..d9fa2e516e1 100644
--- a/spec/lib/gitlab/utils/strong_memoize_spec.rb
+++ b/spec/lib/gitlab/utils/strong_memoize_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Utils::StrongMemoize do
+RSpec.describe Gitlab::Utils::StrongMemoize do
let(:klass) do
struct = Struct.new(:value) do
def method_name
diff --git a/spec/lib/gitlab/utils/usage_data_spec.rb b/spec/lib/gitlab/utils/usage_data_spec.rb
index 7de615384c5..7940c9af6ff 100644
--- a/spec/lib/gitlab/utils/usage_data_spec.rb
+++ b/spec/lib/gitlab/utils/usage_data_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Utils::UsageData do
+RSpec.describe Gitlab::Utils::UsageData do
describe '#count' do
let(:relation) { double(:relation) }
@@ -88,13 +88,21 @@ describe Gitlab::Utils::UsageData do
end
context 'when Prometheus is disabled' do
- it 'returns nil' do
+ before do
expect(Gitlab::Prometheus::Internal).to receive(:prometheus_enabled?).and_return(false)
+ end
+ it 'returns nil by default' do
result = described_class.with_prometheus_client { |client| client }
expect(result).to be nil
end
+
+ it 'returns fallback if provided' do
+ result = described_class.with_prometheus_client(fallback: []) { |client| client }
+
+ expect(result).to eq([])
+ end
end
end
@@ -108,4 +116,14 @@ describe Gitlab::Utils::UsageData do
expect(duration).to eq(2)
end
end
+
+ describe '#with_finished_at' do
+ it 'adds a timestamp to the hash yielded by the block' do
+ freeze_time do
+ result = described_class.with_finished_at(:current_time) { { a: 1 } }
+
+ expect(result).to eq(a: 1, current_time: Time.now)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index 3a2430d1f2d..7a0d40ff0d2 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Utils do
+RSpec.describe Gitlab::Utils do
delegate :to_boolean, :boolean_to_yes_no, :slugify, :random_string, :which,
:ensure_array_from_string, :to_exclusive_sentence, :bytes_to_megabytes,
:append_path, :check_path_traversal!, :ms_to_round_sec, to: :described_class
@@ -358,4 +358,40 @@ describe Gitlab::Utils do
})
end
end
+
+ describe '.stable_sort_by' do
+ subject(:sorted_list) { described_class.stable_sort_by(list) { |obj| obj[:priority] } }
+
+ context 'when items have the same priority' do
+ let(:list) do
+ [
+ { name: 'obj 1', priority: 1 },
+ { name: 'obj 2', priority: 1 },
+ { name: 'obj 3', priority: 1 }
+ ]
+ end
+
+ it 'does not change order in cases of ties' do
+ expect(sorted_list).to eq(list)
+ end
+ end
+
+ context 'when items have different priorities' do
+ let(:list) do
+ [
+ { name: 'obj 1', priority: 2 },
+ { name: 'obj 2', priority: 1 },
+ { name: 'obj 3', priority: 3 }
+ ]
+ end
+
+ it 'sorts items like the regular sort_by' do
+ expect(sorted_list).to eq([
+ { name: 'obj 2', priority: 1 },
+ { name: 'obj 1', priority: 2 },
+ { name: 'obj 3', priority: 3 }
+ ])
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/verify/job_artifacts_spec.rb b/spec/lib/gitlab/verify/job_artifacts_spec.rb
index b50ec1528d4..fde7ebd442e 100644
--- a/spec/lib/gitlab/verify/job_artifacts_spec.rb
+++ b/spec/lib/gitlab/verify/job_artifacts_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Verify::JobArtifacts do
+RSpec.describe Gitlab::Verify::JobArtifacts do
include GitlabVerifyHelpers
it_behaves_like 'Gitlab::Verify::BatchVerifier subclass' do
diff --git a/spec/lib/gitlab/verify/lfs_objects_spec.rb b/spec/lib/gitlab/verify/lfs_objects_spec.rb
index c27c9b6efa1..760162dd627 100644
--- a/spec/lib/gitlab/verify/lfs_objects_spec.rb
+++ b/spec/lib/gitlab/verify/lfs_objects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Verify::LfsObjects do
+RSpec.describe Gitlab::Verify::LfsObjects do
include GitlabVerifyHelpers
it_behaves_like 'Gitlab::Verify::BatchVerifier subclass' do
diff --git a/spec/lib/gitlab/verify/uploads_spec.rb b/spec/lib/gitlab/verify/uploads_spec.rb
index a3d3f5d46f3..3e5154d5029 100644
--- a/spec/lib/gitlab/verify/uploads_spec.rb
+++ b/spec/lib/gitlab/verify/uploads_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Verify::Uploads do
+RSpec.describe Gitlab::Verify::Uploads do
include GitlabVerifyHelpers
it_behaves_like 'Gitlab::Verify::BatchVerifier subclass' do
diff --git a/spec/lib/gitlab/version_info_spec.rb b/spec/lib/gitlab/version_info_spec.rb
index 8c14b187410..f81e3aa070a 100644
--- a/spec/lib/gitlab/version_info_spec.rb
+++ b/spec/lib/gitlab/version_info_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Gitlab::VersionInfo' do
+RSpec.describe 'Gitlab::VersionInfo' do
before do
@unknown = Gitlab::VersionInfo.new
@v0_0_1 = Gitlab::VersionInfo.new(0, 0, 1)
diff --git a/spec/lib/gitlab/view/presenter/base_spec.rb b/spec/lib/gitlab/view/presenter/base_spec.rb
index e196ab23482..1ab6973e279 100644
--- a/spec/lib/gitlab/view/presenter/base_spec.rb
+++ b/spec/lib/gitlab/view/presenter/base_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::View::Presenter::Base do
+RSpec.describe Gitlab::View::Presenter::Base do
let(:project) { double(:project) }
let(:presenter_class) do
Struct.new(:subject).include(described_class)
diff --git a/spec/lib/gitlab/view/presenter/delegated_spec.rb b/spec/lib/gitlab/view/presenter/delegated_spec.rb
index 0a21cd1358e..27cff970f06 100644
--- a/spec/lib/gitlab/view/presenter/delegated_spec.rb
+++ b/spec/lib/gitlab/view/presenter/delegated_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::View::Presenter::Delegated do
+RSpec.describe Gitlab::View::Presenter::Delegated do
let(:project) { double(:project, user: 'John Doe') }
let(:presenter_class) do
Class.new(described_class)
diff --git a/spec/lib/gitlab/view/presenter/factory_spec.rb b/spec/lib/gitlab/view/presenter/factory_spec.rb
index 7bf3c325019..92986e89b73 100644
--- a/spec/lib/gitlab/view/presenter/factory_spec.rb
+++ b/spec/lib/gitlab/view/presenter/factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::View::Presenter::Factory do
+RSpec.describe Gitlab::View::Presenter::Factory do
let(:build) { Ci::Build.new }
describe '#initialize' do
diff --git a/spec/lib/gitlab/view/presenter/simple_spec.rb b/spec/lib/gitlab/view/presenter/simple_spec.rb
index 70e2b170a36..2b764fcccef 100644
--- a/spec/lib/gitlab/view/presenter/simple_spec.rb
+++ b/spec/lib/gitlab/view/presenter/simple_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::View::Presenter::Simple do
+RSpec.describe Gitlab::View::Presenter::Simple do
let(:project) { double(:project, user: 'John Doe') }
let(:presenter_class) do
Class.new(described_class)
diff --git a/spec/lib/gitlab/visibility_level_checker_spec.rb b/spec/lib/gitlab/visibility_level_checker_spec.rb
index fc929d5cbbf..833021a22ca 100644
--- a/spec/lib/gitlab/visibility_level_checker_spec.rb
+++ b/spec/lib/gitlab/visibility_level_checker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::VisibilityLevelChecker do
+RSpec.describe Gitlab::VisibilityLevelChecker do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:visibility_level_checker) { }
diff --git a/spec/lib/gitlab/visibility_level_spec.rb b/spec/lib/gitlab/visibility_level_spec.rb
index a249b3a235e..2ac343cd1e7 100644
--- a/spec/lib/gitlab/visibility_level_spec.rb
+++ b/spec/lib/gitlab/visibility_level_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::VisibilityLevel do
+RSpec.describe Gitlab::VisibilityLevel do
describe '.level_value' do
it 'converts "public" to integer value' do
expect(described_class.level_value('public')).to eq(Gitlab::VisibilityLevel::PUBLIC)
diff --git a/spec/lib/gitlab/web_ide/config/entry/global_spec.rb b/spec/lib/gitlab/web_ide/config/entry/global_spec.rb
index 04b0752c6fe..3a50667163b 100644
--- a/spec/lib/gitlab/web_ide/config/entry/global_spec.rb
+++ b/spec/lib/gitlab/web_ide/config/entry/global_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::WebIde::Config::Entry::Global do
+RSpec.describe Gitlab::WebIde::Config::Entry::Global do
let(:global) { described_class.new(hash) }
describe '.nodes' do
diff --git a/spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb b/spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb
index 882e389e040..0df0f56f440 100644
--- a/spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb
+++ b/spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::WebIde::Config::Entry::Terminal do
+RSpec.describe Gitlab::WebIde::Config::Entry::Terminal do
let(:entry) { described_class.new(config, with_image_ports: true) }
describe '.nodes' do
diff --git a/spec/lib/gitlab/web_ide/config_spec.rb b/spec/lib/gitlab/web_ide/config_spec.rb
index c1dafd01197..7a9011d03c0 100644
--- a/spec/lib/gitlab/web_ide/config_spec.rb
+++ b/spec/lib/gitlab/web_ide/config_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::WebIde::Config do
+RSpec.describe Gitlab::WebIde::Config do
let(:config) do
described_class.new(yml)
end
diff --git a/spec/lib/gitlab/wiki_file_finder_spec.rb b/spec/lib/gitlab/wiki_file_finder_spec.rb
index aeba081f3d3..7abe92a5a2b 100644
--- a/spec/lib/gitlab/wiki_file_finder_spec.rb
+++ b/spec/lib/gitlab/wiki_file_finder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::WikiFileFinder do
+RSpec.describe Gitlab::WikiFileFinder do
describe '#find' do
let(:project) { create(:project, :public, :wiki_repo) }
let(:wiki) { build(:project_wiki, project: project) }
diff --git a/spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb b/spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb
index 01701589e63..c78103f33f4 100644
--- a/spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb
+++ b/spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::WikiPages::FrontMatterParser do
+RSpec.describe Gitlab::WikiPages::FrontMatterParser do
subject(:parser) { described_class.new(raw_content, gate) }
let(:content) { 'This is the content' }
diff --git a/spec/lib/gitlab/with_request_store_spec.rb b/spec/lib/gitlab/with_request_store_spec.rb
index 1ef8d986f96..353ad02fbd8 100644
--- a/spec/lib/gitlab/with_request_store_spec.rb
+++ b/spec/lib/gitlab/with_request_store_spec.rb
@@ -3,7 +3,7 @@
require 'fast_spec_helper'
require 'request_store'
-describe Gitlab::WithRequestStore do
+RSpec.describe Gitlab::WithRequestStore do
let(:fake_class) { Class.new { include Gitlab::WithRequestStore } }
subject(:object) { fake_class.new }
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 53b6f461a48..5cb08ac1e76 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Workhorse do
+RSpec.describe Gitlab::Workhorse do
let_it_be(:project) { create(:project, :repository) }
let(:repository) { project.repository }
diff --git a/spec/lib/gitlab/x509/commit_spec.rb b/spec/lib/gitlab/x509/commit_spec.rb
index ac93609b467..a81955b995e 100644
--- a/spec/lib/gitlab/x509/commit_spec.rb
+++ b/spec/lib/gitlab/x509/commit_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::X509::Commit do
+RSpec.describe Gitlab::X509::Commit do
describe '#signature' do
let(:signature) { described_class.new(commit).signature }
diff --git a/spec/lib/gitlab/x509/signature_spec.rb b/spec/lib/gitlab/x509/signature_spec.rb
index cff2fd7748b..ac6f7e49fe0 100644
--- a/spec/lib/gitlab/x509/signature_spec.rb
+++ b/spec/lib/gitlab/x509/signature_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::X509::Signature do
+RSpec.describe Gitlab::X509::Signature do
let(:issuer_attributes) do
{
subject_key_identifier: X509Helpers::User1.issuer_subject_key_identifier,
diff --git a/spec/lib/gitlab/x509/tag_spec.rb b/spec/lib/gitlab/x509/tag_spec.rb
index 4bc9723bd0d..b011ea515de 100644
--- a/spec/lib/gitlab/x509/tag_spec.rb
+++ b/spec/lib/gitlab/x509/tag_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::X509::Tag do
+RSpec.describe Gitlab::X509::Tag do
subject(:signature) { described_class.new(tag).signature }
describe '#signature' do
diff --git a/spec/lib/gitlab/zoom_link_extractor_spec.rb b/spec/lib/gitlab/zoom_link_extractor_spec.rb
index c3d1679d031..363668fb2b5 100644
--- a/spec/lib/gitlab/zoom_link_extractor_spec.rb
+++ b/spec/lib/gitlab/zoom_link_extractor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ZoomLinkExtractor do
+RSpec.describe Gitlab::ZoomLinkExtractor do
describe "#links" do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/lib/gitlab_danger_spec.rb b/spec/lib/gitlab_danger_spec.rb
index 8115fbca5e0..49c7a46f321 100644
--- a/spec/lib/gitlab_danger_spec.rb
+++ b/spec/lib/gitlab_danger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabDanger do
+RSpec.describe GitlabDanger do
let(:gitlab_danger_helper) { nil }
subject { described_class.new(gitlab_danger_helper) }
diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb
index 84d072a50ec..7c2758bf27e 100644
--- a/spec/lib/gitlab_spec.rb
+++ b/spec/lib/gitlab_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab do
+RSpec.describe Gitlab do
describe '.root' do
it 'returns the root path of the app' do
expect(described_class.root).to eq(Pathname.new(File.expand_path('../..', __dir__)))
diff --git a/spec/lib/google_api/auth_spec.rb b/spec/lib/google_api/auth_spec.rb
index fa4e6288681..eeb99bfbb6c 100644
--- a/spec/lib/google_api/auth_spec.rb
+++ b/spec/lib/google_api/auth_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GoogleApi::Auth do
+RSpec.describe GoogleApi::Auth do
let(:redirect_uri) { 'http://localhost:3000/google_api/authorizations/callback' }
let(:redirect_to) { 'http://localhost:3000/namaspace/project/clusters' }
diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb
index bd063648ca1..d217699f79d 100644
--- a/spec/lib/google_api/cloud_platform/client_spec.rb
+++ b/spec/lib/google_api/cloud_platform/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GoogleApi::CloudPlatform::Client do
+RSpec.describe GoogleApi::CloudPlatform::Client do
let(:token) { 'token' }
let(:client) { described_class.new(token, nil) }
let(:user_agent_options) { client.instance_eval { user_agent_header } }
diff --git a/spec/lib/grafana/client_spec.rb b/spec/lib/grafana/client_spec.rb
index 699344e940e..c233d0b8445 100644
--- a/spec/lib/grafana/client_spec.rb
+++ b/spec/lib/grafana/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Grafana::Client do
+RSpec.describe Grafana::Client do
let(:grafana_url) { 'https://grafanatest.com/-/grafana-project' }
let(:token) { 'test-token' }
diff --git a/spec/lib/grafana/time_window_spec.rb b/spec/lib/grafana/time_window_spec.rb
index e70861658ca..9ee65c6cf20 100644
--- a/spec/lib/grafana/time_window_spec.rb
+++ b/spec/lib/grafana/time_window_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Grafana::TimeWindow do
+RSpec.describe Grafana::TimeWindow do
let(:from) { '1552799400000' }
let(:to) { '1552828200000' }
@@ -32,7 +32,7 @@ describe Grafana::TimeWindow do
end
end
-describe Grafana::RangeWithDefaults do
+RSpec.describe Grafana::RangeWithDefaults do
let(:from) { Grafana::Timestamp.from_ms_since_epoch('1552799400000') }
let(:to) { Grafana::Timestamp.from_ms_since_epoch('1552828200000') }
@@ -78,7 +78,7 @@ describe Grafana::RangeWithDefaults do
end
end
-describe Grafana::Timestamp do
+RSpec.describe Grafana::Timestamp do
let(:timestamp) { Time.at(1552799400) }
around do |example|
diff --git a/spec/lib/grafana/validator_spec.rb b/spec/lib/grafana/validator_spec.rb
index a048a1f3470..b45749ffc95 100644
--- a/spec/lib/grafana/validator_spec.rb
+++ b/spec/lib/grafana/validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Grafana::Validator do
+RSpec.describe Grafana::Validator do
let(:grafana_dashboard) { Gitlab::Json.parse(fixture_file('grafana/simplified_dashboard_response.json'), symbolize_names: true) }
let(:datasource) { Gitlab::Json.parse(fixture_file('grafana/datasource_response.json'), symbolize_names: true) }
let(:panel) { grafana_dashboard[:dashboard][:panels].first }
diff --git a/spec/lib/json_web_token/hmac_token_spec.rb b/spec/lib/json_web_token/hmac_token_spec.rb
index f2cbc381967..cf7e5c54f45 100644
--- a/spec/lib/json_web_token/hmac_token_spec.rb
+++ b/spec/lib/json_web_token/hmac_token_spec.rb
@@ -3,7 +3,7 @@
require 'json'
require 'timecop'
-describe JSONWebToken::HMACToken do
+RSpec.describe JSONWebToken::HMACToken do
let(:secret) { 'shh secret squirrel' }
shared_examples 'a valid, non-expired token' do
diff --git a/spec/lib/json_web_token/rsa_token_spec.rb b/spec/lib/json_web_token/rsa_token_spec.rb
index 62fddbb97c7..f19471917c2 100644
--- a/spec/lib/json_web_token/rsa_token_spec.rb
+++ b/spec/lib/json_web_token/rsa_token_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe JSONWebToken::RSAToken do
+RSpec.describe JSONWebToken::RSAToken do
let(:rsa_key) do
OpenSSL::PKey::RSA.new <<-eos.strip_heredoc
-----BEGIN RSA PRIVATE KEY-----
diff --git a/spec/lib/json_web_token/token_spec.rb b/spec/lib/json_web_token/token_spec.rb
index ca587a6ebcd..6fb15ae0a9c 100644
--- a/spec/lib/json_web_token/token_spec.rb
+++ b/spec/lib/json_web_token/token_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe JSONWebToken::Token do
+RSpec.describe JSONWebToken::Token do
let(:token) { described_class.new }
context 'custom parameters' do
diff --git a/spec/lib/kramdown/parser/atlassian_document_format_spec.rb b/spec/lib/kramdown/parser/atlassian_document_format_spec.rb
new file mode 100644
index 00000000000..f3559503388
--- /dev/null
+++ b/spec/lib/kramdown/parser/atlassian_document_format_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.context Kramdown::Parser::AtlassianDocumentFormat do
+ let_it_be(:options) { { input: 'AtlassianDocumentFormat', html_tables: true } }
+ let_it_be(:fixtures_path) { 'lib/kramdown/atlassian_document_format' }
+
+ context 'markdown render' do
+ shared_examples 'render elements to markdown' do |base_name|
+ let(:json_file) { "#{base_name}.json" }
+ let(:markdown_file) { "#{base_name}.md" }
+
+ it "renders #{base_name}" do
+ source = fixture_file(File.join(fixtures_path, json_file))
+ target = fixture_file(File.join(fixtures_path, markdown_file))
+ parser = Kramdown::Document.new(source, options)
+
+ expect(parser.to_commonmark).to eq target
+ end
+ end
+
+ it_behaves_like 'render elements to markdown', 'blockquote'
+ it_behaves_like 'render elements to markdown', 'bullet_list'
+ it_behaves_like 'render elements to markdown', 'code_block'
+ it_behaves_like 'render elements to markdown', 'emoji'
+ it_behaves_like 'render elements to markdown', 'hard_break'
+ it_behaves_like 'render elements to markdown', 'heading'
+ it_behaves_like 'render elements to markdown', 'inline_card'
+ it_behaves_like 'render elements to markdown', 'media_group'
+ it_behaves_like 'render elements to markdown', 'media_single'
+ it_behaves_like 'render elements to markdown', 'mention'
+ it_behaves_like 'render elements to markdown', 'ordered_list'
+ it_behaves_like 'render elements to markdown', 'panel'
+ it_behaves_like 'render elements to markdown', 'paragraph'
+ it_behaves_like 'render elements to markdown', 'rule'
+ it_behaves_like 'render elements to markdown', 'table'
+
+ it_behaves_like 'render elements to markdown', 'strong_em_mark'
+ it_behaves_like 'render elements to markdown', 'code_mark'
+ it_behaves_like 'render elements to markdown', 'link_mark'
+ it_behaves_like 'render elements to markdown', 'strike_sup_sub_mark'
+ it_behaves_like 'render elements to markdown', 'underline_text_color_mark'
+
+ it_behaves_like 'render elements to markdown', 'complex_document'
+
+ it 'renders header id to html' do
+ source = fixture_file(File.join(fixtures_path, 'heading.json'))
+ parser = Kramdown::Document.new(source, options)
+
+ expect(parser.to_html).to include('id="header-2"')
+ end
+
+ it 'logs an error with invalid json' do
+ source = fixture_file(File.join(fixtures_path, 'invalid_json.json'))
+
+ expect(Gitlab::AppLogger).to receive(:error).with(/Invalid Atlassian Document Format JSON/)
+ expect(Gitlab::AppLogger).to receive(:error).with(any_args)
+ expect { Kramdown::Document.new(source, options) }.to raise_error(::Kramdown::Error, /Invalid Atlassian Document Format JSON/)
+ end
+
+ it 'logs an error if no valid document node' do
+ source = fixture_file(File.join(fixtures_path, 'invalid_no_doc.json'))
+
+ expect(Gitlab::AppLogger).to receive(:error).with(/Invalid Atlassian Document Format JSON/)
+ expect(Gitlab::AppLogger).to receive(:error).with(any_args)
+ expect { Kramdown::Document.new(source, options) }.to raise_error(::Kramdown::Error, /Invalid Atlassian Document Format JSON/)
+ end
+
+ it 'invalid node gets ignored' do
+ source = fixture_file(File.join(fixtures_path, 'invalid_node_type.json'))
+ parser = Kramdown::Document.new(source, options)
+
+ expect(parser.to_commonmark).to eq "This is a second paragraph\n\n"
+ end
+ end
+end
diff --git a/spec/lib/learn_gitlab_spec.rb b/spec/lib/learn_gitlab_spec.rb
new file mode 100644
index 00000000000..abfd82999c3
--- /dev/null
+++ b/spec/lib/learn_gitlab_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe LearnGitlab do
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:learn_gitlab_project) { create(:project, name: LearnGitlab::PROJECT_NAME) }
+ let_it_be(:learn_gitlab_board) { create(:board, project: learn_gitlab_project, name: LearnGitlab::BOARD_NAME) }
+ let_it_be(:learn_gitlab_label) { create(:label, project: learn_gitlab_project, name: LearnGitlab::LABEL_NAME) }
+
+ before do
+ learn_gitlab_project.add_developer(current_user)
+ end
+
+ describe '.available?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project, :board, :label, :expected_result) do
+ nil | nil | nil | nil
+ nil | nil | true | nil
+ nil | true | nil | nil
+ nil | true | true | nil
+ true | nil | nil | nil
+ true | nil | true | nil
+ true | true | nil | nil
+ true | true | true | true
+ end
+
+ with_them do
+ before do
+ allow_next_instance_of(described_class) do |learn_gitlab|
+ allow(learn_gitlab).to receive(:project).and_return(project)
+ allow(learn_gitlab).to receive(:board).and_return(board)
+ allow(learn_gitlab).to receive(:label).and_return(label)
+ end
+ end
+
+ subject { described_class.new(current_user).available? }
+
+ it { is_expected.to be expected_result }
+ end
+ end
+
+ describe '.project' do
+ subject { described_class.new(current_user).project }
+
+ it { is_expected.to eq learn_gitlab_project }
+ end
+
+ describe '.board' do
+ subject { described_class.new(current_user).board }
+
+ it { is_expected.to eq learn_gitlab_board }
+ end
+
+ describe '.label' do
+ subject { described_class.new(current_user).label }
+
+ it { is_expected.to eq learn_gitlab_label }
+ end
+end
diff --git a/spec/lib/marginalia_spec.rb b/spec/lib/marginalia_spec.rb
index 2f446694083..a920f598c24 100644
--- a/spec/lib/marginalia_spec.rb
+++ b/spec/lib/marginalia_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Marginalia spec' do
+RSpec.describe 'Marginalia spec' do
class MarginaliaTestController < ActionController::Base
def first_user
User.first
diff --git a/spec/lib/mattermost/client_spec.rb b/spec/lib/mattermost/client_spec.rb
index 5fe35eb5f93..32755d1103c 100644
--- a/spec/lib/mattermost/client_spec.rb
+++ b/spec/lib/mattermost/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mattermost::Client do
+RSpec.describe Mattermost::Client do
let(:user) { build(:user) }
subject { described_class.new(user) }
diff --git a/spec/lib/mattermost/command_spec.rb b/spec/lib/mattermost/command_spec.rb
index f8c451a1522..26d1ec32232 100644
--- a/spec/lib/mattermost/command_spec.rb
+++ b/spec/lib/mattermost/command_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mattermost::Command do
+RSpec.describe Mattermost::Command do
let(:params) { { 'token' => 'token', team_id: 'abc' } }
before do
diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb
index ea12bd76c8d..5110d3cdfa3 100644
--- a/spec/lib/mattermost/session_spec.rb
+++ b/spec/lib/mattermost/session_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mattermost::Session, type: :request do
+RSpec.describe Mattermost::Session, type: :request do
include ExclusiveLeaseHelpers
include StubRequests
diff --git a/spec/lib/mattermost/team_spec.rb b/spec/lib/mattermost/team_spec.rb
index 2823dab67c9..0870114ca28 100644
--- a/spec/lib/mattermost/team_spec.rb
+++ b/spec/lib/mattermost/team_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mattermost::Team do
+RSpec.describe Mattermost::Team do
before do
session = Mattermost::Session.new(nil)
session.base_uri = 'http://mattermost.example.com'
diff --git a/spec/lib/microsoft_teams/activity_spec.rb b/spec/lib/microsoft_teams/activity_spec.rb
index 3fad2437f3e..d1eac7204a6 100644
--- a/spec/lib/microsoft_teams/activity_spec.rb
+++ b/spec/lib/microsoft_teams/activity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MicrosoftTeams::Activity do
+RSpec.describe MicrosoftTeams::Activity do
subject { described_class.new(title: 'title', subtitle: 'subtitle', text: 'text', image: 'image') }
describe '#prepare' do
diff --git a/spec/lib/microsoft_teams/notifier_spec.rb b/spec/lib/microsoft_teams/notifier_spec.rb
index 25538db159e..c35d7e8420c 100644
--- a/spec/lib/microsoft_teams/notifier_spec.rb
+++ b/spec/lib/microsoft_teams/notifier_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MicrosoftTeams::Notifier do
+RSpec.describe MicrosoftTeams::Notifier do
subject { described_class.new(webhook_url) }
let(:webhook_url) { 'https://example.gitlab.com/'}
diff --git a/spec/lib/object_storage/direct_upload_spec.rb b/spec/lib/object_storage/direct_upload_spec.rb
index c3890c72852..1c1455e2456 100644
--- a/spec/lib/object_storage/direct_upload_spec.rb
+++ b/spec/lib/object_storage/direct_upload_spec.rb
@@ -2,10 +2,11 @@
require 'spec_helper'
-describe ObjectStorage::DirectUpload do
+RSpec.describe ObjectStorage::DirectUpload do
let(:region) { 'us-east-1' }
let(:path_style) { false }
let(:use_iam_profile) { false }
+ let(:consolidated_settings) { false }
let(:credentials) do
{
provider: 'AWS',
@@ -23,7 +24,7 @@ describe ObjectStorage::DirectUpload do
let(:object_name) { 'tmp/uploads/my-file' }
let(:maximum_size) { 1.gigabyte }
- let(:direct_upload) { described_class.new(credentials, bucket_name, object_name, has_length: has_length, maximum_size: maximum_size) }
+ let(:direct_upload) { described_class.new(credentials, bucket_name, object_name, has_length: has_length, maximum_size: maximum_size, consolidated_settings: consolidated_settings) }
before do
Fog.unmock!
@@ -60,6 +61,38 @@ describe ObjectStorage::DirectUpload do
end
end
+ describe '#get_url' do
+ subject { described_class.new(credentials, bucket_name, object_name, has_length: true) }
+
+ context 'when AWS is used' do
+ it 'calls the proper method' do
+ expect_next_instance_of(::Fog::Storage, credentials) do |connection|
+ expect(connection).to receive(:get_object_url).once
+ end
+
+ subject.get_url
+ end
+ end
+
+ context 'when Google is used' do
+ let(:credentials) do
+ {
+ provider: 'Google',
+ google_storage_access_key_id: 'GOOGLE_ACCESS_KEY_ID',
+ google_storage_secret_access_key: 'GOOGLE_SECRET_ACCESS_KEY'
+ }
+ end
+
+ it 'calls the proper method' do
+ expect_next_instance_of(::Fog::Storage, credentials) do |connection|
+ expect(connection).to receive(:get_object_https_url).once
+ end
+
+ subject.get_url
+ end
+ end
+ end
+
describe '#to_hash' do
subject { direct_upload.to_hash }
@@ -109,6 +142,14 @@ describe ObjectStorage::DirectUpload do
expect(subject[:UseWorkhorseClient]).to eq(use_iam_profile)
end
end
+
+ context 'when consolidated settings are used' do
+ let(:consolidated_settings) { true }
+
+ it 'enables the Workhorse client' do
+ expect(subject[:UseWorkhorseClient]).to be true
+ end
+ end
end
shared_examples 'a valid Google upload' do
diff --git a/spec/lib/omni_auth/strategies/jwt_spec.rb b/spec/lib/omni_auth/strategies/jwt_spec.rb
index 302329cf198..0f4528d4fbe 100644
--- a/spec/lib/omni_auth/strategies/jwt_spec.rb
+++ b/spec/lib/omni_auth/strategies/jwt_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe OmniAuth::Strategies::Jwt do
+RSpec.describe OmniAuth::Strategies::Jwt do
include Rack::Test::Methods
include DeviseHelpers
diff --git a/spec/lib/pager_duty/webhook_payload_parser_spec.rb b/spec/lib/pager_duty/webhook_payload_parser_spec.rb
new file mode 100644
index 00000000000..0010165318d
--- /dev/null
+++ b/spec/lib/pager_duty/webhook_payload_parser_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe PagerDuty::WebhookPayloadParser do
+ describe '.call' do
+ let(:fixture_file) do
+ File.read(File.join(File.dirname(__FILE__), '../../fixtures/pager_duty/webhook_incident_trigger.json'))
+ end
+
+ subject(:parse) { described_class.call(payload) }
+
+ context 'when payload is a correct PagerDuty payload' do
+ let(:payload) { Gitlab::Json.parse(fixture_file) }
+
+ it 'returns parsed payload' do
+ is_expected.to eq(
+ [
+ {
+ 'event' => 'incident.trigger',
+ 'incident' => {
+ 'url' => 'https://webdemo.pagerduty.com/incidents/PRORDTY',
+ 'incident_number' => 33,
+ 'title' => 'My new incident',
+ 'status' => 'triggered',
+ 'created_at' => '2017-09-26T15:14:36Z',
+ 'urgency' => 'high',
+ 'incident_key' => nil,
+ 'assignees' => [{
+ 'summary' => 'Laura Haley',
+ 'url' => 'https://webdemo.pagerduty.com/users/P553OPV'
+ }],
+ 'impacted_services' => [{
+ 'summary' => 'Production XDB Cluster',
+ 'url' => 'https://webdemo.pagerduty.com/services/PN49J75'
+ }]
+ }
+ }
+ ]
+ )
+ end
+
+ context 'when assignments summary and html_url are blank' do
+ before do
+ payload['messages'].each do |m|
+ m['incident']['assignments'] = [{ 'assignee' => { 'summary' => '', 'html_url' => '' } }]
+ end
+ end
+
+ it 'returns parsed payload with blank assignees' do
+ assignees = parse.map { |events| events['incident'].slice('assignees') }
+
+ expect(assignees).to eq([{ 'assignees' => [] }])
+ end
+ end
+
+ context 'when impacted_services summary and html_url are blank' do
+ before do
+ payload['messages'].each do |m|
+ m['incident']['impacted_services'] = [{ 'summary' => '', 'html_url' => '' }]
+ end
+ end
+
+ it 'returns parsed payload with blank assignees' do
+ assignees = parse.map { |events| events['incident'].slice('impacted_services') }
+
+ expect(assignees).to eq([{ 'impacted_services' => [] }])
+ end
+ end
+ end
+
+ context 'when payload has no incidents' do
+ let(:payload) { { 'messages' => [{ 'event' => 'incident.trigger' }] } }
+
+ it 'returns payload with blank incidents' do
+ is_expected.to eq([{ 'event' => 'incident.trigger', 'incident' => {} }])
+ end
+ end
+ end
+end
diff --git a/spec/lib/peek/views/bullet_detailed_spec.rb b/spec/lib/peek/views/bullet_detailed_spec.rb
index a482cadc7db..ec2f798a320 100644
--- a/spec/lib/peek/views/bullet_detailed_spec.rb
+++ b/spec/lib/peek/views/bullet_detailed_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Peek::Views::BulletDetailed do
+RSpec.describe Peek::Views::BulletDetailed do
subject { described_class.new }
before do
diff --git a/spec/lib/peek/views/detailed_view_spec.rb b/spec/lib/peek/views/detailed_view_spec.rb
index d8660a55ea9..8d6d9a829ef 100644
--- a/spec/lib/peek/views/detailed_view_spec.rb
+++ b/spec/lib/peek/views/detailed_view_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Peek::Views::DetailedView, :request_store do
+RSpec.describe Peek::Views::DetailedView, :request_store do
context 'when a class defines thresholds' do
let(:threshold_view) do
Class.new(described_class) do
diff --git a/spec/lib/peek/views/redis_detailed_spec.rb b/spec/lib/peek/views/redis_detailed_spec.rb
index a270c006a43..a757af50dcb 100644
--- a/spec/lib/peek/views/redis_detailed_spec.rb
+++ b/spec/lib/peek/views/redis_detailed_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Peek::Views::RedisDetailed, :request_store do
+RSpec.describe Peek::Views::RedisDetailed, :request_store do
subject { described_class.new }
using RSpec::Parameterized::TableSyntax
diff --git a/spec/lib/peek/views/rugged_spec.rb b/spec/lib/peek/views/rugged_spec.rb
index 39968afed39..31418b5fc81 100644
--- a/spec/lib/peek/views/rugged_spec.rb
+++ b/spec/lib/peek/views/rugged_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Peek::Views::Rugged, :request_store do
+RSpec.describe Peek::Views::Rugged, :request_store do
subject { described_class.new }
let(:project) { create(:project) }
diff --git a/spec/lib/product_analytics/event_params_spec.rb b/spec/lib/product_analytics/event_params_spec.rb
new file mode 100644
index 00000000000..d6c098599d6
--- /dev/null
+++ b/spec/lib/product_analytics/event_params_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ProductAnalytics::EventParams do
+ describe '.parse_event_params' do
+ subject { described_class.parse_event_params(raw_event) }
+
+ let(:raw_event) { Gitlab::Json.parse(fixture_file('product_analytics/event.json')) }
+
+ it 'extracts all params from raw event' do
+ expected_params = {
+ project_id: '1',
+ platform: 'web',
+ name_tracker: 'sp',
+ v_tracker: 'js-2.14.0',
+ event_id: 'fbf14096-74ee-47e4-883c-8a0d6cb72e37',
+ domain_userid: '79543c31-cfc3-4479-a737-fafb9333c8ba',
+ domain_sessionid: '54f6d3f3-f4f9-4fdc-87e0-a2c775234c1b',
+ domain_sessionidx: 4,
+ page_url: 'http://example.com/products/1',
+ page_referrer: 'http://example.com/products/1',
+ br_lang: 'en-US',
+ br_cookies: true,
+ os_timezone: 'America/Los_Angeles',
+ doc_charset: 'UTF-8'
+ }
+
+ expect(subject).to include(expected_params)
+ end
+ end
+
+ describe '.has_required_params?' do
+ subject { described_class.has_required_params?(params) }
+
+ context 'aid and eid are present' do
+ let(:params) { { 'aid' => 1, 'eid' => 2 } }
+
+ it { expect(subject).to be_truthy }
+ end
+
+ context 'aid and eid are missing' do
+ let(:params) { {} }
+
+ it { expect(subject).to be_falsey }
+ end
+
+ context 'eid is missing' do
+ let(:params) { { 'aid' => 1 } }
+
+ it { expect(subject).to be_falsey }
+ end
+ end
+end
diff --git a/spec/lib/prometheus/cleanup_multiproc_dir_service_spec.rb b/spec/lib/prometheus/cleanup_multiproc_dir_service_spec.rb
index c7302a1a656..db77a5d42d8 100644
--- a/spec/lib/prometheus/cleanup_multiproc_dir_service_spec.rb
+++ b/spec/lib/prometheus/cleanup_multiproc_dir_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Prometheus::CleanupMultiprocDirService do
+RSpec.describe Prometheus::CleanupMultiprocDirService do
describe '.call' do
subject { described_class.new.execute }
diff --git a/spec/lib/prometheus/pid_provider_spec.rb b/spec/lib/prometheus/pid_provider_spec.rb
index 5a17f25f144..f1d7f2ffff5 100644
--- a/spec/lib/prometheus/pid_provider_spec.rb
+++ b/spec/lib/prometheus/pid_provider_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Prometheus::PidProvider do
+RSpec.describe Prometheus::PidProvider do
describe '.worker_id' do
subject { described_class.worker_id }
diff --git a/spec/lib/quality/test_level_spec.rb b/spec/lib/quality/test_level_spec.rb
index ad29c80b07a..0b113e8b63a 100644
--- a/spec/lib/quality/test_level_spec.rb
+++ b/spec/lib/quality/test_level_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe Quality::TestLevel do
context 'when level is migration' do
it 'returns a pattern' do
expect(subject.pattern(:migration))
- .to eq("spec/{migrations}{,/**/}*_spec.rb")
+ .to eq("spec/{migrations,lib/gitlab/background_migration,lib/ee/gitlab/background_migration}{,/**/}*_spec.rb")
end
end
@@ -96,7 +96,7 @@ RSpec.describe Quality::TestLevel do
context 'when level is migration' do
it 'returns a regexp' do
expect(subject.regexp(:migration))
- .to eq(%r{spec/(migrations)})
+ .to eq(%r{spec/(migrations|lib/gitlab/background_migration|lib/ee/gitlab/background_migration)})
end
end
diff --git a/spec/lib/rspec_flaky/config_spec.rb b/spec/lib/rspec_flaky/config_spec.rb
index 13b2219267b..6b148599b67 100644
--- a/spec/lib/rspec_flaky/config_spec.rb
+++ b/spec/lib/rspec_flaky/config_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RspecFlaky::Config, :aggregate_failures do
+RSpec.describe RspecFlaky::Config, :aggregate_failures do
before do
# Stub these env variables otherwise specs don't behave the same on the CI
stub_env('FLAKY_RSPEC_GENERATE_REPORT', nil)
diff --git a/spec/lib/rspec_flaky/example_spec.rb b/spec/lib/rspec_flaky/example_spec.rb
index 4679dd818db..aaf5ddc6f74 100644
--- a/spec/lib/rspec_flaky/example_spec.rb
+++ b/spec/lib/rspec_flaky/example_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RspecFlaky::Example do
+RSpec.describe RspecFlaky::Example do
let(:example_attrs) do
{
id: 'spec/foo/bar_spec.rb:2',
diff --git a/spec/lib/rspec_flaky/flaky_example_spec.rb b/spec/lib/rspec_flaky/flaky_example_spec.rb
index d4a1d6c882a..8ac323475d6 100644
--- a/spec/lib/rspec_flaky/flaky_example_spec.rb
+++ b/spec/lib/rspec_flaky/flaky_example_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RspecFlaky::FlakyExample, :aggregate_failures do
+RSpec.describe RspecFlaky::FlakyExample, :aggregate_failures do
let(:flaky_example_attrs) do
{
example_id: 'spec/foo/bar_spec.rb:2',
diff --git a/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb b/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb
index 2e224cda61b..5718d8211af 100644
--- a/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb
+++ b/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RspecFlaky::FlakyExamplesCollection, :aggregate_failures do
+RSpec.describe RspecFlaky::FlakyExamplesCollection, :aggregate_failures do
let(:collection_hash) do
{
a: { example_id: 'spec/foo/bar_spec.rb:2' },
diff --git a/spec/lib/rspec_flaky/listener_spec.rb b/spec/lib/rspec_flaky/listener_spec.rb
index 44b8d99b74f..2438ae171d3 100644
--- a/spec/lib/rspec_flaky/listener_spec.rb
+++ b/spec/lib/rspec_flaky/listener_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RspecFlaky::Listener, :aggregate_failures do
+RSpec.describe RspecFlaky::Listener, :aggregate_failures do
let(:already_flaky_example_uid) { '6e869794f4cfd2badd93eb68719371d1' }
let(:suite_flaky_example_report) do
{
diff --git a/spec/lib/rspec_flaky/report_spec.rb b/spec/lib/rspec_flaky/report_spec.rb
index 37330f39e1c..e735329a8a3 100644
--- a/spec/lib/rspec_flaky/report_spec.rb
+++ b/spec/lib/rspec_flaky/report_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RspecFlaky::Report, :aggregate_failures do
+RSpec.describe RspecFlaky::Report, :aggregate_failures do
let(:thirty_one_days) { 3600 * 24 * 31 }
let(:collection_hash) do
{
diff --git a/spec/lib/safe_zip/entry_spec.rb b/spec/lib/safe_zip/entry_spec.rb
index be3d46917ee..9929b8073a0 100644
--- a/spec/lib/safe_zip/entry_spec.rb
+++ b/spec/lib/safe_zip/entry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SafeZip::Entry do
+RSpec.describe SafeZip::Entry do
let(:target_path) { Dir.mktmpdir('safe-zip') }
let(:directories) { %w(public folder/with/subfolder) }
let(:params) { SafeZip::ExtractParams.new(directories: directories, to: target_path) }
diff --git a/spec/lib/safe_zip/extract_params_spec.rb b/spec/lib/safe_zip/extract_params_spec.rb
index f66d3de89ee..880c4358663 100644
--- a/spec/lib/safe_zip/extract_params_spec.rb
+++ b/spec/lib/safe_zip/extract_params_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SafeZip::ExtractParams do
+RSpec.describe SafeZip::ExtractParams do
let(:target_path) { Dir.mktmpdir("safe-zip") }
let(:params) { described_class.new(directories: directories, to: target_path) }
let(:directories) { %w(public folder/with/subfolder) }
diff --git a/spec/lib/safe_zip/extract_spec.rb b/spec/lib/safe_zip/extract_spec.rb
index d388135c3fb..30b7e1cdd2c 100644
--- a/spec/lib/safe_zip/extract_spec.rb
+++ b/spec/lib/safe_zip/extract_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SafeZip::Extract do
+RSpec.describe SafeZip::Extract do
let(:target_path) { Dir.mktmpdir('safe-zip') }
let(:directories) { %w(public) }
let(:object) { described_class.new(archive) }
diff --git a/spec/lib/sentry/api_urls_spec.rb b/spec/lib/sentry/api_urls_spec.rb
index 78455f8d51f..d56b4397e1c 100644
--- a/spec/lib/sentry/api_urls_spec.rb
+++ b/spec/lib/sentry/api_urls_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Sentry::ApiUrls do
+RSpec.describe Sentry::ApiUrls do
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/' }
let(:token) { 'test-token' }
let(:issue_id) { '123456' }
diff --git a/spec/lib/sentry/client/event_spec.rb b/spec/lib/sentry/client/event_spec.rb
index 58891895bfa..af1e28d09bb 100644
--- a/spec/lib/sentry/client/event_spec.rb
+++ b/spec/lib/sentry/client/event_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Sentry::Client do
+RSpec.describe Sentry::Client do
include SentryClientHelpers
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
diff --git a/spec/lib/sentry/client/issue_link_spec.rb b/spec/lib/sentry/client/issue_link_spec.rb
index 293937f6100..fe3abe7cb23 100644
--- a/spec/lib/sentry/client/issue_link_spec.rb
+++ b/spec/lib/sentry/client/issue_link_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Sentry::Client::IssueLink do
+RSpec.describe Sentry::Client::IssueLink do
include SentryClientHelpers
let_it_be(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
diff --git a/spec/lib/sentry/client/issue_spec.rb b/spec/lib/sentry/client/issue_spec.rb
index b683ad6d4a9..a6279aeadd2 100644
--- a/spec/lib/sentry/client/issue_spec.rb
+++ b/spec/lib/sentry/client/issue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Sentry::Client::Issue do
+RSpec.describe Sentry::Client::Issue do
include SentryClientHelpers
let(:token) { 'test-token' }
@@ -234,6 +234,7 @@ describe Sentry::Client::Issue do
:first_release_short_version | [:firstRelease, :shortVersion]
:last_release_short_version | [:lastRelease, :shortVersion]
:first_release_version | [:firstRelease, :version]
+ :last_release_version | [:lastRelease, :version]
end
with_them do
diff --git a/spec/lib/sentry/client/projects_spec.rb b/spec/lib/sentry/client/projects_spec.rb
index 1b5bbb8f81a..ea2c5ccb81e 100644
--- a/spec/lib/sentry/client/projects_spec.rb
+++ b/spec/lib/sentry/client/projects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Sentry::Client::Projects do
+RSpec.describe Sentry::Client::Projects do
include SentryClientHelpers
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
diff --git a/spec/lib/sentry/client/repo_spec.rb b/spec/lib/sentry/client/repo_spec.rb
index 524dca8dcf6..956c0b6eee1 100644
--- a/spec/lib/sentry/client/repo_spec.rb
+++ b/spec/lib/sentry/client/repo_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Sentry::Client::Repo do
+RSpec.describe Sentry::Client::Repo do
include SentryClientHelpers
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
diff --git a/spec/lib/sentry/client_spec.rb b/spec/lib/sentry/client_spec.rb
index e2da4564ca1..cddcb6e98fa 100644
--- a/spec/lib/sentry/client_spec.rb
+++ b/spec/lib/sentry/client_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Sentry::Client do
+RSpec.describe Sentry::Client do
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
let(:token) { 'test-token' }
diff --git a/spec/lib/sentry/pagination_parser_spec.rb b/spec/lib/sentry/pagination_parser_spec.rb
index 1be6f9f4163..c4ed24827bb 100644
--- a/spec/lib/sentry/pagination_parser_spec.rb
+++ b/spec/lib/sentry/pagination_parser_spec.rb
@@ -1,11 +1,8 @@
# frozen_string_literal: true
require 'fast_spec_helper'
-require 'support/helpers/fixture_helpers'
-
-describe Sentry::PaginationParser do
- include FixtureHelpers
+RSpec.describe Sentry::PaginationParser do
describe '.parse' do
subject { described_class.parse(headers) }
diff --git a/spec/lib/serializers/json_spec.rb b/spec/lib/serializers/json_spec.rb
index dfe85d3f362..7054f98a719 100644
--- a/spec/lib/serializers/json_spec.rb
+++ b/spec/lib/serializers/json_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-describe Serializers::JSON do
+RSpec.describe Serializers::JSON do
describe '.dump' do
let(:obj) { { key: "value" } }
diff --git a/spec/lib/system_check/app/authorized_keys_permission_check_spec.rb b/spec/lib/system_check/app/authorized_keys_permission_check_spec.rb
index 1a8123c3f0a..a5a3105c125 100644
--- a/spec/lib/system_check/app/authorized_keys_permission_check_spec.rb
+++ b/spec/lib/system_check/app/authorized_keys_permission_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SystemCheck::App::AuthorizedKeysPermissionCheck do
+RSpec.describe SystemCheck::App::AuthorizedKeysPermissionCheck do
subject(:system_check) { described_class.new }
describe '#skip?' do
diff --git a/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb b/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb
index f132f608ab6..2c996635c36 100644
--- a/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb
+++ b/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SystemCheck::App::GitUserDefaultSSHConfigCheck do
+RSpec.describe SystemCheck::App::GitUserDefaultSSHConfigCheck do
let(:username) { '_this_user_will_not_exist_unless_it_is_stubbed' }
let(:base_dir) { Dir.mktmpdir }
let(:home_dir) { File.join(base_dir, "/var/lib/#{username}") }
diff --git a/spec/lib/system_check/app/hashed_storage_all_projects_check_spec.rb b/spec/lib/system_check/app/hashed_storage_all_projects_check_spec.rb
index e5e7f6a4450..14bf9d61ab5 100644
--- a/spec/lib/system_check/app/hashed_storage_all_projects_check_spec.rb
+++ b/spec/lib/system_check/app/hashed_storage_all_projects_check_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rake_helper'
-describe SystemCheck::App::HashedStorageAllProjectsCheck do
+RSpec.describe SystemCheck::App::HashedStorageAllProjectsCheck do
before do
silence_output
end
diff --git a/spec/lib/system_check/app/hashed_storage_enabled_check_spec.rb b/spec/lib/system_check/app/hashed_storage_enabled_check_spec.rb
index d5a0014b791..32a2f409858 100644
--- a/spec/lib/system_check/app/hashed_storage_enabled_check_spec.rb
+++ b/spec/lib/system_check/app/hashed_storage_enabled_check_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rake_helper'
-describe SystemCheck::App::HashedStorageEnabledCheck do
+RSpec.describe SystemCheck::App::HashedStorageEnabledCheck do
before do
silence_output
end
diff --git a/spec/lib/system_check/base_check_spec.rb b/spec/lib/system_check/base_check_spec.rb
index ccb7b483bdc..59b2fc519ae 100644
--- a/spec/lib/system_check/base_check_spec.rb
+++ b/spec/lib/system_check/base_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SystemCheck::BaseCheck do
+RSpec.describe SystemCheck::BaseCheck do
context 'helpers on instance level' do
it 'responds to SystemCheck::Helpers methods' do
expect(subject).to respond_to :fix_and_rerun, :for_more_information, :see_installation_guide_section,
diff --git a/spec/lib/system_check/orphans/namespace_check_spec.rb b/spec/lib/system_check/orphans/namespace_check_spec.rb
index f7491e40438..795dfde9029 100644
--- a/spec/lib/system_check/orphans/namespace_check_spec.rb
+++ b/spec/lib/system_check/orphans/namespace_check_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rake_helper'
-describe SystemCheck::Orphans::NamespaceCheck do
+RSpec.describe SystemCheck::Orphans::NamespaceCheck do
let(:storages) { Gitlab.config.repositories.storages.reject { |key, _| key.eql? 'broken' } }
before do
diff --git a/spec/lib/system_check/orphans/repository_check_spec.rb b/spec/lib/system_check/orphans/repository_check_spec.rb
index a5e06f30e75..2ab30f4802d 100644
--- a/spec/lib/system_check/orphans/repository_check_spec.rb
+++ b/spec/lib/system_check/orphans/repository_check_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rake_helper'
-describe SystemCheck::Orphans::RepositoryCheck do
+RSpec.describe SystemCheck::Orphans::RepositoryCheck do
let(:storages) { Gitlab.config.repositories.storages.reject { |key, _| key.eql? 'broken' } }
before do
diff --git a/spec/lib/system_check/simple_executor_spec.rb b/spec/lib/system_check/simple_executor_spec.rb
index 58f3a7df197..c9a09d86e32 100644
--- a/spec/lib/system_check/simple_executor_spec.rb
+++ b/spec/lib/system_check/simple_executor_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rake_helper'
-describe SystemCheck::SimpleExecutor do
+RSpec.describe SystemCheck::SimpleExecutor do
before do
stub_const('SimpleCheck', Class.new(SystemCheck::BaseCheck))
stub_const('OtherCheck', Class.new(SystemCheck::BaseCheck))
diff --git a/spec/lib/system_check_spec.rb b/spec/lib/system_check_spec.rb
index da1916455ba..5ddb2741c4a 100644
--- a/spec/lib/system_check_spec.rb
+++ b/spec/lib/system_check_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rake_helper'
-describe SystemCheck do
+RSpec.describe SystemCheck do
before do
stub_const('SimpleCheck', Class.new(SystemCheck::BaseCheck))
stub_const('OtherCheck', Class.new(SystemCheck::BaseCheck))
diff --git a/spec/lib/uploaded_file_spec.rb b/spec/lib/uploaded_file_spec.rb
index 39055a2479f..5ff46193b4f 100644
--- a/spec/lib/uploaded_file_spec.rb
+++ b/spec/lib/uploaded_file_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UploadedFile do
+RSpec.describe UploadedFile do
let(:temp_dir) { Dir.tmpdir }
let(:temp_file) { Tempfile.new(%w[test test], temp_dir) }
diff --git a/spec/mailers/abuse_report_mailer_spec.rb b/spec/mailers/abuse_report_mailer_spec.rb
index fcbffb52849..4eb616722ac 100644
--- a/spec/mailers/abuse_report_mailer_spec.rb
+++ b/spec/mailers/abuse_report_mailer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AbuseReportMailer do
+RSpec.describe AbuseReportMailer do
include EmailSpec::Matchers
describe '.notify' do
diff --git a/spec/mailers/devise_mailer_spec.rb b/spec/mailers/devise_mailer_spec.rb
new file mode 100644
index 00000000000..4637df9c8a3
--- /dev/null
+++ b/spec/mailers/devise_mailer_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'email_spec'
+
+RSpec.describe DeviseMailer do
+ describe "#confirmation_instructions" do
+ subject { described_class.confirmation_instructions(user, 'faketoken', {}) }
+
+ context "when confirming a new account" do
+ let(:user) { build(:user, created_at: 1.minute.ago, unconfirmed_email: nil) }
+
+ it "shows the expected text" do
+ expect(subject.body.encoded).to have_text "Welcome"
+ expect(subject.body.encoded).not_to have_text user.email
+ end
+ end
+
+ context "when confirming the unconfirmed_email" do
+ let(:user) { build(:user, unconfirmed_email: 'jdoe@example.com') }
+
+ it "shows the expected text" do
+ expect(subject.body.encoded).not_to have_text "Welcome"
+ expect(subject.body.encoded).to have_text user.unconfirmed_email
+ expect(subject.body.encoded).not_to have_text user.email
+ end
+ end
+
+ context "when re-confirming the primary email after a security issue" do
+ let(:user) { build(:user, created_at: 10.days.ago, unconfirmed_email: nil) }
+
+ it "shows the expected text" do
+ expect(subject.body.encoded).not_to have_text "Welcome"
+ expect(subject.body.encoded).to have_text user.email
+ end
+ end
+ end
+end
diff --git a/spec/mailers/email_rejection_mailer_spec.rb b/spec/mailers/email_rejection_mailer_spec.rb
index 2ce2e3e1410..d28f034105a 100644
--- a/spec/mailers/email_rejection_mailer_spec.rb
+++ b/spec/mailers/email_rejection_mailer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EmailRejectionMailer do
+RSpec.describe EmailRejectionMailer do
include EmailSpec::Matchers
describe '#rejection' do
diff --git a/spec/mailers/emails/auto_devops_spec.rb b/spec/mailers/emails/auto_devops_spec.rb
index dd7c12c3143..950fcfbf4df 100644
--- a/spec/mailers/emails/auto_devops_spec.rb
+++ b/spec/mailers/emails/auto_devops_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Emails::AutoDevops do
+RSpec.describe Emails::AutoDevops do
include EmailSpec::Matchers
describe '#auto_devops_disabled_email' do
diff --git a/spec/mailers/emails/groups_spec.rb b/spec/mailers/emails/groups_spec.rb
index b4746e120e0..01846172440 100644
--- a/spec/mailers/emails/groups_spec.rb
+++ b/spec/mailers/emails/groups_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'email_spec'
-describe Emails::Groups do
+RSpec.describe Emails::Groups do
include EmailSpec::Matchers
let(:group) { create(:group) }
diff --git a/spec/mailers/emails/issues_spec.rb b/spec/mailers/emails/issues_spec.rb
index dfd974aa5f3..21e07c0252d 100644
--- a/spec/mailers/emails/issues_spec.rb
+++ b/spec/mailers/emails/issues_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'email_spec'
-describe Emails::Issues do
+RSpec.describe Emails::Issues do
include EmailSpec::Matchers
it 'adds email methods to Notify' do
diff --git a/spec/mailers/emails/merge_requests_spec.rb b/spec/mailers/emails/merge_requests_spec.rb
index 541acc47172..477fb16400a 100644
--- a/spec/mailers/emails/merge_requests_spec.rb
+++ b/spec/mailers/emails/merge_requests_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'email_spec'
-describe Emails::MergeRequests do
+RSpec.describe Emails::MergeRequests do
include EmailSpec::Matchers
describe "#resolved_all_discussions_email" do
@@ -17,4 +17,20 @@ describe Emails::MergeRequests do
expect(subject).to have_body_text current_user.name
end
end
+
+ describe "#merge_when_pipeline_succeeds_email" do
+ let(:user) { create(:user) }
+ let(:merge_request) { create(:merge_request) }
+ let(:current_user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:title) { "Merge request #{merge_request.to_reference} was scheduled to merge after pipeline succeeds by #{current_user.name}" }
+
+ subject { Notify.merge_when_pipeline_succeeds_email(user.id, merge_request.id, current_user.id) }
+
+ it "has required details" do
+ expect(subject).to have_content title
+ expect(subject).to have_content merge_request.to_reference
+ expect(subject).to have_content current_user.name
+ end
+ end
end
diff --git a/spec/mailers/emails/pages_domains_spec.rb b/spec/mailers/emails/pages_domains_spec.rb
index 5029a17e4e5..cf17f2e5ebf 100644
--- a/spec/mailers/emails/pages_domains_spec.rb
+++ b/spec/mailers/emails/pages_domains_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'email_spec'
-describe Emails::PagesDomains do
+RSpec.describe Emails::PagesDomains do
include EmailSpec::Matchers
include_context 'gitlab email notification'
diff --git a/spec/mailers/emails/pipelines_spec.rb b/spec/mailers/emails/pipelines_spec.rb
index cc901da98dc..f0f23fe2097 100644
--- a/spec/mailers/emails/pipelines_spec.rb
+++ b/spec/mailers/emails/pipelines_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'email_spec'
-describe Emails::Pipelines do
+RSpec.describe Emails::Pipelines do
include EmailSpec::Matchers
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/mailers/emails/profile_spec.rb b/spec/mailers/emails/profile_spec.rb
index cbf42da2085..ee91df360b6 100644
--- a/spec/mailers/emails/profile_spec.rb
+++ b/spec/mailers/emails/profile_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'email_spec'
-describe Emails::Profile do
+RSpec.describe Emails::Profile do
include EmailSpec::Matchers
include_context 'gitlab email notification'
diff --git a/spec/mailers/emails/projects_spec.rb b/spec/mailers/emails/projects_spec.rb
index 6c94ed0aa4d..599f62a8113 100644
--- a/spec/mailers/emails/projects_spec.rb
+++ b/spec/mailers/emails/projects_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'email_spec'
-describe Emails::Projects do
+RSpec.describe Emails::Projects do
include EmailSpec::Matchers
include_context 'gitlab email notification'
diff --git a/spec/mailers/emails/releases_spec.rb b/spec/mailers/emails/releases_spec.rb
index c614c009434..60e522c7cfa 100644
--- a/spec/mailers/emails/releases_spec.rb
+++ b/spec/mailers/emails/releases_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'email_spec'
-describe Emails::Releases do
+RSpec.describe Emails::Releases do
include EmailSpec::Matchers
include_context 'gitlab email notification'
diff --git a/spec/mailers/emails/service_desk_spec.rb b/spec/mailers/emails/service_desk_spec.rb
new file mode 100644
index 00000000000..842f82539cb
--- /dev/null
+++ b/spec/mailers/emails/service_desk_spec.rb
@@ -0,0 +1,188 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'email_spec'
+
+RSpec.describe Emails::ServiceDesk do
+ include EmailSpec::Helpers
+ include EmailSpec::Matchers
+ include EmailHelpers
+
+ include_context 'gitlab email notification'
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let(:template) { double(content: template_content) }
+
+ before do
+ stub_const('ServiceEmailClass', Class.new(ApplicationMailer))
+
+ ServiceEmailClass.class_eval do
+ include GitlabRoutingHelper
+ include EmailsHelper
+ include Emails::ServiceDesk
+
+ helper GitlabRoutingHelper
+ helper EmailsHelper
+
+ # this method is implemented in Notify class, we don't need to test it
+ def reply_key
+ 'test-key'
+ end
+
+ # this method is implemented in Notify class, we don't need to test it
+ def sender(author_id, params = {})
+ author_id
+ end
+
+ # this method is implemented in Notify class
+ #
+ # We do not need to test the Notify method, it is already tested in notify_spec
+ def mail_new_thread(issue, options)
+ # we need to rewrite this in order to look up templates in the correct directory
+ self.class.mailer_name = 'notify'
+
+ # this is needed for default layout
+ @unsubscribe_url = 'http://unsubscribe.example.com'
+
+ mail(options)
+ end
+ alias_method :mail_answer_thread, :mail_new_thread
+ end
+ end
+
+ shared_examples 'handle template content' do |template_key|
+ before do
+ expect(Gitlab::Template::ServiceDeskTemplate).to receive(:find)
+ .with(template_key, issue.project)
+ .and_return(template)
+ end
+
+ it 'builds the email correctly' do
+ aggregate_failures do
+ is_expected.to have_referable_subject(issue, include_project: false, reply: reply_in_subject)
+ is_expected.to have_body_text(expected_body)
+ expect(subject.content_type).to include('text/html')
+ end
+ end
+ end
+
+ shared_examples 'read template from repository' do |template_key|
+ let(:template_content) { 'custom text' }
+ let(:issue) { create(:issue, project: project)}
+
+ context 'when a template is in the repository' do
+ let(:project) { create(:project, :custom_repo, files: { ".gitlab/service_desk_templates/#{template_key}.md" => template_content }) }
+
+ it 'uses the text template from the template' do
+ is_expected.to have_body_text(template_content)
+ end
+ end
+
+ context 'when the service_desk_templates directory does not contain correct template' do
+ let(:project) { create(:project, :custom_repo, files: { ".gitlab/service_desk_templates/another_file.md" => template_content }) }
+
+ it 'uses the default template' do
+ is_expected.to have_body_text(default_text)
+ end
+ end
+
+ context 'when the service_desk_templates directory does not exist' do
+ let(:project) { create(:project, :custom_repo, files: { "other_directory/another_file.md" => template_content }) }
+
+ it 'uses the default template' do
+ is_expected.to have_body_text(default_text)
+ end
+ end
+
+ context 'when the project does not have a repo' do
+ let(:project) { create(:project) }
+
+ it 'uses the default template' do
+ is_expected.to have_body_text(default_text)
+ end
+ end
+ end
+
+ describe '.service_desk_thank_you_email' do
+ let_it_be(:reply_in_subject) { true }
+ let_it_be(:default_text) do
+ "Thank you for your support request! We are tracking your request as ticket #{issue.to_reference}, and will respond as soon as we can."
+ end
+
+ subject { ServiceEmailClass.service_desk_thank_you_email(issue.id) }
+
+ it_behaves_like 'read template from repository', 'thank_you'
+
+ context 'handling template markdown' do
+ context 'with a simple text' do
+ let(:template_content) { 'thank you, **your new issue** has been created.' }
+ let(:expected_body) { 'thank you, <strong>your new issue</strong> has been created.' }
+
+ it_behaves_like 'handle template content', 'thank_you'
+ end
+
+ context 'with an issue id and issue path placeholders' do
+ let(:template_content) { 'thank you, **your new issue:** %{ISSUE_ID}, path: %{ISSUE_PATH}' }
+ let(:expected_body) { "thank you, <strong>your new issue:</strong> ##{issue.iid}, path: #{project.full_path}##{issue.iid}" }
+
+ it_behaves_like 'handle template content', 'thank_you'
+ end
+
+ context 'with an issue id placeholder with whitespace' do
+ let(:template_content) { 'thank you, **your new issue:** %{ ISSUE_ID}' }
+ let(:expected_body) { "thank you, <strong>your new issue:</strong> ##{issue.iid}" }
+
+ it_behaves_like 'handle template content', 'thank_you'
+ end
+
+ context 'with unexpected placeholder' do
+ let(:template_content) { 'thank you, **your new issue:** %{this is issue}' }
+ let(:expected_body) { "thank you, <strong>your new issue:</strong> %{this is issue}" }
+
+ it_behaves_like 'handle template content', 'thank_you'
+ end
+ end
+ end
+
+ describe '.service_desk_new_note_email' do
+ let_it_be(:reply_in_subject) { false }
+ let_it_be(:note) { create(:note_on_issue, noteable: issue, project: project) }
+ let_it_be(:default_text) { note.note }
+
+ subject { ServiceEmailClass.service_desk_new_note_email(issue.id, note.id) }
+
+ it_behaves_like 'read template from repository', 'new_note'
+
+ context 'handling template markdown' do
+ context 'with a simple text' do
+ let(:template_content) { 'thank you, **new note on issue** has been created.' }
+ let(:expected_body) { 'thank you, <strong>new note on issue</strong> has been created.' }
+
+ it_behaves_like 'handle template content', 'new_note'
+ end
+
+ context 'with an issue id, issue path and note placeholders' do
+ let(:template_content) { 'thank you, **new note on issue:** %{ISSUE_ID}, path: %{ISSUE_PATH}: %{NOTE_TEXT}' }
+ let(:expected_body) { "thank you, <strong>new note on issue:</strong> ##{issue.iid}, path: #{project.full_path}##{issue.iid}: #{note.note}" }
+
+ it_behaves_like 'handle template content', 'new_note'
+ end
+
+ context 'with an issue id placeholder with whitespace' do
+ let(:template_content) { 'thank you, **new note on issue:** %{ ISSUE_ID}: %{ NOTE_TEXT }' }
+ let(:expected_body) { "thank you, <strong>new note on issue:</strong> ##{issue.iid}: #{note.note}" }
+
+ it_behaves_like 'handle template content', 'new_note'
+ end
+
+ context 'with unexpected placeholder' do
+ let(:template_content) { 'thank you, **new note on issue:** %{this is issue}' }
+ let(:expected_body) { "thank you, <strong>new note on issue:</strong> %{this is issue}" }
+
+ it_behaves_like 'handle template content', 'new_note'
+ end
+ end
+ end
+end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 8b99cc41a53..e56cd488a52 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'email_spec'
-describe Notify do
+RSpec.describe Notify do
include EmailSpec::Helpers
include EmailSpec::Matchers
include EmailHelpers
@@ -105,6 +105,7 @@ describe Notify do
it 'contains a link to issue author' do
is_expected.to have_body_text(issue.author_name)
is_expected.to have_body_text 'created an issue'
+ is_expected.to have_link(issue.to_reference, href: project_issue_url(issue.project, issue))
end
it 'contains a link to the issue' do
@@ -467,6 +468,7 @@ describe Notify do
is_expected.to have_body_text(status)
is_expected.to have_body_text(current_user_sanitized)
is_expected.to have_body_text(project_merge_request_path(project, merge_request))
+ is_expected.to have_link(merge_request.to_reference, href: project_merge_request_url(merge_request.target_project, merge_request))
end
end
end
@@ -497,6 +499,7 @@ describe Notify do
is_expected.to have_referable_subject(merge_request, reply: true)
is_expected.to have_body_text('merged')
is_expected.to have_body_text(project_merge_request_path(project, merge_request))
+ is_expected.to have_link(merge_request.to_reference, href: project_merge_request_url(merge_request.target_project, merge_request))
end
end
end
@@ -534,6 +537,7 @@ describe Notify do
is_expected.to have_referable_subject(merge_request, reply: true)
is_expected.to have_body_text(project_merge_request_path(project, merge_request))
is_expected.to have_body_text('due to conflict.')
+ is_expected.to have_link(merge_request.to_reference, href: project_merge_request_url(merge_request.target_project, merge_request))
end
end
end
@@ -567,6 +571,7 @@ describe Notify do
is_expected.to have_referable_subject(merge_request, reply: true)
is_expected.to have_body_text("#{push_user.name} pushed new commits")
is_expected.to have_body_text(project_merge_request_path(project, merge_request))
+ is_expected.to have_link(merge_request.to_reference, href: project_merge_request_url(merge_request.target_project, merge_request))
end
end
end
@@ -1175,9 +1180,7 @@ describe Notify do
context 'when note is not on text' do
before do
- allow_next_instance_of(DiffDiscussion) do |instance|
- allow(instance).to receive(:on_text?).and_return(false)
- end
+ allow(note.discussion).to receive(:on_text?).and_return(false)
end
it 'does not include diffs with character-level highlighting' do
@@ -1248,6 +1251,78 @@ describe Notify do
it_behaves_like 'appearance header and footer not enabled'
end
end
+
+ context 'for service desk issues' do
+ before do
+ issue.update!(service_desk_reply_to: 'service.desk@example.com')
+ end
+
+ def expect_sender(username)
+ sender = subject.header[:from].addrs[0]
+ expect(sender.display_name).to eq(username)
+ expect(sender.address).to eq(gitlab_sender)
+ end
+
+ describe 'thank you email' do
+ subject { described_class.service_desk_thank_you_email(issue.id) }
+
+ it_behaves_like 'an unsubscribeable thread'
+
+ it 'has the correct recipient' do
+ is_expected.to deliver_to('service.desk@example.com')
+ end
+
+ it 'has the correct subject and body' do
+ aggregate_failures do
+ is_expected.to have_referable_subject(issue, include_project: false, reply: true)
+ is_expected.to have_body_text("Thank you for your support request! We are tracking your request as ticket #{issue.to_reference}, and will respond as soon as we can.")
+ end
+ end
+
+ it 'uses service bot name by default' do
+ expect_sender(User.support_bot.name)
+ end
+
+ context 'when custom outgoing name is set' do
+ let_it_be(:settings) { create(:service_desk_setting, project: project, outgoing_name: 'some custom name') }
+
+ it 'uses custom name in "from" header' do
+ expect_sender('some custom name')
+ end
+ end
+
+ context 'when custom outgoing name is empty' do
+ let_it_be(:settings) { create(:service_desk_setting, project: project, outgoing_name: '') }
+
+ it 'uses service bot name' do
+ expect_sender(User.support_bot.name)
+ end
+ end
+ end
+
+ describe 'new note email' do
+ let_it_be(:first_note) { create(:discussion_note_on_issue, note: 'Hello world') }
+
+ subject { described_class.service_desk_new_note_email(issue.id, first_note.id) }
+
+ it_behaves_like 'an unsubscribeable thread'
+
+ it 'has the correct recipient' do
+ is_expected.to deliver_to('service.desk@example.com')
+ end
+
+ it 'uses author\'s name in "from" header' do
+ expect_sender(first_note.author.name)
+ end
+
+ it 'has the correct subject and body' do
+ aggregate_failures do
+ is_expected.to have_referable_subject(issue, include_project: false, reply: true)
+ is_expected.to have_body_text(first_note.note)
+ end
+ end
+ end
+ end
end
context 'for a group' do
diff --git a/spec/mailers/repository_check_mailer_spec.rb b/spec/mailers/repository_check_mailer_spec.rb
index 1fd4d28ca53..8b1bc33d8be 100644
--- a/spec/mailers/repository_check_mailer_spec.rb
+++ b/spec/mailers/repository_check_mailer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RepositoryCheckMailer do
+RSpec.describe RepositoryCheckMailer do
include EmailSpec::Matchers
describe '.notify' do
diff --git a/spec/migrations/20190924152703_migrate_issue_trackers_data_spec.rb b/spec/migrations/20190924152703_migrate_issue_trackers_data_spec.rb
index dfa4cc21d63..750d3d1fa30 100644
--- a/spec/migrations/20190924152703_migrate_issue_trackers_data_spec.rb
+++ b/spec/migrations/20190924152703_migrate_issue_trackers_data_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190924152703_migrate_issue_trackers_data.rb')
-describe MigrateIssueTrackersData do
+RSpec.describe MigrateIssueTrackersData do
let(:services) { table(:services) }
let(:migration_class) { Gitlab::BackgroundMigration::MigrateIssueTrackersSensitiveData }
let(:migration_name) { migration_class.to_s.demodulize }
diff --git a/spec/migrations/20191015154408_drop_merge_requests_require_code_owner_approval_from_projects_spec.rb b/spec/migrations/20191015154408_drop_merge_requests_require_code_owner_approval_from_projects_spec.rb
index ac9ff5632eb..e2eacc00e5a 100644
--- a/spec/migrations/20191015154408_drop_merge_requests_require_code_owner_approval_from_projects_spec.rb
+++ b/spec/migrations/20191015154408_drop_merge_requests_require_code_owner_approval_from_projects_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20191015154408_drop_merge_requests_require_code_owner_approval_from_projects.rb')
-describe DropMergeRequestsRequireCodeOwnerApprovalFromProjects do
+RSpec.describe DropMergeRequestsRequireCodeOwnerApprovalFromProjects do
let(:projects_table) { table(:projects) }
subject(:migration) { described_class.new }
diff --git a/spec/migrations/20191125114345_add_admin_mode_protected_path_spec.rb b/spec/migrations/20191125114345_add_admin_mode_protected_path_spec.rb
index 77d8dd002e3..51f798220c3 100644
--- a/spec/migrations/20191125114345_add_admin_mode_protected_path_spec.rb
+++ b/spec/migrations/20191125114345_add_admin_mode_protected_path_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20191125114345_add_admin_mode_protected_path.rb')
-describe AddAdminModeProtectedPath do
+RSpec.describe AddAdminModeProtectedPath do
subject(:migration) { described_class.new }
let(:admin_mode_endpoint) { '/admin/session' }
diff --git a/spec/migrations/20191204114127_delete_legacy_triggers_spec.rb b/spec/migrations/20191204114127_delete_legacy_triggers_spec.rb
index 58061d80f21..07c69872497 100644
--- a/spec/migrations/20191204114127_delete_legacy_triggers_spec.rb
+++ b/spec/migrations/20191204114127_delete_legacy_triggers_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20191204114127_delete_legacy_triggers.rb')
-describe DeleteLegacyTriggers, schema: 2019_11_25_140458 do
+RSpec.describe DeleteLegacyTriggers, schema: 2019_11_25_140458 do
let(:ci_trigger_table) { table(:ci_triggers) }
let(:user) { table(:users).create!(name: 'test', email: 'test@example.com', projects_limit: 1) }
diff --git a/spec/migrations/20200107172020_add_timestamp_softwarelicensespolicy_spec.rb b/spec/migrations/20200107172020_add_timestamp_softwarelicensespolicy_spec.rb
index 7a6b21d485b..1377d5e6ba0 100644
--- a/spec/migrations/20200107172020_add_timestamp_softwarelicensespolicy_spec.rb
+++ b/spec/migrations/20200107172020_add_timestamp_softwarelicensespolicy_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200107172020_add_timestamp_softwarelicensespolicy.rb')
-describe AddTimestampSoftwarelicensespolicy do
+RSpec.describe AddTimestampSoftwarelicensespolicy do
let(:software_licenses_policy) { table(:software_license_policies) }
let(:projects) { table(:projects) }
let(:licenses) { table(:software_licenses) }
diff --git a/spec/migrations/20200122123016_backfill_project_settings_spec.rb b/spec/migrations/20200122123016_backfill_project_settings_spec.rb
index ce86e94b6d5..0992ddde0c1 100644
--- a/spec/migrations/20200122123016_backfill_project_settings_spec.rb
+++ b/spec/migrations/20200122123016_backfill_project_settings_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200122123016_backfill_project_settings.rb')
-describe BackfillProjectSettings, :sidekiq, schema: 20200114113341 do
+RSpec.describe BackfillProjectSettings, :sidekiq, schema: 20200114113341 do
let(:projects) { table(:projects) }
let(:namespace) { table(:namespaces).create(name: 'user', path: 'user') }
let(:project) { projects.create(namespace_id: namespace.id) }
diff --git a/spec/migrations/20200123155929_remove_invalid_jira_data_spec.rb b/spec/migrations/20200123155929_remove_invalid_jira_data_spec.rb
index 253e39c1647..cae8e858af9 100644
--- a/spec/migrations/20200123155929_remove_invalid_jira_data_spec.rb
+++ b/spec/migrations/20200123155929_remove_invalid_jira_data_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200123155929_remove_invalid_jira_data.rb')
-describe RemoveInvalidJiraData do
+RSpec.describe RemoveInvalidJiraData do
let(:jira_tracker_data) { table(:jira_tracker_data) }
let(:services) { table(:services) }
diff --git a/spec/migrations/20200127090233_remove_invalid_issue_tracker_data_spec.rb b/spec/migrations/20200127090233_remove_invalid_issue_tracker_data_spec.rb
index ca7cde08071..a86b5d83a0b 100644
--- a/spec/migrations/20200127090233_remove_invalid_issue_tracker_data_spec.rb
+++ b/spec/migrations/20200127090233_remove_invalid_issue_tracker_data_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200127090233_remove_invalid_issue_tracker_data.rb')
-describe RemoveInvalidIssueTrackerData do
+RSpec.describe RemoveInvalidIssueTrackerData do
let(:issue_tracker_data) { table(:issue_tracker_data) }
let(:services) { table(:services) }
diff --git a/spec/migrations/20200130145430_reschedule_migrate_issue_trackers_data_spec.rb b/spec/migrations/20200130145430_reschedule_migrate_issue_trackers_data_spec.rb
index 8e3e55f3e19..146302ba650 100644
--- a/spec/migrations/20200130145430_reschedule_migrate_issue_trackers_data_spec.rb
+++ b/spec/migrations/20200130145430_reschedule_migrate_issue_trackers_data_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200130145430_reschedule_migrate_issue_trackers_data.rb')
-describe RescheduleMigrateIssueTrackersData do
+RSpec.describe RescheduleMigrateIssueTrackersData do
let(:services) { table(:services) }
let(:migration_class) { Gitlab::BackgroundMigration::MigrateIssueTrackersSensitiveData }
let(:migration_name) { migration_class.to_s.demodulize }
diff --git a/spec/migrations/20200313203550_remove_orphaned_chat_names_spec.rb b/spec/migrations/20200313203550_remove_orphaned_chat_names_spec.rb
index fd30ebaa66f..d9ce62fe475 100644
--- a/spec/migrations/20200313203550_remove_orphaned_chat_names_spec.rb
+++ b/spec/migrations/20200313203550_remove_orphaned_chat_names_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200313203550_remove_orphaned_chat_names.rb')
-describe RemoveOrphanedChatNames, schema: 20200313202430 do
+RSpec.describe RemoveOrphanedChatNames, schema: 20200313202430 do
let(:projects) { table(:projects) }
let(:namespaces) { table(:namespaces) }
let(:services) { table(:services) }
diff --git a/spec/migrations/20200406102120_backfill_deployment_clusters_from_deployments_spec.rb b/spec/migrations/20200406102120_backfill_deployment_clusters_from_deployments_spec.rb
index fcb253677e1..47bc7428286 100644
--- a/spec/migrations/20200406102120_backfill_deployment_clusters_from_deployments_spec.rb
+++ b/spec/migrations/20200406102120_backfill_deployment_clusters_from_deployments_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200406102120_backfill_deployment_clusters_from_deployments.rb')
-describe BackfillDeploymentClustersFromDeployments, :migration, :sidekiq, schema: 20200227140242 do
+RSpec.describe BackfillDeploymentClustersFromDeployments, :migration, :sidekiq, schema: 20200227140242 do
describe '#up' do
it 'schedules BackfillDeploymentClustersFromDeployments background jobs' do
stub_const("#{described_class}::BATCH_SIZE", 2)
diff --git a/spec/migrations/20200511145545_change_variable_interpolation_format_in_common_metrics_spec.rb b/spec/migrations/20200511145545_change_variable_interpolation_format_in_common_metrics_spec.rb
index f9e8a7ee6e9..e42a448a01e 100644
--- a/spec/migrations/20200511145545_change_variable_interpolation_format_in_common_metrics_spec.rb
+++ b/spec/migrations/20200511145545_change_variable_interpolation_format_in_common_metrics_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200511145545_change_variable_interpolation_format_in_common_metrics')
-describe ChangeVariableInterpolationFormatInCommonMetrics, :migration do
+RSpec.describe ChangeVariableInterpolationFormatInCommonMetrics, :migration do
let(:prometheus_metrics) { table(:prometheus_metrics) }
let!(:common_metric) do
@@ -23,9 +23,14 @@ describe ChangeVariableInterpolationFormatInCommonMetrics, :migration do
end
it 'updates query to use {{}}' do
- expected_query = 'avg(sum(container_memory_usage_bytes{container_name!="POD",' \
- 'pod_name=~"^{{ci_environment_slug}}-(.*)",namespace="{{kube_namespace}}"})' \
- ' by (job)) without (job) /1024/1024/1024'
+ expected_query = <<~EOS.chomp
+ avg(sum(container_memory_usage_bytes{container!="POD",\
+ pod=~"^{{ci_environment_slug}}-(.*)",namespace="{{kube_namespace}}"}) \
+ by (job)) without (job) /1024/1024/1024 OR \
+ avg(sum(container_memory_usage_bytes{container_name!="POD",\
+ pod_name=~"^{{ci_environment_slug}}-(.*)",namespace="{{kube_namespace}}"}) \
+ by (job)) without (job) /1024/1024/1024
+ EOS
migrate!
diff --git a/spec/migrations/20200526115436_dedup_mr_metrics_spec.rb b/spec/migrations/20200526115436_dedup_mr_metrics_spec.rb
new file mode 100644
index 00000000000..f2698a0f352
--- /dev/null
+++ b/spec/migrations/20200526115436_dedup_mr_metrics_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200526115436_dedup_mr_metrics')
+
+RSpec.describe DedupMrMetrics, :migration, schema: 20200526013844 do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:merge_requests) { table(:merge_requests) }
+ let(:metrics) { table(:merge_request_metrics) }
+ let(:merge_request_params) { { source_branch: 'x', target_branch: 'y', target_project_id: project.id } }
+
+ let!(:namespace) { namespaces.create(name: 'foo', path: 'foo') }
+ let!(:project) { projects.create!(namespace_id: namespace.id) }
+ let!(:merge_request_1) { merge_requests.create!(merge_request_params) }
+ let!(:merge_request_2) { merge_requests.create!(merge_request_params) }
+ let!(:merge_request_3) { merge_requests.create!(merge_request_params) }
+
+ let!(:duplicated_metrics_1) { metrics.create(merge_request_id: merge_request_1.id, latest_build_started_at: 1.day.ago, first_deployed_to_production_at: 5.days.ago, updated_at: 2.months.ago) }
+ let!(:duplicated_metrics_2) { metrics.create(merge_request_id: merge_request_1.id, latest_build_started_at: Time.now, merged_at: Time.now, updated_at: 1.month.ago) }
+
+ let!(:duplicated_metrics_3) { metrics.create(merge_request_id: merge_request_3.id, diff_size: 30, commits_count: 20, updated_at: 2.months.ago) }
+ let!(:duplicated_metrics_4) { metrics.create(merge_request_id: merge_request_3.id, added_lines: 5, commits_count: nil, updated_at: 1.month.ago) }
+
+ let!(:non_duplicated_metrics) { metrics.create(merge_request_id: merge_request_2.id, latest_build_started_at: 2.days.ago) }
+
+ it 'deduplicates merge_request_metrics table' do
+ expect { migrate! }.to change { metrics.count }.from(5).to(3)
+ end
+
+ it 'merges `duplicated_metrics_1` with `duplicated_metrics_2`' do
+ migrate!
+
+ expect(metrics.where(id: duplicated_metrics_1.id)).not_to exist
+
+ merged_metrics = metrics.find_by(id: duplicated_metrics_2.id)
+
+ expect(merged_metrics).to be_present
+ expect(merged_metrics.latest_build_started_at).to be_like_time(duplicated_metrics_2.latest_build_started_at)
+ expect(merged_metrics.merged_at).to be_like_time(duplicated_metrics_2.merged_at)
+ expect(merged_metrics.first_deployed_to_production_at).to be_like_time(duplicated_metrics_1.first_deployed_to_production_at)
+ end
+
+ it 'merges `duplicated_metrics_3` with `duplicated_metrics_4`' do
+ migrate!
+
+ expect(metrics.where(id: duplicated_metrics_3.id)).not_to exist
+
+ merged_metrics = metrics.find_by(id: duplicated_metrics_4.id)
+
+ expect(merged_metrics).to be_present
+ expect(merged_metrics.diff_size).to eq(duplicated_metrics_3.diff_size)
+ expect(merged_metrics.commits_count).to eq(duplicated_metrics_3.commits_count)
+ expect(merged_metrics.added_lines).to eq(duplicated_metrics_4.added_lines)
+ end
+
+ it 'does not change non duplicated records' do
+ expect { migrate! }.not_to change { non_duplicated_metrics.reload.attributes }
+ end
+
+ it 'does nothing when there are no metrics' do
+ metrics.delete_all
+
+ migrate!
+
+ expect(metrics.count).to eq(0)
+ end
+end
diff --git a/spec/migrations/20200526231421_update_index_approval_rule_name_for_code_owners_rule_type_spec.rb b/spec/migrations/20200526231421_update_index_approval_rule_name_for_code_owners_rule_type_spec.rb
new file mode 100644
index 00000000000..9f26b698158
--- /dev/null
+++ b/spec/migrations/20200526231421_update_index_approval_rule_name_for_code_owners_rule_type_spec.rb
@@ -0,0 +1,175 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20200526231421_update_index_approval_rule_name_for_code_owners_rule_type.rb')
+
+RSpec.describe UpdateIndexApprovalRuleNameForCodeOwnersRuleType do
+ let(:migration) { described_class.new }
+
+ let(:approval_rules) { table(:approval_merge_request_rules) }
+ let(:namespace) { table(:namespaces).create!(name: 'gitlab', path: 'gitlab') }
+
+ let(:project) do
+ table(:projects).create!(
+ namespace_id: namespace.id,
+ name: 'gitlab',
+ path: 'gitlab'
+ )
+ end
+
+ let(:merge_request) do
+ table(:merge_requests).create!(
+ target_project_id: project.id,
+ source_project_id: project.id,
+ target_branch: 'feature',
+ source_branch: 'master'
+ )
+ end
+
+ let(:index_names) do
+ ActiveRecord::Base.connection
+ .indexes(:approval_merge_request_rules)
+ .collect(&:name)
+ end
+
+ def create_sectional_approval_rules
+ approval_rules.create!(
+ merge_request_id: merge_request.id,
+ name: "*.rb",
+ code_owner: true,
+ rule_type: 2,
+ section: "First Section"
+ )
+
+ approval_rules.create!(
+ merge_request_id: merge_request.id,
+ name: "*.rb",
+ code_owner: true,
+ rule_type: 2,
+ section: "Second Section"
+ )
+ end
+
+ def create_two_matching_nil_section_approval_rules
+ 2.times do
+ approval_rules.create!(
+ merge_request_id: merge_request.id,
+ name: "nil_section",
+ code_owner: true,
+ rule_type: 2
+ )
+ end
+ end
+
+ before do
+ approval_rules.delete_all
+ end
+
+ describe "#up" do
+ it "creates the new index and removes the 'legacy' indices" do
+ # Confirm that existing legacy indices prevent duplicate entries
+ #
+ expect { create_sectional_approval_rules }
+ .to raise_exception(ActiveRecord::RecordNotUnique)
+ expect { create_two_matching_nil_section_approval_rules }
+ .to raise_exception(ActiveRecord::RecordNotUnique)
+
+ approval_rules.delete_all
+
+ disable_migrations_output { migrate! }
+
+ # After running the migration, expect `section == nil` rules to still be
+ # blocked by the legacy indices, but sectional rules are allowed.
+ #
+ expect { create_sectional_approval_rules }
+ .to change { approval_rules.count }.by(2)
+ expect { create_two_matching_nil_section_approval_rules }
+ .to raise_exception(ActiveRecord::RecordNotUnique)
+
+ # Attempt to rerun the creation of sectional rules, and see that sectional
+ # rules are unique by section
+ #
+ expect { create_sectional_approval_rules }
+ .to raise_exception(ActiveRecord::RecordNotUnique)
+
+ expect(index_names).to include(
+ described_class::SECTIONAL_INDEX_NAME,
+ described_class::LEGACY_INDEX_NAME_RULE_TYPE,
+ described_class::LEGACY_INDEX_NAME_CODE_OWNERS
+ )
+ end
+ end
+
+ describe "#down" do
+ context "run as FOSS" do
+ before do
+ expect(Gitlab).to receive(:ee?).twice.and_return(false)
+ end
+
+ it "recreates legacy indices, but does not invoke EE-specific code" do
+ disable_migrations_output { migrate! }
+
+ expect(index_names).to include(
+ described_class::SECTIONAL_INDEX_NAME,
+ described_class::LEGACY_INDEX_NAME_RULE_TYPE,
+ described_class::LEGACY_INDEX_NAME_CODE_OWNERS
+ )
+
+ # Since ApprovalMergeRequestRules are EE-specific, we expect none to be
+ # deleted during the migration.
+ #
+ expect { disable_migrations_output { migration.down } }
+ .not_to change { approval_rules.count }
+
+ index_names = ActiveRecord::Base.connection
+ .indexes(:approval_merge_request_rules)
+ .collect(&:name)
+
+ expect(index_names).not_to include(described_class::SECTIONAL_INDEX_NAME)
+ expect(index_names).to include(
+ described_class::LEGACY_INDEX_NAME_RULE_TYPE,
+ described_class::LEGACY_INDEX_NAME_CODE_OWNERS
+ )
+ end
+ end
+
+ context "EE" do
+ it "recreates 'legacy' indices and removes duplicate code owner approval rules" do
+ skip("This test is skipped under FOSS") unless Gitlab.ee?
+
+ disable_migrations_output { migrate! }
+
+ expect { create_sectional_approval_rules }
+ .to change { approval_rules.count }.by(2)
+ expect { create_two_matching_nil_section_approval_rules }
+ .to raise_exception(ActiveRecord::RecordNotUnique)
+
+ expect(MergeRequests::SyncCodeOwnerApprovalRules)
+ .to receive(:new).with(MergeRequest.find(merge_request.id)).once.and_call_original
+
+ # Run the down migration. This will remove the 3 approval rules we create
+ # above, and call MergeRequests::SyncCodeOwnerApprovalRules to recreate
+ # new ones. However, as there is no CODEOWNERS file in this test
+ # context, no approval rules will be created, so we can expect
+ # approval_rules.count to be changed by -3.
+ #
+ expect { disable_migrations_output { migration.down } }
+ .to change { approval_rules.count }.by(-3)
+
+ # Test that the index does not allow us to create the same rules as the
+ # previous sectional index.
+ #
+ expect { create_sectional_approval_rules }
+ .to raise_exception(ActiveRecord::RecordNotUnique)
+ expect { create_two_matching_nil_section_approval_rules }
+ .to raise_exception(ActiveRecord::RecordNotUnique)
+
+ expect(index_names).not_to include(described_class::SECTIONAL_INDEX_NAME)
+ expect(index_names).to include(
+ described_class::LEGACY_INDEX_NAME_RULE_TYPE,
+ described_class::LEGACY_INDEX_NAME_CODE_OWNERS
+ )
+ end
+ end
+ end
+end
diff --git a/spec/migrations/20200703125016_backfill_namespace_settings_spec.rb b/spec/migrations/20200703125016_backfill_namespace_settings_spec.rb
new file mode 100644
index 00000000000..7b84ef9e236
--- /dev/null
+++ b/spec/migrations/20200703125016_backfill_namespace_settings_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200703125016_backfill_namespace_settings.rb')
+
+RSpec.describe BackfillNamespaceSettings, :sidekiq, schema: 20200703124823 do
+ let(:namespaces) { table(:namespaces) }
+
+ describe '#up' do
+ before do
+ stub_const("#{described_class}::BATCH_SIZE", 2)
+
+ namespaces.create!(id: 1, name: 'test1', path: 'test1')
+ namespaces.create!(id: 2, name: 'test2', path: 'test2')
+ namespaces.create!(id: 3, name: 'test3', path: 'test3')
+ end
+
+ it 'schedules BackfillNamespaceSettings background jobs' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(2.minutes, 1, 2)
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(4.minutes, 3, 3)
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/migrations/20200706035141_adjust_unique_index_alert_management_alerts_spec.rb b/spec/migrations/20200706035141_adjust_unique_index_alert_management_alerts_spec.rb
new file mode 100644
index 00000000000..0068571ad0d
--- /dev/null
+++ b/spec/migrations/20200706035141_adjust_unique_index_alert_management_alerts_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20200706035141_adjust_unique_index_alert_management_alerts.rb')
+
+RSpec.describe AdjustUniqueIndexAlertManagementAlerts, :migration do
+ let(:migration) { described_class.new }
+ let(:alerts) { AlertManagement::Alert }
+ let(:project) { create_project }
+ let(:other_project) { create_project }
+ let(:resolved_state) { 2 }
+ let(:triggered_state) { 1 }
+ let!(:existing_alert) { create_alert(project, resolved_state, '1234', 1) }
+ let!(:p2_alert) { create_alert(other_project, resolved_state, '1234', 1) }
+ let!(:p2_alert_diff_fingerprint) { create_alert(other_project, resolved_state, '4567', 2) }
+
+ it 'can reverse the migration' do
+ expect(existing_alert.fingerprint).not_to eq(nil)
+ expect(p2_alert.fingerprint).not_to eq(nil)
+ expect(p2_alert_diff_fingerprint.fingerprint).not_to eq(nil)
+
+ migrate!
+
+ # Adding a second alert with the same fingerprint now that we can
+ second_alert = create_alert(project, triggered_state, '1234', 2)
+ expect(alerts.count).to eq(4)
+
+ schema_migrate_down!
+
+ # We keep the alerts, but the oldest ones fingerprint is removed
+ expect(alerts.count).to eq(4)
+ expect(second_alert.reload.fingerprint).not_to eq(nil)
+ expect(p2_alert.fingerprint).not_to eq(nil)
+ expect(p2_alert_diff_fingerprint.fingerprint).not_to eq(nil)
+ expect(existing_alert.reload.fingerprint).to eq(nil)
+ end
+
+ def namespace
+ @namespace ||= table(:namespaces).create!(name: 'foo', path: 'foo')
+ end
+
+ def create_project
+ table(:projects).create!(namespace_id: namespace.id)
+ end
+
+ def create_alert(project, status, fingerprint, iid)
+ params = {
+ title: 'test',
+ started_at: Time.current,
+ iid: iid,
+ project_id: project.id,
+ status: status,
+ fingerprint: fingerprint
+ }
+ table(:alert_management_alerts).create!(params)
+ end
+end
diff --git a/spec/migrations/active_record/schema_spec.rb b/spec/migrations/active_record/schema_spec.rb
index a3348424f47..8199f55f5fc 100644
--- a/spec/migrations/active_record/schema_spec.rb
+++ b/spec/migrations/active_record/schema_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
# Check consistency of db/structure.sql version, migrations' timestamps, and the latest migration timestamp
# stored in the database's schema_migrations table.
-describe ActiveRecord::Schema, schema: :latest do
+RSpec.describe ActiveRecord::Schema, schema: :latest do
let(:all_migrations) do
migrations_paths = %w[db/migrate db/post_migrate]
.map { |path| Rails.root.join(*path, '*') }
@@ -25,6 +25,6 @@ describe ActiveRecord::Schema, schema: :latest do
it 'the schema_migrations table contains all schema versions' do
versions = ActiveRecord::Base.connection.execute('SELECT version FROM schema_migrations ORDER BY version').map { |m| Integer(m['version']) }
- expect(versions).to eq(all_migrations)
+ expect(versions).to match_array(all_migrations)
end
end
diff --git a/spec/migrations/add_default_and_free_plans_spec.rb b/spec/migrations/add_default_and_free_plans_spec.rb
index dffdeb8e71a..75787896999 100644
--- a/spec/migrations/add_default_and_free_plans_spec.rb
+++ b/spec/migrations/add_default_and_free_plans_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20191023152913_add_default_and_free_plans.rb')
-describe AddDefaultAndFreePlans do
+RSpec.describe AddDefaultAndFreePlans do
describe 'migrate' do
let(:plans) { table(:plans) }
diff --git a/spec/migrations/add_default_value_stream_to_groups_with_group_stages_spec.rb b/spec/migrations/add_default_value_stream_to_groups_with_group_stages_spec.rb
new file mode 100644
index 00000000000..31dee29a39b
--- /dev/null
+++ b/spec/migrations/add_default_value_stream_to_groups_with_group_stages_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200701070435_add_default_value_stream_to_groups_with_group_stages.rb')
+
+RSpec.describe AddDefaultValueStreamToGroupsWithGroupStages, schema: 20200624142207 do
+ let(:groups) { table(:namespaces) }
+ let(:group_stages) { table(:analytics_cycle_analytics_group_stages) }
+ let(:value_streams) { table(:analytics_cycle_analytics_group_value_streams) }
+
+ let!(:group) { groups.create!(name: 'test', path: 'path', type: 'Group') }
+ let!(:group_stage) { group_stages.create!(name: 'test', group_id: group.id, start_event_identifier: 1, end_event_identifier: 2) }
+
+ describe '#up' do
+ it 'creates default value stream record for the group' do
+ migrate!
+
+ group_value_streams = value_streams.where(group_id: group.id)
+ expect(group_value_streams.size).to eq(1)
+
+ value_stream = group_value_streams.first
+ expect(value_stream.name).to eq('default')
+ end
+
+ it 'migrates existing stages to the default value stream' do
+ migrate!
+
+ group_stage.reload
+
+ value_stream = value_streams.find_by(group_id: group.id, name: 'default')
+ expect(group_stage.group_value_stream_id).to eq(value_stream.id)
+ end
+ end
+
+ describe '#down' do
+ it 'sets the group_value_stream_id to nil' do
+ described_class.new.down
+
+ group_stage.reload
+
+ expect(group_stage.group_value_stream_id).to be_nil
+ end
+ end
+end
diff --git a/spec/migrations/add_deploy_token_type_to_deploy_tokens_spec.rb b/spec/migrations/add_deploy_token_type_to_deploy_tokens_spec.rb
index 3bc3d3f8ee2..f1fe27e72a1 100644
--- a/spec/migrations/add_deploy_token_type_to_deploy_tokens_spec.rb
+++ b/spec/migrations/add_deploy_token_type_to_deploy_tokens_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200122161638_add_deploy_token_type_to_deploy_tokens.rb')
-describe AddDeployTokenTypeToDeployTokens do
+RSpec.describe AddDeployTokenTypeToDeployTokens do
let(:deploy_tokens) { table(:deploy_tokens) }
let(:deploy_token) do
deploy_tokens.create(name: 'token_test',
diff --git a/spec/migrations/add_incident_settings_to_all_existing_projects_spec.rb b/spec/migrations/add_incident_settings_to_all_existing_projects_spec.rb
index 507b1a8d580..dab42c0ffc3 100644
--- a/spec/migrations/add_incident_settings_to_all_existing_projects_spec.rb
+++ b/spec/migrations/add_incident_settings_to_all_existing_projects_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200609212701_add_incident_settings_to_all_existing_projects.rb')
-describe AddIncidentSettingsToAllExistingProjects, :migration do
+RSpec.describe AddIncidentSettingsToAllExistingProjects, :migration do
let(:project_incident_management_settings) { table(:project_incident_management_settings) }
let(:labels) { table(:labels) }
let(:label_links) { table(:label_links) }
@@ -67,7 +67,7 @@ describe AddIncidentSettingsToAllExistingProjects, :migration do
context 'when project has incident labels' do
before do
issue = issues.create!(project_id: project.id)
- incident_label_attrs = IncidentManagement::CreateIssueService::INCIDENT_LABEL
+ incident_label_attrs = IncidentManagement::CreateIncidentLabelService::LABEL_PROPERTIES
incident_label = labels.create!(project_id: project.id, **incident_label_attrs)
label_links.create!(target_id: issue.id, label_id: incident_label.id, target_type: 'Issue')
end
diff --git a/spec/migrations/add_temporary_partial_index_on_project_id_to_services_spec.rb b/spec/migrations/add_temporary_partial_index_on_project_id_to_services_spec.rb
index ce790b0266c..1be699f5636 100644
--- a/spec/migrations/add_temporary_partial_index_on_project_id_to_services_spec.rb
+++ b/spec/migrations/add_temporary_partial_index_on_project_id_to_services_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200114112932_add_temporary_partial_index_on_project_id_to_services.rb')
-describe AddTemporaryPartialIndexOnProjectIdToServices do
+RSpec.describe AddTemporaryPartialIndexOnProjectIdToServices do
let(:migration) { described_class.new }
describe '#up' do
diff --git a/spec/migrations/add_unique_constraint_to_approvals_user_id_and_merge_request_id_spec.rb b/spec/migrations/add_unique_constraint_to_approvals_user_id_and_merge_request_id_spec.rb
index 74830ab4ce2..6c04be5fc60 100644
--- a/spec/migrations/add_unique_constraint_to_approvals_user_id_and_merge_request_id_spec.rb
+++ b/spec/migrations/add_unique_constraint_to_approvals_user_id_and_merge_request_id_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190404143330_add_unique_constraint_to_approvals_user_id_and_merge_request_id.rb')
-describe AddUniqueConstraintToApprovalsUserIdAndMergeRequestId do
+RSpec.describe AddUniqueConstraintToApprovalsUserIdAndMergeRequestId do
let(:migration) { described_class.new }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
diff --git a/spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb b/spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb
index 8fceba276ce..9d0ad5863c6 100644
--- a/spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb
+++ b/spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20190628185004_backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table.rb')
-describe BackfillAndAddNotNullConstraintToReleasedAtColumnOnReleasesTable do
+RSpec.describe BackfillAndAddNotNullConstraintToReleasedAtColumnOnReleasesTable do
let(:releases) { table(:releases) }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
diff --git a/spec/migrations/backfill_imported_snippet_repositories_spec.rb b/spec/migrations/backfill_imported_snippet_repositories_spec.rb
index c77978b23e4..208bda274e2 100644
--- a/spec/migrations/backfill_imported_snippet_repositories_spec.rb
+++ b/spec/migrations/backfill_imported_snippet_repositories_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200608072931_backfill_imported_snippet_repositories.rb')
-describe BackfillImportedSnippetRepositories do
+RSpec.describe BackfillImportedSnippetRepositories do
let(:users) { table(:users) }
let(:snippets) { table(:snippets) }
let(:user) { users.create(id: 1, email: 'user@example.com', projects_limit: 10, username: 'test', name: 'Test', state: 'active') }
diff --git a/spec/migrations/backfill_operations_feature_flags_active_spec.rb b/spec/migrations/backfill_operations_feature_flags_active_spec.rb
index c51ed9fea8c..4ec2a4a2a92 100644
--- a/spec/migrations/backfill_operations_feature_flags_active_spec.rb
+++ b/spec/migrations/backfill_operations_feature_flags_active_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20191213184609_backfill_operations_feature_flags_active.rb')
-describe BackfillOperationsFeatureFlagsActive do
+RSpec.describe BackfillOperationsFeatureFlagsActive do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:flags) { table(:operations_feature_flags) }
diff --git a/spec/migrations/backfill_operations_feature_flags_iid_spec.rb b/spec/migrations/backfill_operations_feature_flags_iid_spec.rb
index 4628780787c..bafe5830652 100644
--- a/spec/migrations/backfill_operations_feature_flags_iid_spec.rb
+++ b/spec/migrations/backfill_operations_feature_flags_iid_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200117194850_backfill_operations_feature_flags_iid.rb')
-describe BackfillOperationsFeatureFlagsIid do
+RSpec.describe BackfillOperationsFeatureFlagsIid do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:flags) { table(:operations_feature_flags) }
diff --git a/spec/migrations/backfill_releases_table_updated_at_and_add_not_null_constraints_to_timestamps_spec.rb b/spec/migrations/backfill_releases_table_updated_at_and_add_not_null_constraints_to_timestamps_spec.rb
index bf9a8154e1e..4c3517bc574 100644
--- a/spec/migrations/backfill_releases_table_updated_at_and_add_not_null_constraints_to_timestamps_spec.rb
+++ b/spec/migrations/backfill_releases_table_updated_at_and_add_not_null_constraints_to_timestamps_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20190920194925_backfill_releases_table_updated_at_and_add_not_null_constraints_to_timestamps.rb')
-describe BackfillReleasesTableUpdatedAtAndAddNotNullConstraintsToTimestamps do
+RSpec.describe BackfillReleasesTableUpdatedAtAndAddNotNullConstraintsToTimestamps do
let(:releases) { table(:releases) }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
diff --git a/spec/migrations/backfill_snippet_repositories_spec.rb b/spec/migrations/backfill_snippet_repositories_spec.rb
index e87bf7376dd..084faa16a37 100644
--- a/spec/migrations/backfill_snippet_repositories_spec.rb
+++ b/spec/migrations/backfill_snippet_repositories_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200420094444_backfill_snippet_repositories.rb')
-describe BackfillSnippetRepositories do
+RSpec.describe BackfillSnippetRepositories do
let(:users) { table(:users) }
let(:snippets) { table(:snippets) }
let(:user) { users.create(id: 1, email: 'user@example.com', projects_limit: 10, username: 'test', name: 'Test', state: 'active') }
diff --git a/spec/migrations/backfill_status_page_published_incidents_spec.rb b/spec/migrations/backfill_status_page_published_incidents_spec.rb
index ccdc8be4168..2b1ab891038 100644
--- a/spec/migrations/backfill_status_page_published_incidents_spec.rb
+++ b/spec/migrations/backfill_status_page_published_incidents_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200421195234_backfill_status_page_published_incidents.rb')
-describe BackfillStatusPagePublishedIncidents, :migration do
+RSpec.describe BackfillStatusPagePublishedIncidents, :migration do
subject(:migration) { described_class.new }
describe '#up' do
diff --git a/spec/migrations/backport_enterprise_schema_spec.rb b/spec/migrations/backport_enterprise_schema_spec.rb
index c167301e1e3..b76b53dc259 100644
--- a/spec/migrations/backport_enterprise_schema_spec.rb
+++ b/spec/migrations/backport_enterprise_schema_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require Rails.root.join('db', 'migrate', '20190402150158_backport_enterprise_schema.rb')
-describe BackportEnterpriseSchema, schema: 20190329085614 do
+RSpec.describe BackportEnterpriseSchema, schema: 20190329085614 do
include MigrationsHelpers
def drop_if_exists(table)
diff --git a/spec/migrations/cap_designs_filename_length_to_new_limit_spec.rb b/spec/migrations/cap_designs_filename_length_to_new_limit_spec.rb
index daa07953cb5..523c8864b63 100644
--- a/spec/migrations/cap_designs_filename_length_to_new_limit_spec.rb
+++ b/spec/migrations/cap_designs_filename_length_to_new_limit_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200602013901_cap_designs_filename_length_to_new_limit')
-describe CapDesignsFilenameLengthToNewLimit, :migration, schema: 20200528125905 do
+RSpec.describe CapDesignsFilenameLengthToNewLimit, :migration, schema: 20200528125905 do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:issues) { table(:issues) }
diff --git a/spec/migrations/change_outbound_local_requests_whitelist_default_spec.rb b/spec/migrations/change_outbound_local_requests_whitelist_default_spec.rb
index dd45cac4a70..53b02425010 100644
--- a/spec/migrations/change_outbound_local_requests_whitelist_default_spec.rb
+++ b/spec/migrations/change_outbound_local_requests_whitelist_default_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20190725012225_change_outbound_local_requests_whitelist_default.rb')
-describe ChangeOutboundLocalRequestsWhitelistDefault do
+RSpec.describe ChangeOutboundLocalRequestsWhitelistDefault do
let(:application_settings) { table(:application_settings) }
it 'defaults to empty array' do
diff --git a/spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb b/spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb
index c36506643de..8614bfea7cb 100644
--- a/spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb
+++ b/spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20190516155724_change_packages_size_defaults_in_project_statistics.rb')
-describe ChangePackagesSizeDefaultsInProjectStatistics do
+RSpec.describe ChangePackagesSizeDefaultsInProjectStatistics do
let(:project_statistics) { table(:project_statistics) }
let(:projects) { table(:projects) }
diff --git a/spec/migrations/clean_grafana_url_spec.rb b/spec/migrations/clean_grafana_url_spec.rb
index f6ea88a6f8d..caaf44b884d 100644
--- a/spec/migrations/clean_grafana_url_spec.rb
+++ b/spec/migrations/clean_grafana_url_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200214085940_clean_grafana_url.rb')
-describe CleanGrafanaUrl do
+RSpec.describe CleanGrafanaUrl do
let(:application_settings_table) { table(:application_settings) }
[
diff --git a/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb b/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb
index 602e1c1fe93..531c1dbb76a 100644
--- a/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb
+++ b/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190313092516_clean_up_noteable_id_for_notes_on_commits.rb')
-describe CleanUpNoteableIdForNotesOnCommits do
+RSpec.describe CleanUpNoteableIdForNotesOnCommits do
let(:notes) { table(:notes) }
before do
diff --git a/spec/migrations/cleanup_empty_commit_user_mentions_spec.rb b/spec/migrations/cleanup_empty_commit_user_mentions_spec.rb
index 529fe046e32..da714e7da4c 100644
--- a/spec/migrations/cleanup_empty_commit_user_mentions_spec.rb
+++ b/spec/migrations/cleanup_empty_commit_user_mentions_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200128133510_cleanup_empty_commit_user_mentions')
-describe CleanupEmptyCommitUserMentions, :migration, :sidekiq do
+RSpec.describe CleanupEmptyCommitUserMentions, :migration, :sidekiq do
let(:users) { table(:users) }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
diff --git a/spec/migrations/cleanup_legacy_artifact_migration_spec.rb b/spec/migrations/cleanup_legacy_artifact_migration_spec.rb
index 29a5c34373c..83b88a0cf1a 100644
--- a/spec/migrations/cleanup_legacy_artifact_migration_spec.rb
+++ b/spec/migrations/cleanup_legacy_artifact_migration_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20190104182041_cleanup_legacy_artifact_migration.rb')
-describe CleanupLegacyArtifactMigration, :redis do
+RSpec.describe CleanupLegacyArtifactMigration, :redis do
let(:migration) { spy('migration') }
context 'when still legacy artifacts exist' do
diff --git a/spec/migrations/cleanup_optimistic_locking_nulls_pt2_fixed_spec.rb b/spec/migrations/cleanup_optimistic_locking_nulls_pt2_fixed_spec.rb
index 2e5e450afc7..7c7f87d4e06 100644
--- a/spec/migrations/cleanup_optimistic_locking_nulls_pt2_fixed_spec.rb
+++ b/spec/migrations/cleanup_optimistic_locking_nulls_pt2_fixed_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200427064130_cleanup_optimistic_locking_nulls_pt2_fixed.rb')
-describe CleanupOptimisticLockingNullsPt2Fixed, :migration do
+RSpec.describe CleanupOptimisticLockingNullsPt2Fixed, :migration, schema: 20200219193117 do
test_tables = %w(ci_stages ci_builds ci_pipelines).freeze
test_tables.each do |table|
let(table.to_sym) { table(table.to_sym) }
diff --git a/spec/migrations/cleanup_optimistic_locking_nulls_spec.rb b/spec/migrations/cleanup_optimistic_locking_nulls_spec.rb
index 6e541c903ff..bcdcd3e9273 100644
--- a/spec/migrations/cleanup_optimistic_locking_nulls_spec.rb
+++ b/spec/migrations/cleanup_optimistic_locking_nulls_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200128210353_cleanup_optimistic_locking_nulls')
-describe CleanupOptimisticLockingNulls do
+RSpec.describe CleanupOptimisticLockingNulls do
let(:epics) { table(:epics) }
let(:merge_requests) { table(:merge_requests) }
let(:issues) { table(:issues) }
diff --git a/spec/migrations/cleanup_projects_with_missing_namespace_spec.rb b/spec/migrations/cleanup_projects_with_missing_namespace_spec.rb
index 27c954d2984..cef6e0f470f 100644
--- a/spec/migrations/cleanup_projects_with_missing_namespace_spec.rb
+++ b/spec/migrations/cleanup_projects_with_missing_namespace_spec.rb
@@ -47,7 +47,7 @@ class SchemaVersionFinder
end
end
-describe CleanupProjectsWithMissingNamespace, :migration, schema: SchemaVersionFinder.previous_migration do
+RSpec.describe CleanupProjectsWithMissingNamespace, :migration, schema: SchemaVersionFinder.previous_migration do
let(:projects) { table(:projects) }
let(:namespaces) { table(:namespaces) }
let(:users) { table(:users) }
diff --git a/spec/migrations/confirm_project_bot_users_spec.rb b/spec/migrations/confirm_project_bot_users_spec.rb
new file mode 100644
index 00000000000..11aa08d16a9
--- /dev/null
+++ b/spec/migrations/confirm_project_bot_users_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200713071042_confirm_project_bot_users.rb')
+
+RSpec.describe ConfirmProjectBotUsers, :migration do
+ let(:users) { table(:users) }
+
+ context 'project bot users that are currently unconfirmed' do
+ let!(:project_bot_1) do
+ create_user!(
+ name: 'bot_1',
+ email: 'bot_1@example.com',
+ created_at: 2.days.ago,
+ user_type: described_class::User::USER_TYPE_PROJECT_BOT
+ )
+ end
+
+ let!(:project_bot_2) do
+ create_user!(
+ name: 'bot_2',
+ email: 'bot_2@example.com',
+ created_at: 4.days.ago,
+ user_type: described_class::User::USER_TYPE_PROJECT_BOT
+ )
+ end
+
+ it 'updates their `confirmed_at` attribute' do
+ expect { migrate! }
+ .to change { project_bot_1.reload.confirmed_at }
+ .and change { project_bot_2.reload.confirmed_at }
+ end
+
+ it 'sets `confirmed_at` to be the same as their `created_at` attribute' do
+ migrate!
+
+ [project_bot_1, project_bot_2].each do |bot|
+ expect(bot.reload.confirmed_at).to eq(bot.created_at)
+ end
+ end
+ end
+
+ context 'project bot users that are currently confirmed' do
+ let!(:confirmed_project_bot) do
+ create_user!(
+ name: 'bot_1',
+ email: 'bot_1@example.com',
+ user_type: described_class::User::USER_TYPE_PROJECT_BOT,
+ confirmed_at: 1.day.ago
+ )
+ end
+
+ it 'does not update their `confirmed_at` attribute' do
+ expect { migrate! }.not_to change { confirmed_project_bot.reload.confirmed_at }
+ end
+ end
+
+ context 'human users that are currently unconfirmed' do
+ let!(:unconfirmed_human) do
+ create_user!(
+ name: 'human',
+ email: 'human@example.com',
+ user_type: nil
+ )
+ end
+
+ it 'does not update their `confirmed_at` attribute' do
+ expect { migrate! }.not_to change { unconfirmed_human.reload.confirmed_at }
+ end
+ end
+
+ private
+
+ def create_user!(name:, email:, user_type:, created_at: Time.now, confirmed_at: nil)
+ users.create!(
+ name: name,
+ email: email,
+ username: name,
+ projects_limit: 0,
+ user_type: user_type,
+ confirmed_at: confirmed_at
+ )
+ end
+end
diff --git a/spec/migrations/create_environment_for_self_monitoring_project_spec.rb b/spec/migrations/create_environment_for_self_monitoring_project_spec.rb
index aee0651dee0..1ba464f1610 100644
--- a/spec/migrations/create_environment_for_self_monitoring_project_spec.rb
+++ b/spec/migrations/create_environment_for_self_monitoring_project_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200214214934_create_environment_for_self_monitoring_project')
-describe CreateEnvironmentForSelfMonitoringProject do
+RSpec.describe CreateEnvironmentForSelfMonitoringProject do
let(:application_settings_table) { table(:application_settings) }
let(:environments) { table(:environments) }
diff --git a/spec/migrations/delete_internal_ids_where_feature_flags_usage_spec.rb b/spec/migrations/delete_internal_ids_where_feature_flags_usage_spec.rb
index 6eecd0870ed..a58121fb708 100644
--- a/spec/migrations/delete_internal_ids_where_feature_flags_usage_spec.rb
+++ b/spec/migrations/delete_internal_ids_where_feature_flags_usage_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200117194900_delete_internal_ids_where_feature_flags_usage')
-describe DeleteInternalIdsWhereFeatureFlagsUsage do
+RSpec.describe DeleteInternalIdsWhereFeatureFlagsUsage do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:internal_ids) { table(:internal_ids) }
diff --git a/spec/migrations/delete_template_project_services_spec.rb b/spec/migrations/delete_template_project_services_spec.rb
index 3c6709ec310..aacc4fcfd58 100644
--- a/spec/migrations/delete_template_project_services_spec.rb
+++ b/spec/migrations/delete_template_project_services_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200305151736_delete_template_project_services.rb')
-describe DeleteTemplateProjectServices, :migration do
+RSpec.describe DeleteTemplateProjectServices, :migration do
let(:services) { table(:services) }
let(:project) { table(:projects).create!(namespace_id: 1) }
diff --git a/spec/migrations/delete_template_services_duplicated_by_type_spec.rb b/spec/migrations/delete_template_services_duplicated_by_type_spec.rb
index 64da0664e2c..5bcbfb7c733 100644
--- a/spec/migrations/delete_template_services_duplicated_by_type_spec.rb
+++ b/spec/migrations/delete_template_services_duplicated_by_type_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200304160801_delete_template_services_duplicated_by_type.rb')
-describe DeleteTemplateServicesDuplicatedByType do
+RSpec.describe DeleteTemplateServicesDuplicatedByType do
let(:services) { table(:services) }
before do
diff --git a/spec/migrations/delete_user_callout_alerts_moved_spec.rb b/spec/migrations/delete_user_callout_alerts_moved_spec.rb
new file mode 100644
index 00000000000..f6b1a8982fb
--- /dev/null
+++ b/spec/migrations/delete_user_callout_alerts_moved_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200710102418_delete_user_callout_alerts_moved.rb')
+
+RSpec.describe DeleteUserCalloutAlertsMoved do
+ let(:users) { table(:users) }
+ let(:user_callouts) { table(:user_callouts) }
+ let(:alerts_moved_feature) { described_class::FEATURE_NAME_ALERTS_MOVED }
+ let(:unrelated_feature) { 1 }
+
+ let!(:user1) { users.create!(email: '1', projects_limit: 0) }
+ let!(:user2) { users.create!(email: '2', projects_limit: 0) }
+
+ subject(:migration) { described_class.new }
+
+ before do
+ user_callouts.create!(user_id: user1.id, feature_name: alerts_moved_feature)
+ user_callouts.create!(user_id: user1.id, feature_name: unrelated_feature)
+ user_callouts.create!(user_id: user2.id, feature_name: alerts_moved_feature)
+ end
+
+ describe '#up' do
+ it 'deletes `alerts_moved` user callouts' do
+ migration.up
+
+ expect(user_callouts.all.map(&:feature_name)).to eq([unrelated_feature])
+ end
+ end
+end
diff --git a/spec/migrations/drop_activate_prometheus_services_background_jobs_spec.rb b/spec/migrations/drop_activate_prometheus_services_background_jobs_spec.rb
index a02a0819a7b..b4bbadd199e 100644
--- a/spec/migrations/drop_activate_prometheus_services_background_jobs_spec.rb
+++ b/spec/migrations/drop_activate_prometheus_services_background_jobs_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200221144534_drop_activate_prometheus_services_background_jobs.rb')
-describe DropActivatePrometheusServicesBackgroundJobs, :sidekiq, :redis, schema: 2020_02_21_144534 do
+RSpec.describe DropActivatePrometheusServicesBackgroundJobs, :sidekiq, :redis, schema: 2020_02_21_144534 do
subject(:migration) { described_class.new }
describe '#up' do
diff --git a/spec/migrations/drop_background_migration_jobs_spec.rb b/spec/migrations/drop_background_migration_jobs_spec.rb
index d9e0561f326..2896f4ca0eb 100644
--- a/spec/migrations/drop_background_migration_jobs_spec.rb
+++ b/spec/migrations/drop_background_migration_jobs_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200116051619_drop_background_migration_jobs.rb')
-describe DropBackgroundMigrationJobs, :sidekiq, :redis, schema: 2020_01_16_051619 do
+RSpec.describe DropBackgroundMigrationJobs, :sidekiq, :redis, schema: 2020_01_16_051619 do
subject(:migration) { described_class.new }
describe '#up' do
diff --git a/spec/migrations/drop_project_ci_cd_settings_merge_trains_enabled_spec.rb b/spec/migrations/drop_project_ci_cd_settings_merge_trains_enabled_spec.rb
index 9166f626922..3f6aae401be 100644
--- a/spec/migrations/drop_project_ci_cd_settings_merge_trains_enabled_spec.rb
+++ b/spec/migrations/drop_project_ci_cd_settings_merge_trains_enabled_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20191128162854_drop_project_ci_cd_settings_merge_trains_enabled.rb')
-describe DropProjectCiCdSettingsMergeTrainsEnabled do
+RSpec.describe DropProjectCiCdSettingsMergeTrainsEnabled do
let!(:project_ci_cd_setting) { table(:project_ci_cd_settings) }
it 'correctly migrates up and down' do
diff --git a/spec/migrations/encrypt_feature_flags_clients_tokens_spec.rb b/spec/migrations/encrypt_feature_flags_clients_tokens_spec.rb
index 9b139c4b57b..ad83119f324 100644
--- a/spec/migrations/encrypt_feature_flags_clients_tokens_spec.rb
+++ b/spec/migrations/encrypt_feature_flags_clients_tokens_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190606175050_encrypt_feature_flags_clients_tokens.rb')
-describe EncryptFeatureFlagsClientsTokens do
+RSpec.describe EncryptFeatureFlagsClientsTokens do
let(:migration) { described_class.new }
let(:feature_flags_clients) { table(:operations_feature_flags_clients) }
let(:projects) { table(:projects) }
diff --git a/spec/migrations/encrypt_plaintext_attributes_on_application_settings_spec.rb b/spec/migrations/encrypt_plaintext_attributes_on_application_settings_spec.rb
index fda810d1da9..ff5aa81b5b5 100644
--- a/spec/migrations/encrypt_plaintext_attributes_on_application_settings_spec.rb
+++ b/spec/migrations/encrypt_plaintext_attributes_on_application_settings_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20191120115530_encrypt_plaintext_attributes_on_application_settings.rb')
-describe EncryptPlaintextAttributesOnApplicationSettings do
+RSpec.describe EncryptPlaintextAttributesOnApplicationSettings do
let(:migration) { described_class.new }
let(:application_settings) { table(:application_settings) }
let(:plaintext) { 'secret-token' }
diff --git a/spec/migrations/enqueue_reset_merge_status_second_run_spec.rb b/spec/migrations/enqueue_reset_merge_status_second_run_spec.rb
index d4cf3d15758..f5728534675 100644
--- a/spec/migrations/enqueue_reset_merge_status_second_run_spec.rb
+++ b/spec/migrations/enqueue_reset_merge_status_second_run_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190620112608_enqueue_reset_merge_status_second_run.rb')
-describe EnqueueResetMergeStatusSecondRun do
+RSpec.describe EnqueueResetMergeStatusSecondRun do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
diff --git a/spec/migrations/enqueue_reset_merge_status_spec.rb b/spec/migrations/enqueue_reset_merge_status_spec.rb
index 9728ada14ba..683d2caf9ca 100644
--- a/spec/migrations/enqueue_reset_merge_status_spec.rb
+++ b/spec/migrations/enqueue_reset_merge_status_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190528180441_enqueue_reset_merge_status.rb')
-describe EnqueueResetMergeStatus do
+RSpec.describe EnqueueResetMergeStatus do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
diff --git a/spec/migrations/fill_file_store_ci_job_artifacts_spec.rb b/spec/migrations/fill_file_store_ci_job_artifacts_spec.rb
index 5435a438824..64ab879d53c 100644
--- a/spec/migrations/fill_file_store_ci_job_artifacts_spec.rb
+++ b/spec/migrations/fill_file_store_ci_job_artifacts_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200513235532_fill_file_store_ci_job_artifacts.rb')
-describe FillFileStoreCiJobArtifacts do
+RSpec.describe FillFileStoreCiJobArtifacts do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:builds) { table(:ci_builds) }
diff --git a/spec/migrations/fill_file_store_lfs_objects_spec.rb b/spec/migrations/fill_file_store_lfs_objects_spec.rb
index e574eacca35..2a610e5311b 100644
--- a/spec/migrations/fill_file_store_lfs_objects_spec.rb
+++ b/spec/migrations/fill_file_store_lfs_objects_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200513234502_fill_file_store_lfs_objects.rb')
-describe FillFileStoreLfsObjects do
+RSpec.describe FillFileStoreLfsObjects do
let(:lfs_objects) { table(:lfs_objects) }
let(:oid) { 'b804383982bb89b00e828e3f44c038cc991d3d1768009fc39ba8e2c081b9fb75' }
diff --git a/spec/migrations/fill_productivity_analytics_start_date_spec.rb b/spec/migrations/fill_productivity_analytics_start_date_spec.rb
index 4ae7b0eed24..5d7f0ffba50 100644
--- a/spec/migrations/fill_productivity_analytics_start_date_spec.rb
+++ b/spec/migrations/fill_productivity_analytics_start_date_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20191004081520_fill_productivity_analytics_start_date.rb')
-describe FillProductivityAnalyticsStartDate do
+RSpec.describe FillProductivityAnalyticsStartDate do
let(:settings_table) { table('application_settings') }
let(:metrics_table) { table('merge_request_metrics') }
diff --git a/spec/migrations/fill_store_uploads_spec.rb b/spec/migrations/fill_store_uploads_spec.rb
index 6a2a3c4ea8e..60eaf982c57 100644
--- a/spec/migrations/fill_store_uploads_spec.rb
+++ b/spec/migrations/fill_store_uploads_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200513235347_fill_store_uploads.rb')
-describe FillStoreUploads do
+RSpec.describe FillStoreUploads do
let(:uploads) { table(:uploads) }
let(:path) { 'uploads/-/system/avatar.jpg' }
diff --git a/spec/migrations/fix_max_pages_size_spec.rb b/spec/migrations/fix_max_pages_size_spec.rb
index 9882cda7fba..b44ce21b858 100644
--- a/spec/migrations/fix_max_pages_size_spec.rb
+++ b/spec/migrations/fix_max_pages_size_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20191213120427_fix_max_pages_size.rb')
-describe FixMaxPagesSize do
+RSpec.describe FixMaxPagesSize do
let(:application_settings) { table(:application_settings) }
let!(:default_setting) { application_settings.create! }
let!(:max_possible_setting) { application_settings.create!(max_pages_size: described_class::MAX_SIZE) }
diff --git a/spec/migrations/fix_null_type_labels_spec.rb b/spec/migrations/fix_null_type_labels_spec.rb
index b098141c5e9..fadc2a5d29e 100644
--- a/spec/migrations/fix_null_type_labels_spec.rb
+++ b/spec/migrations/fix_null_type_labels_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190131122559_fix_null_type_labels')
-describe FixNullTypeLabels do
+RSpec.describe FixNullTypeLabels do
let(:migration) { described_class.new }
let(:projects) { table(:projects) }
let(:namespaces) { table(:namespaces) }
diff --git a/spec/migrations/fix_pool_repository_source_project_id_spec.rb b/spec/migrations/fix_pool_repository_source_project_id_spec.rb
index 5a878dba6e7..67a75b893ef 100644
--- a/spec/migrations/fix_pool_repository_source_project_id_spec.rb
+++ b/spec/migrations/fix_pool_repository_source_project_id_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20190604184643_fix_pool_repository_source_project_id.rb')
-describe FixPoolRepositorySourceProjectId do
+RSpec.describe FixPoolRepositorySourceProjectId do
let(:projects) { table(:projects) }
let(:pool_repositories) { table(:pool_repositories) }
let(:shards) { table(:shards) }
diff --git a/spec/migrations/fix_projects_without_project_feature_spec.rb b/spec/migrations/fix_projects_without_project_feature_spec.rb
index 01413261008..2a4ba3f3ca5 100644
--- a/spec/migrations/fix_projects_without_project_feature_spec.rb
+++ b/spec/migrations/fix_projects_without_project_feature_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200127111840_fix_projects_without_project_feature.rb')
-describe FixProjectsWithoutProjectFeature do
+RSpec.describe FixProjectsWithoutProjectFeature do
let(:namespace) { table(:namespaces).create(name: 'gitlab', path: 'gitlab-org') }
let!(:projects) do
diff --git a/spec/migrations/fix_projects_without_prometheus_services_spec.rb b/spec/migrations/fix_projects_without_prometheus_services_spec.rb
index ecfad313f58..73ede2ad90c 100644
--- a/spec/migrations/fix_projects_without_prometheus_services_spec.rb
+++ b/spec/migrations/fix_projects_without_prometheus_services_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200220115023_fix_projects_without_prometheus_service.rb')
-describe FixProjectsWithoutPrometheusService, :migration do
+RSpec.describe FixProjectsWithoutPrometheusService, :migration do
let(:namespace) { table(:namespaces).create(name: 'gitlab', path: 'gitlab-org') }
let!(:projects) do
diff --git a/spec/migrations/fix_wrong_pages_access_level_spec.rb b/spec/migrations/fix_wrong_pages_access_level_spec.rb
index e0d09add740..9ee5fa783a4 100644
--- a/spec/migrations/fix_wrong_pages_access_level_spec.rb
+++ b/spec/migrations/fix_wrong_pages_access_level_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190703185326_fix_wrong_pages_access_level.rb')
-describe FixWrongPagesAccessLevel, :sidekiq_might_not_need_inline, schema: 20190628185004 do
+RSpec.describe FixWrongPagesAccessLevel, :sidekiq_might_not_need_inline, schema: 20190628185004 do
using RSpec::Parameterized::TableSyntax
let(:migration_class) { described_class::MIGRATION }
diff --git a/spec/migrations/generate_lets_encrypt_private_key_spec.rb b/spec/migrations/generate_lets_encrypt_private_key_spec.rb
index c0cb39fd519..cad257c18a0 100644
--- a/spec/migrations/generate_lets_encrypt_private_key_spec.rb
+++ b/spec/migrations/generate_lets_encrypt_private_key_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20190524062810_generate_lets_encrypt_private_key.rb')
-describe GenerateLetsEncryptPrivateKey do
+RSpec.describe GenerateLetsEncryptPrivateKey do
describe '#up' do
it 'does not fail' do
expect do
diff --git a/spec/migrations/generate_missing_routes_for_bots_spec.rb b/spec/migrations/generate_missing_routes_for_bots_spec.rb
new file mode 100644
index 00000000000..8af22042350
--- /dev/null
+++ b/spec/migrations/generate_missing_routes_for_bots_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require Rails.root.join('db', 'post_migrate', '20200703064117_generate_missing_routes_for_bots.rb')
+
+RSpec.describe GenerateMissingRoutesForBots, :migration do
+ let(:users) { table(:users) }
+ let(:namespaces) { table(:namespaces) }
+ let(:routes) { table(:routes) }
+
+ let(:visual_review_bot) do
+ users.create!(email: 'visual-review-bot@gitlab.com', name: 'GitLab Visual Review Bot', username: 'visual-review-bot', user_type: 3, projects_limit: 5)
+ end
+
+ let(:migration_bot) do
+ users.create!(email: 'migration-bot@gitlab.com', name: 'GitLab Migration Bot', username: 'migration-bot', user_type: 7, projects_limit: 5)
+ end
+
+ let!(:visual_review_bot_namespace) do
+ namespaces.create!(owner_id: visual_review_bot.id, name: visual_review_bot.name, path: visual_review_bot.username)
+ end
+
+ let!(:migration_bot_namespace) do
+ namespaces.create!(owner_id: migration_bot.id, name: migration_bot.name, path: migration_bot.username)
+ end
+
+ context 'for bot users without an existing route' do
+ it 'creates new routes' do
+ expect { migrate! }.to change { routes.count }.by(2)
+ end
+
+ it 'creates new routes with the same path and name as their namespace' do
+ migrate!
+
+ [visual_review_bot, migration_bot].each do |bot|
+ namespace = namespaces.find_by(owner_id: bot.id)
+ route = route_for(namespace: namespace)
+
+ expect(route.path).to eq(namespace.path)
+ expect(route.name).to eq(namespace.name)
+ end
+ end
+ end
+
+ it 'does not create routes for bot users with existing routes' do
+ create_route!(namespace: visual_review_bot_namespace)
+ create_route!(namespace: migration_bot_namespace)
+
+ expect { migrate! }.not_to change { routes.count }
+ end
+
+ it 'does not create routes for human users without an existing route' do
+ human_namespace = create_human_namespace!(name: 'GitLab Human', username: 'human')
+
+ expect { migrate! }.not_to change { route_for(namespace: human_namespace) }
+ end
+
+ it 'does not create route for a bot user with a missing route, if a human user with the same path already exists' do
+ human_namespace = create_human_namespace!(name: visual_review_bot.name, username: visual_review_bot.username)
+ create_route!(namespace: human_namespace)
+
+ expect { migrate! }.not_to change { route_for(namespace: visual_review_bot_namespace) }
+ end
+
+ private
+
+ def create_human_namespace!(name:, username:)
+ human = users.create!(email: 'human@gitlab.com', name: name, username: username, user_type: nil, projects_limit: 5)
+ namespaces.create!(owner_id: human.id, name: human.name, path: human.username)
+ end
+
+ def create_route!(namespace:)
+ routes.create!(path: namespace.path, name: namespace.name, source_id: namespace.id, source_type: 'Namespace')
+ end
+
+ def route_for(namespace:)
+ routes.find_by(source_type: 'Namespace', source_id: namespace.id)
+ end
+end
diff --git a/spec/migrations/insert_project_hooks_plan_limits_spec.rb b/spec/migrations/insert_project_hooks_plan_limits_spec.rb
index e4bdda4cf5e..09fae160a6f 100644
--- a/spec/migrations/insert_project_hooks_plan_limits_spec.rb
+++ b/spec/migrations/insert_project_hooks_plan_limits_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20191216183532_insert_project_hooks_plan_limits.rb')
-describe InsertProjectHooksPlanLimits do
+RSpec.describe InsertProjectHooksPlanLimits do
let(:migration) { described_class.new }
let(:plans) { table(:plans) }
let(:plan_limits) { table(:plan_limits) }
diff --git a/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb b/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb
index 2fe6f3f62a9..d0abc777326 100644
--- a/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb
+++ b/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190204115450_migrate_auto_dev_ops_domain_to_cluster_domain.rb')
-describe MigrateAutoDevOpsDomainToClusterDomain do
+RSpec.describe MigrateAutoDevOpsDomainToClusterDomain do
include MigrationHelpers::ClusterHelpers
let(:migration) { described_class.new }
diff --git a/spec/migrations/migrate_bot_type_to_user_type_spec.rb b/spec/migrations/migrate_bot_type_to_user_type_spec.rb
index 9686aae0cd3..2b85f2a7f69 100644
--- a/spec/migrations/migrate_bot_type_to_user_type_spec.rb
+++ b/spec/migrations/migrate_bot_type_to_user_type_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200311074438_migrate_bot_type_to_user_type.rb')
-describe MigrateBotTypeToUserType, :migration do
+RSpec.describe MigrateBotTypeToUserType, :migration do
let(:users) { table(:users) }
it 'updates bots & ignores humans' do
diff --git a/spec/migrations/migrate_code_owner_approval_status_to_protected_branches_in_batches_spec.rb b/spec/migrations/migrate_code_owner_approval_status_to_protected_branches_in_batches_spec.rb
index cda965135b0..70304a4deb2 100644
--- a/spec/migrations/migrate_code_owner_approval_status_to_protected_branches_in_batches_spec.rb
+++ b/spec/migrations/migrate_code_owner_approval_status_to_protected_branches_in_batches_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190827102026_migrate_code_owner_approval_status_to_protected_branches_in_batches.rb')
-describe MigrateCodeOwnerApprovalStatusToProtectedBranchesInBatches do
+RSpec.describe MigrateCodeOwnerApprovalStatusToProtectedBranchesInBatches do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:protected_branches) { table(:protected_branches) }
diff --git a/spec/migrations/migrate_commit_notes_mentions_to_db_spec.rb b/spec/migrations/migrate_commit_notes_mentions_to_db_spec.rb
index dc40d0865f2..eb7fb7d4fc5 100644
--- a/spec/migrations/migrate_commit_notes_mentions_to_db_spec.rb
+++ b/spec/migrations/migrate_commit_notes_mentions_to_db_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200128134110_migrate_commit_notes_mentions_to_db')
-describe MigrateCommitNotesMentionsToDb, :migration, :sidekiq do
+RSpec.describe MigrateCommitNotesMentionsToDb, :migration, :sidekiq do
let(:users) { table(:users) }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
diff --git a/spec/migrations/migrate_create_commit_signature_worker_sidekiq_queue_spec.rb b/spec/migrations/migrate_create_commit_signature_worker_sidekiq_queue_spec.rb
index 3d7803b7563..5f7b4755980 100644
--- a/spec/migrations/migrate_create_commit_signature_worker_sidekiq_queue_spec.rb
+++ b/spec/migrations/migrate_create_commit_signature_worker_sidekiq_queue_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200206091544_migrate_create_commit_signature_worker_sidekiq_queue.rb')
-describe MigrateCreateCommitSignatureWorkerSidekiqQueue, :sidekiq, :redis do
+RSpec.describe MigrateCreateCommitSignatureWorkerSidekiqQueue, :sidekiq, :redis do
include Gitlab::Database::MigrationHelpers
include StubWorker
diff --git a/spec/migrations/migrate_discussion_id_on_promoted_epics_spec.rb b/spec/migrations/migrate_discussion_id_on_promoted_epics_spec.rb
index 0a8975402da..e3119be495d 100644
--- a/spec/migrations/migrate_discussion_id_on_promoted_epics_spec.rb
+++ b/spec/migrations/migrate_discussion_id_on_promoted_epics_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190715193142_migrate_discussion_id_on_promoted_epics.rb')
-describe MigrateDiscussionIdOnPromotedEpics do
+RSpec.describe MigrateDiscussionIdOnPromotedEpics do
let(:migration_class) { described_class::MIGRATION }
let(:migration_name) { migration_class.to_s.demodulize }
diff --git a/spec/migrations/migrate_k8s_service_integration_spec.rb b/spec/migrations/migrate_k8s_service_integration_spec.rb
index 660e958eb42..a1b2f2ae841 100644
--- a/spec/migrations/migrate_k8s_service_integration_spec.rb
+++ b/spec/migrations/migrate_k8s_service_integration_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190517153211_migrate_k8s_service_integration.rb')
-describe MigrateK8sServiceIntegration do
+RSpec.describe MigrateK8sServiceIntegration do
context 'template service' do
context 'with namespace' do
let!(:service) do
diff --git a/spec/migrations/migrate_legacy_managed_clusters_to_unmanaged_spec.rb b/spec/migrations/migrate_legacy_managed_clusters_to_unmanaged_spec.rb
index e3462e1d6bd..c8bfeec8049 100644
--- a/spec/migrations/migrate_legacy_managed_clusters_to_unmanaged_spec.rb
+++ b/spec/migrations/migrate_legacy_managed_clusters_to_unmanaged_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190606163724_migrate_legacy_managed_clusters_to_unmanaged.rb')
-describe MigrateLegacyManagedClustersToUnmanaged do
+RSpec.describe MigrateLegacyManagedClustersToUnmanaged do
let(:cluster_type) { 'project_type' }
let(:created_at) { 1.hour.ago }
diff --git a/spec/migrations/migrate_managed_clusters_with_no_token_to_unmanaged_spec.rb b/spec/migrations/migrate_managed_clusters_with_no_token_to_unmanaged_spec.rb
index 2931fba3eb2..1fc92f6ceea 100644
--- a/spec/migrations/migrate_managed_clusters_with_no_token_to_unmanaged_spec.rb
+++ b/spec/migrations/migrate_managed_clusters_with_no_token_to_unmanaged_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190613231640_migrate_managed_clusters_with_no_token_to_unmanaged.rb')
-describe MigrateManagedClustersWithNoTokenToUnmanaged do
+RSpec.describe MigrateManagedClustersWithNoTokenToUnmanaged do
let(:cluster_type) { 'project_type' }
let(:created_at) { Date.new(2018, 11, 1).midnight }
diff --git a/spec/migrations/migrate_merge_request_mentions_to_db_spec.rb b/spec/migrations/migrate_merge_request_mentions_to_db_spec.rb
index aef8fd6490b..83388cc6314 100644
--- a/spec/migrations/migrate_merge_request_mentions_to_db_spec.rb
+++ b/spec/migrations/migrate_merge_request_mentions_to_db_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200211155539_migrate_merge_request_mentions_to_db')
-describe MigrateMergeRequestMentionsToDb, :migration do
+RSpec.describe MigrateMergeRequestMentionsToDb, :migration do
let(:users) { table(:users) }
let(:projects) { table(:projects) }
let(:namespaces) { table(:namespaces) }
diff --git a/spec/migrations/migrate_ops_feature_flags_scopes_target_user_ids_spec.rb b/spec/migrations/migrate_ops_feature_flags_scopes_target_user_ids_spec.rb
index fc5d814a2de..b2c36db2e1d 100644
--- a/spec/migrations/migrate_ops_feature_flags_scopes_target_user_ids_spec.rb
+++ b/spec/migrations/migrate_ops_feature_flags_scopes_target_user_ids_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20191118211629_migrate_ops_feature_flags_scopes_target_user_ids.rb')
-describe MigrateOpsFeatureFlagsScopesTargetUserIds do
+RSpec.describe MigrateOpsFeatureFlagsScopesTargetUserIds do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:flags) { table(:operations_feature_flags) }
diff --git a/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb b/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb
index 557eb52632f..1181c169f57 100644
--- a/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb
+++ b/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190124200344_migrate_storage_migrator_sidekiq_queue.rb')
-describe MigrateStorageMigratorSidekiqQueue, :redis do
+RSpec.describe MigrateStorageMigratorSidekiqQueue, :redis do
include Gitlab::Database::MigrationHelpers
include StubWorker
diff --git a/spec/migrations/migrate_store_security_reports_sidekiq_queue_spec.rb b/spec/migrations/migrate_store_security_reports_sidekiq_queue_spec.rb
index ddffa036af1..4face37bd66 100644
--- a/spec/migrations/migrate_store_security_reports_sidekiq_queue_spec.rb
+++ b/spec/migrations/migrate_store_security_reports_sidekiq_queue_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200213220159_migrate_store_security_reports_sidekiq_queue.rb')
-describe MigrateStoreSecurityReportsSidekiqQueue, :redis do
+RSpec.describe MigrateStoreSecurityReportsSidekiqQueue, :redis do
include Gitlab::Database::MigrationHelpers
include StubWorker
diff --git a/spec/migrations/migrate_sync_security_reports_to_report_approval_rules_sidekiq_queue_spec.rb b/spec/migrations/migrate_sync_security_reports_to_report_approval_rules_sidekiq_queue_spec.rb
index 6dfaff06ddb..15c5761bd99 100644
--- a/spec/migrations/migrate_sync_security_reports_to_report_approval_rules_sidekiq_queue_spec.rb
+++ b/spec/migrations/migrate_sync_security_reports_to_report_approval_rules_sidekiq_queue_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200213220211_migrate_sync_security_reports_to_report_approval_rules_sidekiq_queue.rb')
-describe MigrateSyncSecurityReportsToReportApprovalRulesSidekiqQueue, :redis do
+RSpec.describe MigrateSyncSecurityReportsToReportApprovalRulesSidekiqQueue, :redis do
include Gitlab::Database::MigrationHelpers
include StubWorker
diff --git a/spec/migrations/move_limits_from_plans_spec.rb b/spec/migrations/move_limits_from_plans_spec.rb
index aeb36100205..16f94ba6dbb 100644
--- a/spec/migrations/move_limits_from_plans_spec.rb
+++ b/spec/migrations/move_limits_from_plans_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20191030152934_move_limits_from_plans.rb')
-describe MoveLimitsFromPlans do
+RSpec.describe MoveLimitsFromPlans do
let(:plans) { table(:plans) }
let(:plan_limits) { table(:plan_limits) }
diff --git a/spec/migrations/nullify_users_role_spec.rb b/spec/migrations/nullify_users_role_spec.rb
index b11929ef76f..3cdeb81f362 100644
--- a/spec/migrations/nullify_users_role_spec.rb
+++ b/spec/migrations/nullify_users_role_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20191104142124_nullify_users_role.rb')
-describe NullifyUsersRole do
+RSpec.describe NullifyUsersRole do
let(:users) { table(:users) }
before do
diff --git a/spec/migrations/populate_project_statistics_packages_size_spec.rb b/spec/migrations/populate_project_statistics_packages_size_spec.rb
index c316a4bc8b7..9024406c614 100644
--- a/spec/migrations/populate_project_statistics_packages_size_spec.rb
+++ b/spec/migrations/populate_project_statistics_packages_size_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190418132125_populate_project_statistics_packages_size.rb')
-describe PopulateProjectStatisticsPackagesSize do
+RSpec.describe PopulateProjectStatisticsPackagesSize do
let(:project_statistics) { table(:project_statistics) }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
diff --git a/spec/migrations/populate_rule_type_on_approval_merge_request_rules_spec.rb b/spec/migrations/populate_rule_type_on_approval_merge_request_rules_spec.rb
index d6362528068..2ac912d7979 100644
--- a/spec/migrations/populate_rule_type_on_approval_merge_request_rules_spec.rb
+++ b/spec/migrations/populate_rule_type_on_approval_merge_request_rules_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190520201748_populate_rule_type_on_approval_merge_request_rules.rb')
-describe PopulateRuleTypeOnApprovalMergeRequestRules do
+RSpec.describe PopulateRuleTypeOnApprovalMergeRequestRules do
let(:migration) { described_class.new }
describe '#up' do
diff --git a/spec/migrations/remove_additional_application_settings_rows_spec.rb b/spec/migrations/remove_additional_application_settings_rows_spec.rb
index 379fa385b8e..a865e8e8dd7 100644
--- a/spec/migrations/remove_additional_application_settings_rows_spec.rb
+++ b/spec/migrations/remove_additional_application_settings_rows_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200420162730_remove_additional_application_settings_rows.rb')
-describe RemoveAdditionalApplicationSettingsRows do
+RSpec.describe RemoveAdditionalApplicationSettingsRows do
let(:application_settings) { table(:application_settings) }
it 'removes additional rows from application settings' do
diff --git a/spec/migrations/remove_deprecated_jenkins_service_records_spec.rb b/spec/migrations/remove_deprecated_jenkins_service_records_spec.rb
index 9c9abd36203..00fde88c528 100644
--- a/spec/migrations/remove_deprecated_jenkins_service_records_spec.rb
+++ b/spec/migrations/remove_deprecated_jenkins_service_records_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200511130129_remove_deprecated_jenkins_service_records.rb')
require Rails.root.join('db', 'post_migrate', '20200511130130_ensure_deprecated_jenkins_service_records_removal.rb')
-shared_examples 'remove DeprecatedJenkinsService records' do
+RSpec.shared_examples 'remove DeprecatedJenkinsService records' do
let(:services) { table(:services) }
before do
@@ -19,10 +19,10 @@ shared_examples 'remove DeprecatedJenkinsService records' do
end
end
-describe RemoveDeprecatedJenkinsServiceRecords, :migration do
+RSpec.describe RemoveDeprecatedJenkinsServiceRecords, :migration do
it_behaves_like 'remove DeprecatedJenkinsService records'
end
-describe EnsureDeprecatedJenkinsServiceRecordsRemoval, :migration do
+RSpec.describe EnsureDeprecatedJenkinsServiceRecordsRemoval, :migration do
it_behaves_like 'remove DeprecatedJenkinsService records'
end
diff --git a/spec/migrations/remove_duplicate_labels_from_project_spec.rb b/spec/migrations/remove_duplicate_labels_from_project_spec.rb
new file mode 100644
index 00000000000..5e287eaa4ed
--- /dev/null
+++ b/spec/migrations/remove_duplicate_labels_from_project_spec.rb
@@ -0,0 +1,238 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200305082754_remove_duplicate_labels_from_project.rb')
+
+RSpec.describe RemoveDuplicateLabelsFromProject do
+ let(:labels_table) { table(:labels) }
+ let(:labels) { labels_table.all }
+ let(:projects_table) { table(:projects) }
+ let(:projects) { projects_table.all }
+ let(:namespaces_table) { table(:namespaces) }
+ let(:namespaces) { namespaces_table.all }
+ let(:backup_labels_table) { table(:backup_labels) }
+ let(:backup_labels) { backup_labels_table.all }
+
+ # all the possible tables with records that may have a relationship with a label
+ let(:analytics_cycle_analytics_group_stages_table) { table(:analytics_cycle_analytics_group_stages) }
+ let(:analytics_cycle_analytics_project_stages_table) { table(:analytics_cycle_analytics_project_stages) }
+ let(:board_labels_table) { table(:board_labels) }
+ let(:label_links_table) { table(:label_links) }
+ let(:label_priorities_table) { table(:label_priorities) }
+ let(:lists_table) { table(:lists) }
+ let(:resource_label_events_table) { table(:resource_label_events) }
+
+ let!(:group_one) { namespaces_table.create!(id: 1, type: 'Group', name: 'group', path: 'group') }
+ let!(:project_one) do
+ projects_table.create!(id: 1, name: 'project', path: 'project',
+ visibility_level: 0, namespace_id: group_one.id)
+ end
+ let(:label_title) { 'bug' }
+ let(:label_color) { 'red' }
+ let(:label_description) { 'nice label' }
+ let(:group_id) { group_one.id }
+ let(:project_id) { project_one.id }
+ let(:other_title) { 'feature' }
+
+ let(:group_label_attributes) do
+ {
+ title: label_title, color: label_color, group_id: group_id, type: 'GroupLabel', template: false, description: label_description
+ }
+ end
+
+ let(:project_label_attributes) do
+ {
+ title: label_title, color: label_color, project_id: project_id, type: 'ProjectLabel', template: false, description: label_description
+ }
+ end
+
+ let(:migration) { described_class.new }
+
+ describe 'removing full duplicates' do
+ context 'when there are no duplicate labels' do
+ let!(:first_label) { labels_table.create!(project_label_attributes.merge(id: 1, title: "a different label")) }
+ let!(:second_label) { labels_table.create!(project_label_attributes.merge(id: 2, title: "a totally different label")) }
+
+ it 'does not remove anything' do
+ expect { migration.up }.not_to change { backup_labels_table.count }
+ end
+
+ it 'restores removed records when rolling back - no change' do
+ migration.up
+
+ expect { migration.down }.not_to change { labels_table.count }
+ end
+ end
+
+ context 'with duplicates with no relationships' do
+ # can't use the activerecord class because the `type` makes it think it has polymorphism and should be/have a ProjectLabel subclass
+ let(:backup_labels) { ApplicationRecord.connection.execute('SELECT * from backup_labels') }
+
+ let!(:first_label) { labels_table.create!(project_label_attributes.merge(id: 1)) }
+ let!(:second_label) { labels_table.create!(project_label_attributes.merge(id: 2)) }
+ let!(:third_label) { labels_table.create!(project_label_attributes.merge(id: 3, title: other_title)) }
+ let!(:fourth_label) { labels_table.create!(project_label_attributes.merge(id: 4, title: other_title)) }
+
+ it 'creates a backup record for each removed record' do
+ expect { migration.up }.to change { backup_labels_table.count }.from(0).to(2)
+ end
+
+ it 'creates the correct backup records with `create` restore_action' do
+ migration.up
+
+ expect(backup_labels.find { |bl| bl["id"] == 2 }).to include(second_label.attributes.merge("restore_action" => described_class::CREATE, "new_title" => nil, "created_at" => anything, "updated_at" => anything))
+ expect(backup_labels.find { |bl| bl["id"] == 4 }).to include(fourth_label.attributes.merge("restore_action" => described_class::CREATE, "new_title" => nil, "created_at" => anything, "updated_at" => anything))
+ end
+
+ it 'deletes all but one' do
+ migration.up
+
+ expect { second_label.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { fourth_label.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'restores removed records on rollback' do
+ second_label_attributes = modified_attributes(second_label)
+ fourth_label_attributes = modified_attributes(fourth_label)
+
+ migration.up
+
+ migration.down
+
+ expect(second_label.attributes).to include(second_label_attributes)
+ expect(fourth_label.attributes).to include(fourth_label_attributes)
+ end
+ end
+
+ context 'two duplicate records, one of which has a relationship' do
+ let!(:first_label) { labels_table.create!(project_label_attributes.merge(id: 1)) }
+ let!(:second_label) { labels_table.create!(project_label_attributes.merge(id: 2)) }
+ let!(:label_priority) { label_priorities_table.create!(label_id: second_label.id, project_id: project_id, priority: 1) }
+
+ it 'does not remove anything' do
+ expect { migration.up }.not_to change { labels_table.count }
+ end
+
+ it 'does not create a backup record with `create` restore_action' do
+ expect { migration.up }.not_to change { backup_labels_table.where(restore_action: described_class::CREATE).count }
+ end
+
+ it 'restores removed records when rolling back - no change' do
+ migration.up
+
+ expect { migration.down }.not_to change { labels_table.count }
+ end
+ end
+
+ context 'multiple duplicates, a subset of which have relationships' do
+ let!(:first_label) { labels_table.create!(project_label_attributes.merge(id: 1)) }
+ let!(:second_label) { labels_table.create!(project_label_attributes.merge(id: 2)) }
+ let!(:label_priority_for_second_label) { label_priorities_table.create!(label_id: second_label.id, project_id: project_id, priority: 1) }
+ let!(:third_label) { labels_table.create!(project_label_attributes.merge(id: 3)) }
+ let!(:fourth_label) { labels_table.create!(project_label_attributes.merge(id: 4)) }
+ let!(:label_priority_for_fourth_label) { label_priorities_table.create!(label_id: fourth_label.id, project_id: project_id, priority: 2) }
+
+ it 'creates a backup record with `create` restore_action for each removed record' do
+ expect { migration.up }.to change { backup_labels_table.where(restore_action: described_class::CREATE).count }.from(0).to(1)
+ end
+
+ it 'creates the correct backup records' do
+ migration.up
+
+ # can't use the activerecord class because the `type` column makes it think it has polymorphism and should be/have a ProjectLabel subclass
+ backup_labels = ApplicationRecord.connection.execute('SELECT * from backup_labels')
+
+ expect(backup_labels.find { |bl| bl["id"] == 3 }).to include(third_label.attributes.merge("restore_action" => described_class::CREATE, "new_title" => nil, "created_at" => anything, "updated_at" => anything))
+ end
+
+ it 'deletes the duplicate record' do
+ migration.up
+
+ expect { first_label.reload }.not_to raise_error
+ expect { second_label.reload }.not_to raise_error
+ expect { third_label.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'restores removed records on rollback' do
+ third_label_attributes = modified_attributes(third_label)
+
+ migration.up
+ migration.down
+
+ expect(third_label.attributes).to include(third_label_attributes)
+ end
+ end
+ end
+
+ describe 'renaming partial duplicates' do
+ # partial duplicates - only project_id and title match. Distinct colour prevents deletion.
+ context 'when there are no duplicate labels' do
+ let!(:first_label) { labels_table.create!(project_label_attributes.merge(id: 1, title: "a unique label", color: 'green')) }
+ let!(:second_label) { labels_table.create!(project_label_attributes.merge(id: 2, title: "a totally different, unique, label", color: 'blue')) }
+
+ it 'does not rename anything' do
+ expect { migration.up }.not_to change { backup_labels_table.count }
+ end
+ end
+
+ context 'with duplicates with no relationships' do
+ let!(:first_label) { labels_table.create!(project_label_attributes.merge(id: 1, color: 'green')) }
+ let!(:second_label) { labels_table.create!(project_label_attributes.merge(id: 2, color: 'blue')) }
+ let!(:third_label) { labels_table.create!(project_label_attributes.merge(id: 3, title: other_title, color: 'purple')) }
+ let!(:fourth_label) { labels_table.create!(project_label_attributes.merge(id: 4, title: other_title, color: 'yellow')) }
+
+ it 'creates a backup record for each renamed record' do
+ expect { migration.up }.to change { backup_labels_table.count }.from(0).to(2)
+ end
+
+ it 'creates the correct backup records with `rename` restore_action' do
+ migration.up
+
+ # can't use the activerecord class because the `type` makes it think it has polymorphism and should be/have a ProjectLabel subclass
+ backup_labels = ApplicationRecord.connection.execute('SELECT * from backup_labels')
+
+ expect(backup_labels.find { |bl| bl["id"] == 2 }).to include(second_label.attributes.merge("restore_action" => described_class::RENAME, "created_at" => anything, "updated_at" => anything))
+ expect(backup_labels.find { |bl| bl["id"] == 4 }).to include(fourth_label.attributes.merge("restore_action" => described_class::RENAME, "created_at" => anything, "updated_at" => anything))
+ end
+
+ it 'modifies the titles of the partial duplicates' do
+ migration.up
+
+ expect(second_label.reload.title).to match(/#{label_title}_duplicate#{second_label.id}$/)
+ expect(fourth_label.reload.title).to match(/#{other_title}_duplicate#{fourth_label.id}$/)
+ end
+
+ it 'restores renamed records on rollback' do
+ second_label_attributes = modified_attributes(second_label)
+ fourth_label_attributes = modified_attributes(fourth_label)
+
+ migration.up
+
+ migration.down
+
+ expect(second_label.reload.attributes).to include(second_label_attributes)
+ expect(fourth_label.reload.attributes).to include(fourth_label_attributes)
+ end
+
+ context 'when the labels have a long title that might overflow' do
+ let(:long_title) { "a" * 255 }
+
+ before do
+ first_label.update_attribute(:title, long_title)
+ second_label.update_attribute(:title, long_title)
+ end
+
+ it 'keeps the length within the limit' do
+ migration.up
+
+ expect(second_label.reload.title).to eq("#{"a" * 244}_duplicate#{second_label.id}")
+ expect(second_label.title.length).to eq 255
+ end
+ end
+ end
+ end
+
+ def modified_attributes(label)
+ label.attributes.except('created_at', 'updated_at')
+ end
+end
diff --git a/spec/migrations/remove_empty_github_service_templates_spec.rb b/spec/migrations/remove_empty_github_service_templates_spec.rb
index 51b29ec6efc..7a77e342efd 100644
--- a/spec/migrations/remove_empty_github_service_templates_spec.rb
+++ b/spec/migrations/remove_empty_github_service_templates_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20191021101942_remove_empty_github_service_templates.rb')
-describe RemoveEmptyGithubServiceTemplates do
+RSpec.describe RemoveEmptyGithubServiceTemplates do
subject(:migration) { described_class.new }
let(:services) do
diff --git a/spec/migrations/remove_gitlab_issue_tracker_service_records_spec.rb b/spec/migrations/remove_gitlab_issue_tracker_service_records_spec.rb
new file mode 100644
index 00000000000..81fa29f4c54
--- /dev/null
+++ b/spec/migrations/remove_gitlab_issue_tracker_service_records_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200623142159_remove_gitlab_issue_tracker_service_records.rb')
+
+RSpec.describe RemoveGitlabIssueTrackerServiceRecords do
+ let(:services) { table(:services) }
+
+ before do
+ 5.times { services.create!(type: 'GitlabIssueTrackerService') }
+ services.create!(type: 'SomeOtherType')
+ end
+
+ it 'removes services records of type GitlabIssueTrackerService', :aggregate_failures do
+ expect { migrate! }.to change { services.count }.from(6).to(1)
+ expect(services.first.type).to eq('SomeOtherType')
+ expect(services.where(type: 'GitlabIssueTrackerService')).to be_empty
+ end
+end
diff --git a/spec/migrations/remove_orphaned_invited_members_spec.rb b/spec/migrations/remove_orphaned_invited_members_spec.rb
index 0ed4c15428a..0474b5362be 100644
--- a/spec/migrations/remove_orphaned_invited_members_spec.rb
+++ b/spec/migrations/remove_orphaned_invited_members_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200424050250_remove_orphaned_invited_members.rb')
-describe RemoveOrphanedInvitedMembers do
+RSpec.describe RemoveOrphanedInvitedMembers do
let(:members_table) { table(:members) }
let(:users_table) { table(:users) }
let(:namespaces_table) { table(:namespaces) }
diff --git a/spec/migrations/remove_packages_deprecated_dependencies_spec.rb b/spec/migrations/remove_packages_deprecated_dependencies_spec.rb
index 2ba7a3b268b..84c23240af9 100644
--- a/spec/migrations/remove_packages_deprecated_dependencies_spec.rb
+++ b/spec/migrations/remove_packages_deprecated_dependencies_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200210135504_remove_packages_deprecated_dependencies.rb')
-describe RemovePackagesDeprecatedDependencies do
+RSpec.describe RemovePackagesDeprecatedDependencies do
let(:projects) { table(:projects) }
let(:packages) { table(:packages_packages) }
let(:dependency_links) { table(:packages_dependency_links) }
diff --git a/spec/migrations/remove_security_dashboard_feature_flag_spec.rb b/spec/migrations/remove_security_dashboard_feature_flag_spec.rb
index fa0489526e2..77363aebfe6 100644
--- a/spec/migrations/remove_security_dashboard_feature_flag_spec.rb
+++ b/spec/migrations/remove_security_dashboard_feature_flag_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200214034836_remove_security_dashboard_feature_flag.rb')
-describe RemoveSecurityDashboardFeatureFlag do
+RSpec.describe RemoveSecurityDashboardFeatureFlag do
let(:feature_gates) { table(:feature_gates) }
subject(:migration) { described_class.new }
diff --git a/spec/migrations/rename_security_dashboard_feature_flag_to_instance_security_dashboard_spec.rb b/spec/migrations/rename_security_dashboard_feature_flag_to_instance_security_dashboard_spec.rb
index 07be7a4ad51..83a79ac9795 100644
--- a/spec/migrations/rename_security_dashboard_feature_flag_to_instance_security_dashboard_spec.rb
+++ b/spec/migrations/rename_security_dashboard_feature_flag_to_instance_security_dashboard_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200212014653_rename_security_dashboard_feature_flag_to_instance_security_dashboard.rb')
-describe RenameSecurityDashboardFeatureFlagToInstanceSecurityDashboard do
+RSpec.describe RenameSecurityDashboardFeatureFlagToInstanceSecurityDashboard do
let(:feature_gates) { table(:feature_gates) }
subject(:migration) { described_class.new }
diff --git a/spec/migrations/save_instance_administrators_group_id_spec.rb b/spec/migrations/save_instance_administrators_group_id_spec.rb
index 74ced009fa5..cb11cd1653a 100644
--- a/spec/migrations/save_instance_administrators_group_id_spec.rb
+++ b/spec/migrations/save_instance_administrators_group_id_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200210092405_save_instance_administrators_group_id')
-describe SaveInstanceAdministratorsGroupId do
+RSpec.describe SaveInstanceAdministratorsGroupId do
let(:application_settings_table) { table(:application_settings) }
let(:instance_administrators_group) do
diff --git a/spec/migrations/schedule_backfill_push_rules_id_in_projects_spec.rb b/spec/migrations/schedule_backfill_push_rules_id_in_projects_spec.rb
index 77721eab77d..37a769bbc52 100644
--- a/spec/migrations/schedule_backfill_push_rules_id_in_projects_spec.rb
+++ b/spec/migrations/schedule_backfill_push_rules_id_in_projects_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200325162730_schedule_backfill_push_rules_id_in_projects.rb')
-describe ScheduleBackfillPushRulesIdInProjects do
+RSpec.describe ScheduleBackfillPushRulesIdInProjects do
let(:push_rules) { table(:push_rules) }
it 'adds global rule association to application settings' do
diff --git a/spec/migrations/schedule_calculate_wiki_sizes_spec.rb b/spec/migrations/schedule_calculate_wiki_sizes_spec.rb
new file mode 100644
index 00000000000..39a93f3ed25
--- /dev/null
+++ b/spec/migrations/schedule_calculate_wiki_sizes_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190527194900_schedule_calculate_wiki_sizes.rb')
+
+RSpec.describe ScheduleCalculateWikiSizes do
+ let(:migration_class) { Gitlab::BackgroundMigration::CalculateWikiSizes }
+ let(:migration_name) { migration_class.to_s.demodulize }
+
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:project_statistics) { table(:project_statistics) }
+ let(:namespace) { namespaces.create!(name: 'wiki-migration', path: 'wiki-migration') }
+ let(:project1) { projects.create!(name: 'wiki-project-1', path: 'wiki-project-1', namespace_id: namespace.id) }
+ let(:project2) { projects.create!(name: 'wiki-project-2', path: 'wiki-project-2', namespace_id: namespace.id) }
+ let(:project3) { projects.create!(name: 'wiki-project-3', path: 'wiki-project-3', namespace_id: namespace.id) }
+
+ context 'when missing wiki sizes exist' do
+ let!(:project_statistic1) { project_statistics.create!(id: 1, project_id: project1.id, namespace_id: namespace.id, wiki_size: 1000) }
+ let!(:project_statistic2) { project_statistics.create!(id: 2, project_id: project2.id, namespace_id: namespace.id, wiki_size: nil) }
+ let!(:project_statistic3) { project_statistics.create!(id: 3, project_id: project3.id, namespace_id: namespace.id, wiki_size: nil) }
+
+ it 'schedules a background migration' do
+ Timecop.freeze do
+ migrate!
+
+ expect(migration_name).to be_scheduled_delayed_migration(5.minutes, project_statistic2.id, project_statistic3.id)
+ expect(BackgroundMigrationWorker.jobs.size).to eq 1
+ end
+ end
+
+ it 'calculates missing wiki sizes', :sidekiq_inline do
+ expect(project_statistic2.wiki_size).to be_nil
+ expect(project_statistic3.wiki_size).to be_nil
+
+ migrate!
+
+ expect(project_statistic2.reload.wiki_size).not_to be_nil
+ expect(project_statistic3.reload.wiki_size).not_to be_nil
+ end
+ end
+
+ context 'when missing wiki sizes do not exist' do
+ before do
+ namespace = namespaces.create!(name: 'wiki-migration', path: 'wiki-migration')
+ project = projects.create!(name: 'wiki-project-1', path: 'wiki-project-1', namespace_id: namespace.id)
+ project_statistics.create!(project_id: project.id, namespace_id: 1, wiki_size: 1000)
+ end
+
+ it 'does not schedule a background migration' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq 0
+ end
+ end
+ end
+ end
+end
diff --git a/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb b/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb
index 30cb68c742c..a51bc374d5f 100644
--- a/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb
+++ b/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190524073827_schedule_fill_valid_time_for_pages_domain_certificates.rb')
-describe ScheduleFillValidTimeForPagesDomainCertificates do
+RSpec.describe ScheduleFillValidTimeForPagesDomainCertificates do
let(:migration_class) { described_class::MIGRATION }
let(:migration_name) { migration_class.to_s.demodulize }
diff --git a/spec/migrations/schedule_link_lfs_objects_projects_spec.rb b/spec/migrations/schedule_link_lfs_objects_projects_spec.rb
index 055ab3cdd83..04ca18de91f 100644
--- a/spec/migrations/schedule_link_lfs_objects_projects_spec.rb
+++ b/spec/migrations/schedule_link_lfs_objects_projects_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200310075115_schedule_link_lfs_objects_projects.rb')
-describe ScheduleLinkLfsObjectsProjects, :migration, :sidekiq do
+RSpec.describe ScheduleLinkLfsObjectsProjects, :migration, :sidekiq do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:fork_networks) { table(:fork_networks) }
diff --git a/spec/migrations/schedule_merge_request_assignees_migration_progress_check_spec.rb b/spec/migrations/schedule_merge_request_assignees_migration_progress_check_spec.rb
index bea985fabb1..0a69f49f10d 100644
--- a/spec/migrations/schedule_merge_request_assignees_migration_progress_check_spec.rb
+++ b/spec/migrations/schedule_merge_request_assignees_migration_progress_check_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190402224749_schedule_merge_request_assignees_migration_progress_check.rb')
-describe ScheduleMergeRequestAssigneesMigrationProgressCheck do
+RSpec.describe ScheduleMergeRequestAssigneesMigrationProgressCheck do
describe '#up' do
it 'schedules MergeRequestAssigneesMigrationProgressCheck background job' do
expect(BackgroundMigrationWorker).to receive(:perform_async)
diff --git a/spec/migrations/schedule_migrate_security_scans_spec.rb b/spec/migrations/schedule_migrate_security_scans_spec.rb
index 29e4e2b5cac..4fd17b20666 100644
--- a/spec/migrations/schedule_migrate_security_scans_spec.rb
+++ b/spec/migrations/schedule_migrate_security_scans_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200217225719_schedule_migrate_security_scans.rb')
# rubocop: disable RSpec/FactoriesInMigrationSpecs
-describe ScheduleMigrateSecurityScans, :sidekiq do
+RSpec.describe ScheduleMigrateSecurityScans, :sidekiq do
let(:migration) { described_class.new }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
diff --git a/spec/migrations/schedule_pages_metadata_migration_spec.rb b/spec/migrations/schedule_pages_metadata_migration_spec.rb
index 748b9fe1cd1..c37e19eb71c 100644
--- a/spec/migrations/schedule_pages_metadata_migration_spec.rb
+++ b/spec/migrations/schedule_pages_metadata_migration_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20191002031332_schedule_pages_metadata_migration')
-describe SchedulePagesMetadataMigration do
+RSpec.describe SchedulePagesMetadataMigration do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
diff --git a/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb b/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb
index d778b47179f..8b26cd589fd 100644
--- a/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb
+++ b/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190322132835_schedule_populate_merge_request_assignees_table.rb')
-describe SchedulePopulateMergeRequestAssigneesTable do
+RSpec.describe SchedulePopulateMergeRequestAssigneesTable do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
diff --git a/spec/migrations/schedule_populate_project_snippet_statistics_spec.rb b/spec/migrations/schedule_populate_project_snippet_statistics_spec.rb
new file mode 100644
index 00000000000..05e9d4d2f79
--- /dev/null
+++ b/spec/migrations/schedule_populate_project_snippet_statistics_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200709101408_schedule_populate_project_snippet_statistics.rb')
+
+RSpec.describe SchedulePopulateProjectSnippetStatistics do
+ let(:users) { table(:users) }
+ let(:snippets) { table(:snippets) }
+ let(:projects) { table(:projects) }
+ let(:namespaces) { table(:namespaces) }
+ let(:user1) { users.create!(id: 1, email: 'user1@example.com', projects_limit: 10, username: 'test1', name: 'Test1', state: 'active') }
+ let(:user2) { users.create!(id: 2, email: 'user2@example.com', projects_limit: 10, username: 'test2', name: 'Test2', state: 'active') }
+ let(:namespace1) { namespaces.create!(id: 1, owner_id: user1.id, name: 'user1', path: 'user1') }
+ let(:namespace2) { namespaces.create!(id: 2, owner_id: user2.id, name: 'user2', path: 'user2') }
+ let(:project1) { projects.create!(id: 1, namespace_id: namespace1.id) }
+ let(:project2) { projects.create!(id: 2, namespace_id: namespace1.id) }
+ let(:project3) { projects.create!(id: 3, namespace_id: namespace2.id) }
+
+ def create_snippet(id, user_id, project_id, type = 'ProjectSnippet')
+ params = {
+ id: id,
+ type: type,
+ author_id: user_id,
+ project_id: project_id,
+ file_name: 'foo',
+ content: 'bar'
+ }
+
+ snippets.create!(params)
+ end
+
+ it 'correctly schedules background migrations' do
+ # Creating the snippets in different order
+ create_snippet(1, user1.id, project1.id)
+ create_snippet(2, user2.id, project3.id)
+ create_snippet(3, user1.id, project1.id)
+ create_snippet(4, user1.id, project2.id)
+ create_snippet(5, user2.id, project3.id)
+ create_snippet(6, user1.id, project1.id)
+ # Creating a personal snippet to ensure we don't pick it
+ create_snippet(7, user1.id, nil, 'PersonalSnippet')
+
+ stub_const("#{described_class}::BATCH_SIZE", 4)
+
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ aggregate_failures do
+ expect(described_class::MIGRATION)
+ .to be_scheduled_migration([1, 3, 6, 4])
+
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(2.minutes, [2, 5])
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/migrations/schedule_populate_user_highest_roles_table_spec.rb b/spec/migrations/schedule_populate_user_highest_roles_table_spec.rb
index 67e0b994265..c7e5c6f30a6 100644
--- a/spec/migrations/schedule_populate_user_highest_roles_table_spec.rb
+++ b/spec/migrations/schedule_populate_user_highest_roles_table_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200311130802_schedule_populate_user_highest_roles_table.rb')
-describe SchedulePopulateUserHighestRolesTable do
+RSpec.describe SchedulePopulateUserHighestRolesTable do
let(:users) { table(:users) }
def create_user(id, params = {})
diff --git a/spec/migrations/schedule_recalculate_project_authorizations_second_run_spec.rb b/spec/migrations/schedule_recalculate_project_authorizations_second_run_spec.rb
index 4c05f7d57a1..06f1a7e28eb 100644
--- a/spec/migrations/schedule_recalculate_project_authorizations_second_run_spec.rb
+++ b/spec/migrations/schedule_recalculate_project_authorizations_second_run_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200204113224_schedule_recalculate_project_authorizations_second_run.rb')
-describe ScheduleRecalculateProjectAuthorizationsSecondRun do
+RSpec.describe ScheduleRecalculateProjectAuthorizationsSecondRun do
let(:users_table) { table(:users) }
before do
diff --git a/spec/migrations/schedule_recalculate_project_authorizations_spec.rb b/spec/migrations/schedule_recalculate_project_authorizations_spec.rb
index d30ebf825ef..9519b103284 100644
--- a/spec/migrations/schedule_recalculate_project_authorizations_spec.rb
+++ b/spec/migrations/schedule_recalculate_project_authorizations_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200204113223_schedule_recalculate_project_authorizations.rb')
-describe ScheduleRecalculateProjectAuthorizations do
+RSpec.describe ScheduleRecalculateProjectAuthorizations do
let(:users_table) { table(:users) }
let(:namespaces_table) { table(:namespaces) }
let(:projects_table) { table(:projects) }
diff --git a/spec/migrations/schedule_recalculate_project_authorizations_third_run_spec.rb b/spec/migrations/schedule_recalculate_project_authorizations_third_run_spec.rb
index 19ba8984224..300bb940dd8 100644
--- a/spec/migrations/schedule_recalculate_project_authorizations_third_run_spec.rb
+++ b/spec/migrations/schedule_recalculate_project_authorizations_third_run_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200204113225_schedule_recalculate_project_authorizations_third_run.rb')
-describe ScheduleRecalculateProjectAuthorizationsThirdRun do
+RSpec.describe ScheduleRecalculateProjectAuthorizationsThirdRun do
let(:users_table) { table(:users) }
before do
diff --git a/spec/migrations/schedule_sync_issuables_state_id_spec.rb b/spec/migrations/schedule_sync_issuables_state_id_spec.rb
index 408e7e6d19d..ecfebbde348 100644
--- a/spec/migrations/schedule_sync_issuables_state_id_spec.rb
+++ b/spec/migrations/schedule_sync_issuables_state_id_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190214112022_schedule_sync_issuables_state_id.rb')
-describe ScheduleSyncIssuablesStateId do
+RSpec.describe ScheduleSyncIssuablesStateId do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:merge_requests) { table(:merge_requests) }
diff --git a/spec/migrations/schedule_sync_issuables_state_id_where_nil_spec.rb b/spec/migrations/schedule_sync_issuables_state_id_where_nil_spec.rb
index e26a864b8ba..d23f5b69d22 100644
--- a/spec/migrations/schedule_sync_issuables_state_id_where_nil_spec.rb
+++ b/spec/migrations/schedule_sync_issuables_state_id_where_nil_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190506135400_schedule_sync_issuables_state_id_where_nil')
-describe ScheduleSyncIssuablesStateIdWhereNil do
+RSpec.describe ScheduleSyncIssuablesStateIdWhereNil do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:merge_requests) { table(:merge_requests) }
diff --git a/spec/migrations/schedule_update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb b/spec/migrations/schedule_update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb
index 098fe68927c..949d8d8794f 100644
--- a/spec/migrations/schedule_update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb
+++ b/spec/migrations/schedule_update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200110121314_schedule_update_existing_subgroup_to_match_visibility_level_of_parent.rb')
-describe ScheduleUpdateExistingSubgroupToMatchVisibilityLevelOfParent do
+RSpec.describe ScheduleUpdateExistingSubgroupToMatchVisibilityLevelOfParent do
include MigrationHelpers::NamespacesHelpers
let(:migration_class) { described_class::MIGRATION }
let(:migration_name) { migration_class.to_s.demodulize }
diff --git a/spec/migrations/seed_repository_storages_weighted_spec.rb b/spec/migrations/seed_repository_storages_weighted_spec.rb
index 9a68ff5fb2f..fb077a3e345 100644
--- a/spec/migrations/seed_repository_storages_weighted_spec.rb
+++ b/spec/migrations/seed_repository_storages_weighted_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200526000407_seed_repository_storages_weighted.rb')
-describe SeedRepositoryStoragesWeighted do
+RSpec.describe SeedRepositoryStoragesWeighted do
let(:storages) { { "foo" => {}, "baz" => {} } }
let(:application_settings) do
table(:application_settings).tap do |klass|
diff --git a/spec/migrations/services_remove_temporary_index_on_project_id_spec.rb b/spec/migrations/services_remove_temporary_index_on_project_id_spec.rb
index d4f9969b71b..6cab4c16cfb 100644
--- a/spec/migrations/services_remove_temporary_index_on_project_id_spec.rb
+++ b/spec/migrations/services_remove_temporary_index_on_project_id_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200203104214_services_remove_temporary_index_on_project_id.rb')
-describe ServicesRemoveTemporaryIndexOnProjectId do
+RSpec.describe ServicesRemoveTemporaryIndexOnProjectId do
let(:migration_instance) { described_class.new }
it 'adds and removes temporary partial index in up and down methods' do
diff --git a/spec/migrations/set_issue_id_for_all_versions_spec.rb b/spec/migrations/set_issue_id_for_all_versions_spec.rb
index ff281947db8..0908d054e70 100644
--- a/spec/migrations/set_issue_id_for_all_versions_spec.rb
+++ b/spec/migrations/set_issue_id_for_all_versions_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20190715043954_set_issue_id_for_all_versions.rb')
-describe SetIssueIdForAllVersions do
+RSpec.describe SetIssueIdForAllVersions do
let(:projects) { table(:projects) }
let(:issues) { table(:issues) }
let(:designs) { table(:design_management_designs) }
diff --git a/spec/migrations/sync_issuables_state_id_spec.rb b/spec/migrations/sync_issuables_state_id_spec.rb
index 4bd30172cbe..dcddbca6a36 100644
--- a/spec/migrations/sync_issuables_state_id_spec.rb
+++ b/spec/migrations/sync_issuables_state_id_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190911251732_sync_issuables_state_id')
-describe SyncIssuablesStateId do
+RSpec.describe SyncIssuablesStateId do
let(:migration) { described_class.new }
describe '#up' do
diff --git a/spec/migrations/truncate_user_fullname_spec.rb b/spec/migrations/truncate_user_fullname_spec.rb
index a5e6a0a4fce..cb95c222790 100644
--- a/spec/migrations/truncate_user_fullname_spec.rb
+++ b/spec/migrations/truncate_user_fullname_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20190325080727_truncate_user_fullname.rb')
-describe TruncateUserFullname do
+RSpec.describe TruncateUserFullname do
let(:users) { table(:users) }
let(:user_short) { create_user(name: 'abc', email: 'test_short@example.com') }
diff --git a/spec/migrations/unconfirm_wrongfully_verified_emails_spec.rb b/spec/migrations/unconfirm_wrongfully_verified_emails_spec.rb
new file mode 100644
index 00000000000..e93f2cb64de
--- /dev/null
+++ b/spec/migrations/unconfirm_wrongfully_verified_emails_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200615111857_unconfirm_wrongfully_verified_emails.rb')
+
+RSpec.describe UnconfirmWrongfullyVerifiedEmails do
+ before do
+ user = table(:users).create!(name: 'user1', email: 'test1@test.com', projects_limit: 1)
+ table(:emails).create!(email: 'test2@test.com', user_id: user.id)
+ end
+
+ it 'enqueues WrongullyConfirmedEmailUnconfirmer job' do
+ Sidekiq::Testing.fake! do
+ migrate!
+
+ jobs = BackgroundMigrationWorker.jobs
+ expect(jobs.size).to eq(1)
+ expect(jobs.first["args"].first).to eq(Gitlab::BackgroundMigration::WrongfullyConfirmedEmailUnconfirmer.name.demodulize)
+ end
+ end
+end
diff --git a/spec/migrations/update_application_setting_npm_package_requests_forwarding_default_spec.rb b/spec/migrations/update_application_setting_npm_package_requests_forwarding_default_spec.rb
index f9523e0e582..8b241d1b28e 100644
--- a/spec/migrations/update_application_setting_npm_package_requests_forwarding_default_spec.rb
+++ b/spec/migrations/update_application_setting_npm_package_requests_forwarding_default_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200221105436_update_application_setting_npm_package_requests_forwarding_default.rb')
-describe UpdateApplicationSettingNpmPackageRequestsForwardingDefault do
+RSpec.describe UpdateApplicationSettingNpmPackageRequestsForwardingDefault do
# Create test data - pipeline and CI/CD jobs.
let(:application_settings) { table(:application_settings) }
diff --git a/spec/migrations/update_fingerprint_sha256_within_keys_spec.rb b/spec/migrations/update_fingerprint_sha256_within_keys_spec.rb
index d149ec230a7..7f5ae892391 100644
--- a/spec/migrations/update_fingerprint_sha256_within_keys_spec.rb
+++ b/spec/migrations/update_fingerprint_sha256_within_keys_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200106071113_update_fingerprint_sha256_within_keys.rb')
-describe UpdateFingerprintSha256WithinKeys do
+RSpec.describe UpdateFingerprintSha256WithinKeys do
let(:key_table) { table(:keys) }
describe '#up' do
diff --git a/spec/migrations/update_minimum_password_length_spec.rb b/spec/migrations/update_minimum_password_length_spec.rb
index ed9c85362f5..a4485dfbcea 100644
--- a/spec/migrations/update_minimum_password_length_spec.rb
+++ b/spec/migrations/update_minimum_password_length_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20191205084057_update_minimum_password_length')
-describe UpdateMinimumPasswordLength do
+RSpec.describe UpdateMinimumPasswordLength do
let(:application_settings) { table(:application_settings) }
let(:application_setting) do
application_settings.create!(
diff --git a/spec/migrations/update_routes_for_lost_and_found_group_and_orphaned_projects_spec.rb b/spec/migrations/update_routes_for_lost_and_found_group_and_orphaned_projects_spec.rb
index 103b6f114c4..ffab5c40182 100644
--- a/spec/migrations/update_routes_for_lost_and_found_group_and_orphaned_projects_spec.rb
+++ b/spec/migrations/update_routes_for_lost_and_found_group_and_orphaned_projects_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200602143020_update_routes_for_lost_and_found_group_and_orphaned_projects.rb')
-describe UpdateRoutesForLostAndFoundGroupAndOrphanedProjects, :migration do
+RSpec.describe UpdateRoutesForLostAndFoundGroupAndOrphanedProjects, :migration do
let(:users) { table(:users) }
let(:namespaces) { table(:namespaces) }
let(:members) { table(:members) }
diff --git a/spec/migrations/update_timestamp_softwarelicensespolicy_spec.rb b/spec/migrations/update_timestamp_softwarelicensespolicy_spec.rb
index 918c5fb567f..f55d55f94c7 100644
--- a/spec/migrations/update_timestamp_softwarelicensespolicy_spec.rb
+++ b/spec/migrations/update_timestamp_softwarelicensespolicy_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200116175538_update_timestamp_softwarelicensespolicy.rb')
-describe UpdateTimestampSoftwarelicensespolicy do
+RSpec.describe UpdateTimestampSoftwarelicensespolicy do
let(:software_licenses_policy) { table(:software_license_policies) }
let(:projects) { table(:projects) }
let(:licenses) { table(:software_licenses) }
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index 9ef77da6f43..9206f14fd37 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ability do
+RSpec.describe Ability do
context 'using a nil subject' do
it 'has no permissions' do
expect(described_class.policy_for(nil, nil)).to be_banned
diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb
index 2c4fa398636..a97574fa524 100644
--- a/spec/models/abuse_report_spec.rb
+++ b/spec/models/abuse_report_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AbuseReport do
+RSpec.describe AbuseReport do
let_it_be(:report, reload: true) { create(:abuse_report) }
let_it_be(:user, reload: true) { create(:admin) }
subject { report }
diff --git a/spec/models/alert_management/alert_assignee_spec.rb b/spec/models/alert_management/alert_assignee_spec.rb
index c51a5d543ab..c50a3ec0d01 100644
--- a/spec/models/alert_management/alert_assignee_spec.rb
+++ b/spec/models/alert_management/alert_assignee_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AlertManagement::AlertAssignee do
+RSpec.describe AlertManagement::AlertAssignee do
describe 'associations' do
it { is_expected.to belong_to(:alert) }
it { is_expected.to belong_to(:assignee) }
diff --git a/spec/models/alert_management/alert_spec.rb b/spec/models/alert_management/alert_spec.rb
index 27b8bb48073..becc5475c15 100644
--- a/spec/models/alert_management/alert_spec.rb
+++ b/spec/models/alert_management/alert_spec.rb
@@ -2,10 +2,12 @@
require 'spec_helper'
-describe AlertManagement::Alert do
+RSpec.describe AlertManagement::Alert do
describe 'associations' do
it { is_expected.to belong_to(:project) }
- it { is_expected.to belong_to(:issue) }
+ it { is_expected.to belong_to(:issue).optional }
+ it { is_expected.to belong_to(:prometheus_alert).optional }
+ it { is_expected.to belong_to(:environment).optional }
it { is_expected.to have_many(:assignees).through(:alert_assignees) }
it { is_expected.to have_many(:notes) }
it { is_expected.to have_many(:ordered_notes) }
@@ -81,21 +83,50 @@ describe AlertManagement::Alert do
end
describe 'fingerprint' do
+ let_it_be(:project) { create(:project) }
let_it_be(:fingerprint) { 'fingerprint' }
- let_it_be(:existing_alert) { create(:alert_management_alert, fingerprint: fingerprint) }
let(:new_alert) { build(:alert_management_alert, fingerprint: fingerprint, project: project) }
subject { new_alert }
context 'adding an alert with the same fingerprint' do
- context 'same project' do
- let(:project) { existing_alert.project }
-
- it { is_expected.not_to be_valid }
+ context 'same project, various states' do
+ using RSpec::Parameterized::TableSyntax
+
+ # We are only validating uniqueness for non-resolved alerts
+ where(:existing_status, :new_status, :valid) do
+ :resolved | :triggered | true
+ :resolved | :acknowledged | true
+ :resolved | :ignored | true
+ :resolved | :resolved | true
+ :triggered | :triggered | false
+ :triggered | :acknowledged | false
+ :triggered | :ignored | false
+ :triggered | :resolved | true
+ :acknowledged | :triggered | false
+ :acknowledged | :acknowledged | false
+ :acknowledged | :ignored | false
+ :acknowledged | :resolved | true
+ :ignored | :triggered | false
+ :ignored | :acknowledged | false
+ :ignored | :ignored | false
+ :ignored | :resolved | true
+ end
+
+ with_them do
+ let!(:existing_alert) { create(:alert_management_alert, existing_status, fingerprint: fingerprint, project: project) }
+ let(:new_alert) { build(:alert_management_alert, new_status, fingerprint: fingerprint, project: project) }
+
+ if params[:valid]
+ it { is_expected.to be_valid }
+ else
+ it { is_expected.to be_invalid }
+ end
+ end
end
context 'different project' do
- let(:project) { create(:project) }
+ let!(:existing_alert) { create(:alert_management_alert, fingerprint: fingerprint) }
it { is_expected.to be_valid }
end
@@ -163,6 +194,15 @@ describe AlertManagement::Alert do
it { is_expected.to contain_exactly(alert_with_fingerprint) }
end
+ describe '.for_environment' do
+ let(:environment) { create(:environment, project: project) }
+ let!(:env_alert) { create(:alert_management_alert, project: project, environment: environment) }
+
+ subject { described_class.for_environment(environment) }
+
+ it { is_expected.to match_array(env_alert) }
+ end
+
describe '.counts_by_status' do
subject { described_class.counts_by_status }
@@ -174,6 +214,51 @@ describe AlertManagement::Alert do
)
end
end
+
+ describe '.counts_by_project_id' do
+ subject { described_class.counts_by_project_id }
+
+ let!(:alert_other_project) { create(:alert_management_alert) }
+
+ it do
+ is_expected.to eq(
+ project.id => 3,
+ alert_other_project.project.id => 1
+ )
+ end
+ end
+
+ describe '.open' do
+ subject { described_class.open }
+
+ let!(:acknowledged_alert) { create(:alert_management_alert, :acknowledged, project: project)}
+
+ it { is_expected.to contain_exactly(acknowledged_alert, triggered_alert) }
+ end
+
+ describe '.not_resolved' do
+ subject { described_class.not_resolved }
+
+ let!(:acknowledged_alert) { create(:alert_management_alert, :acknowledged, project: project) }
+
+ it { is_expected.to contain_exactly(acknowledged_alert, triggered_alert, ignored_alert) }
+ end
+ end
+
+ describe '.last_prometheus_alert_by_project_id' do
+ subject { described_class.last_prometheus_alert_by_project_id }
+
+ let(:project_1) { create(:project) }
+ let!(:alert_1) { create(:alert_management_alert, project: project_1) }
+ let!(:alert_2) { create(:alert_management_alert, project: project_1) }
+
+ let(:project_2) { create(:project) }
+ let!(:alert_3) { create(:alert_management_alert, project: project_2) }
+ let!(:alert_4) { create(:alert_management_alert, project: project_2) }
+
+ it 'returns the latest alert for each project' do
+ expect(subject).to contain_exactly(alert_2, alert_4)
+ end
end
describe '.search' do
@@ -337,4 +422,22 @@ describe AlertManagement::Alert do
expect { subject }.to change { alert.events }.by(1)
end
end
+
+ describe '#present' do
+ context 'when alert is generic' do
+ let(:alert) { build(:alert_management_alert) }
+
+ it 'uses generic alert presenter' do
+ expect(alert.present).to be_kind_of(AlertManagement::AlertPresenter)
+ end
+ end
+
+ context 'when alert is Prometheus specific' do
+ let(:alert) { build(:alert_management_alert, :prometheus) }
+
+ it 'uses Prometheus Alert presenter' do
+ expect(alert.present).to be_kind_of(AlertManagement::PrometheusAlertPresenter)
+ end
+ end
+ end
end
diff --git a/spec/models/alert_management/alert_user_mention_spec.rb b/spec/models/alert_management/alert_user_mention_spec.rb
index cce090a2231..27c3d290dde 100644
--- a/spec/models/alert_management/alert_user_mention_spec.rb
+++ b/spec/models/alert_management/alert_user_mention_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AlertManagement::AlertUserMention do
+RSpec.describe AlertManagement::AlertUserMention do
describe 'associations' do
it { is_expected.to belong_to(:alert_management_alert) }
it { is_expected.to belong_to(:note) }
diff --git a/spec/models/alerting/project_alerting_setting_spec.rb b/spec/models/alerting/project_alerting_setting_spec.rb
index 40fbe3a6e78..90c5f8313b0 100644
--- a/spec/models/alerting/project_alerting_setting_spec.rb
+++ b/spec/models/alerting/project_alerting_setting_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Alerting::ProjectAlertingSetting do
+RSpec.describe Alerting::ProjectAlertingSetting do
let_it_be(:project) { create(:project) }
subject { create(:project_alerting_setting, project: project) }
diff --git a/spec/models/analytics/cycle_analytics/project_stage_spec.rb b/spec/models/analytics/cycle_analytics/project_stage_spec.rb
index 9850bfde30e..2e024011553 100644
--- a/spec/models/analytics/cycle_analytics/project_stage_spec.rb
+++ b/spec/models/analytics/cycle_analytics/project_stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Analytics::CycleAnalytics::ProjectStage do
+RSpec.describe Analytics::CycleAnalytics::ProjectStage do
describe 'associations' do
it { is_expected.to belong_to(:project) }
end
diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb
index 2c32028c3e5..37eddf9a22a 100644
--- a/spec/models/appearance_spec.rb
+++ b/spec/models/appearance_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Appearance do
+RSpec.describe Appearance do
subject { build(:appearance) }
it { include(CacheableAttributes) }
diff --git a/spec/models/application_record_spec.rb b/spec/models/application_record_spec.rb
index 74573d0941c..cc314d9077d 100644
--- a/spec/models/application_record_spec.rb
+++ b/spec/models/application_record_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ApplicationRecord do
+RSpec.describe ApplicationRecord do
describe '#id_in' do
let(:records) { create_list(:user, 3) }
@@ -58,4 +58,11 @@ describe ApplicationRecord do
expect(MergeRequest.underscore).to eq('merge_request')
end
end
+
+ describe '.at_most' do
+ it 'limits the number of records returned' do
+ create_list(:user, 3)
+ expect(User.at_most(2).count).to eq(2)
+ end
+ end
end
diff --git a/spec/models/application_setting/term_spec.rb b/spec/models/application_setting/term_spec.rb
index dd263335b81..82347453437 100644
--- a/spec/models/application_setting/term_spec.rb
+++ b/spec/models/application_setting/term_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ApplicationSetting::Term do
+RSpec.describe ApplicationSetting::Term do
describe 'validations' do
it { is_expected.to validate_presence_of(:terms) }
end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 96bf19439a1..f618e13ca26 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ApplicationSetting do
+RSpec.describe ApplicationSetting do
using RSpec::Parameterized::TableSyntax
subject(:setting) { described_class.create_from_defaults }
diff --git a/spec/models/approval_spec.rb b/spec/models/approval_spec.rb
new file mode 100644
index 00000000000..e2c0d5faa07
--- /dev/null
+++ b/spec/models/approval_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Approval do
+ context 'presence validation' do
+ it { is_expected.to validate_presence_of(:merge_request_id) }
+ it { is_expected.to validate_presence_of(:user_id) }
+ end
+
+ context 'uniqueness validation' do
+ let!(:existing_record) { create(:approval) }
+
+ it { is_expected.to validate_uniqueness_of(:user_id).scoped_to([:merge_request_id]) }
+ end
+end
diff --git a/spec/models/award_emoji_spec.rb b/spec/models/award_emoji_spec.rb
index 8b370e80c9b..f268408c095 100644
--- a/spec/models/award_emoji_spec.rb
+++ b/spec/models/award_emoji_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AwardEmoji do
+RSpec.describe AwardEmoji do
describe 'Associations' do
it { is_expected.to belong_to(:awardable) }
it { is_expected.to belong_to(:user) }
diff --git a/spec/models/aws/role_spec.rb b/spec/models/aws/role_spec.rb
index d4165567146..612868f6eb0 100644
--- a/spec/models/aws/role_spec.rb
+++ b/spec/models/aws/role_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Aws::Role do
+RSpec.describe Aws::Role do
it { is_expected.to belong_to(:user) }
it { is_expected.to validate_length_of(:role_external_id).is_at_least(1).is_at_most(64) }
diff --git a/spec/models/badge_spec.rb b/spec/models/badge_spec.rb
index fba8f40e99b..f3c95332ca0 100644
--- a/spec/models/badge_spec.rb
+++ b/spec/models/badge_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Badge do
+RSpec.describe Badge do
let(:placeholder_url) { 'http://www.example.com/%{project_path}/%{project_id}/%{default_branch}/%{commit_sha}' }
describe 'validations' do
diff --git a/spec/models/badges/group_badge_spec.rb b/spec/models/badges/group_badge_spec.rb
index c297bc957ea..8913a2e6f17 100644
--- a/spec/models/badges/group_badge_spec.rb
+++ b/spec/models/badges/group_badge_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupBadge do
+RSpec.describe GroupBadge do
describe 'associations' do
it { is_expected.to belong_to(:group) }
end
diff --git a/spec/models/badges/project_badge_spec.rb b/spec/models/badges/project_badge_spec.rb
index c0e85d3de87..9b9836129a6 100644
--- a/spec/models/badges/project_badge_spec.rb
+++ b/spec/models/badges/project_badge_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectBadge do
+RSpec.describe ProjectBadge do
let(:placeholder_url) { 'http://www.example.com/%{project_path}/%{project_id}/%{default_branch}/%{commit_sha}' }
describe 'associations' do
diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb
index c2d6406c3fb..bd4832bd978 100644
--- a/spec/models/blob_spec.rb
+++ b/spec/models/blob_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Blob do
+RSpec.describe Blob do
include FakeBlobHelpers
using RSpec::Parameterized::TableSyntax
diff --git a/spec/models/blob_viewer/base_spec.rb b/spec/models/blob_viewer/base_spec.rb
index 39c7a34f052..682b6dc3b1d 100644
--- a/spec/models/blob_viewer/base_spec.rb
+++ b/spec/models/blob_viewer/base_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::Base do
+RSpec.describe BlobViewer::Base do
include FakeBlobHelpers
let(:project) { build(:project) }
diff --git a/spec/models/blob_viewer/changelog_spec.rb b/spec/models/blob_viewer/changelog_spec.rb
index b71531ff3c2..5346483cfc8 100644
--- a/spec/models/blob_viewer/changelog_spec.rb
+++ b/spec/models/blob_viewer/changelog_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::Changelog do
+RSpec.describe BlobViewer::Changelog do
include FakeBlobHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/models/blob_viewer/composer_json_spec.rb b/spec/models/blob_viewer/composer_json_spec.rb
index a6bb64ba121..8d66e9e951f 100644
--- a/spec/models/blob_viewer/composer_json_spec.rb
+++ b/spec/models/blob_viewer/composer_json_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::ComposerJson do
+RSpec.describe BlobViewer::ComposerJson do
include FakeBlobHelpers
let(:project) { build_stubbed(:project) }
diff --git a/spec/models/blob_viewer/gemspec_spec.rb b/spec/models/blob_viewer/gemspec_spec.rb
index 291d14e2d72..b6f3e059c7e 100644
--- a/spec/models/blob_viewer/gemspec_spec.rb
+++ b/spec/models/blob_viewer/gemspec_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::Gemspec do
+RSpec.describe BlobViewer::Gemspec do
include FakeBlobHelpers
let(:project) { build_stubbed(:project) }
diff --git a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
index e645733e02d..cd885d312dc 100644
--- a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
+++ b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::GitlabCiYml do
+RSpec.describe BlobViewer::GitlabCiYml do
include FakeBlobHelpers
include RepoHelpers
diff --git a/spec/models/blob_viewer/go_mod_spec.rb b/spec/models/blob_viewer/go_mod_spec.rb
index ba6038533ea..21e84d39a54 100644
--- a/spec/models/blob_viewer/go_mod_spec.rb
+++ b/spec/models/blob_viewer/go_mod_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::GoMod do
+RSpec.describe BlobViewer::GoMod do
include FakeBlobHelpers
let(:project) { build_stubbed(:project) }
diff --git a/spec/models/blob_viewer/license_spec.rb b/spec/models/blob_viewer/license_spec.rb
index b0426401932..bc970136503 100644
--- a/spec/models/blob_viewer/license_spec.rb
+++ b/spec/models/blob_viewer/license_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::License do
+RSpec.describe BlobViewer::License do
include FakeBlobHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/models/blob_viewer/metrics_dashboard_yml_spec.rb b/spec/models/blob_viewer/metrics_dashboard_yml_spec.rb
index f5b8586975d..057f0f32158 100644
--- a/spec/models/blob_viewer/metrics_dashboard_yml_spec.rb
+++ b/spec/models/blob_viewer/metrics_dashboard_yml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::MetricsDashboardYml do
+RSpec.describe BlobViewer::MetricsDashboardYml do
include FakeBlobHelpers
include RepoHelpers
diff --git a/spec/models/blob_viewer/package_json_spec.rb b/spec/models/blob_viewer/package_json_spec.rb
index 7f7b1dcfcb3..d2e8ab6575f 100644
--- a/spec/models/blob_viewer/package_json_spec.rb
+++ b/spec/models/blob_viewer/package_json_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::PackageJson do
+RSpec.describe BlobViewer::PackageJson do
include FakeBlobHelpers
let(:project) { build_stubbed(:project) }
diff --git a/spec/models/blob_viewer/podspec_json_spec.rb b/spec/models/blob_viewer/podspec_json_spec.rb
index dd5ed03b77d..61d2602c413 100644
--- a/spec/models/blob_viewer/podspec_json_spec.rb
+++ b/spec/models/blob_viewer/podspec_json_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::PodspecJson do
+RSpec.describe BlobViewer::PodspecJson do
include FakeBlobHelpers
let(:project) { build_stubbed(:project) }
diff --git a/spec/models/blob_viewer/podspec_spec.rb b/spec/models/blob_viewer/podspec_spec.rb
index 2d9b184c5cb..0a0fbcaebd4 100644
--- a/spec/models/blob_viewer/podspec_spec.rb
+++ b/spec/models/blob_viewer/podspec_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::Podspec do
+RSpec.describe BlobViewer::Podspec do
include FakeBlobHelpers
let(:project) { build_stubbed(:project) }
diff --git a/spec/models/blob_viewer/readme_spec.rb b/spec/models/blob_viewer/readme_spec.rb
index 89bc5be94fb..4c5f11d55ff 100644
--- a/spec/models/blob_viewer/readme_spec.rb
+++ b/spec/models/blob_viewer/readme_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::Readme do
+RSpec.describe BlobViewer::Readme do
include FakeBlobHelpers
let(:project) { create(:project, :repository, :wiki_repo) }
diff --git a/spec/models/blob_viewer/route_map_spec.rb b/spec/models/blob_viewer/route_map_spec.rb
index 6c703df5c4c..bb0284d7868 100644
--- a/spec/models/blob_viewer/route_map_spec.rb
+++ b/spec/models/blob_viewer/route_map_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::RouteMap do
+RSpec.describe BlobViewer::RouteMap do
include FakeBlobHelpers
let(:project) { build_stubbed(:project) }
diff --git a/spec/models/blob_viewer/server_side_spec.rb b/spec/models/blob_viewer/server_side_spec.rb
index f95305abe78..284ac4524f7 100644
--- a/spec/models/blob_viewer/server_side_spec.rb
+++ b/spec/models/blob_viewer/server_side_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobViewer::ServerSide do
+RSpec.describe BlobViewer::ServerSide do
include FakeBlobHelpers
let(:project) { build(:project) }
diff --git a/spec/models/board_group_recent_visit_spec.rb b/spec/models/board_group_recent_visit_spec.rb
index 558be61824f..4d16e1ff839 100644
--- a/spec/models/board_group_recent_visit_spec.rb
+++ b/spec/models/board_group_recent_visit_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BoardGroupRecentVisit do
+RSpec.describe BoardGroupRecentVisit do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:board) { create(:board, group: group) }
diff --git a/spec/models/board_project_recent_visit_spec.rb b/spec/models/board_project_recent_visit_spec.rb
index e404fb3bbdb..8e74405fd8c 100644
--- a/spec/models/board_project_recent_visit_spec.rb
+++ b/spec/models/board_project_recent_visit_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BoardProjectRecentVisit do
+RSpec.describe BoardProjectRecentVisit do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:board) { create(:board, project: project) }
diff --git a/spec/models/board_spec.rb b/spec/models/board_spec.rb
index 2d5309b4d23..d309b4dbdb9 100644
--- a/spec/models/board_spec.rb
+++ b/spec/models/board_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Board do
+RSpec.describe Board do
let(:project) { create(:project) }
let(:other_project) { create(:project) }
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index 8032f913d86..fc463c6af52 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BroadcastMessage do
+RSpec.describe BroadcastMessage do
subject { build(:broadcast_message) }
it { is_expected.to be_valid }
diff --git a/spec/models/chat_name_spec.rb b/spec/models/chat_name_spec.rb
index 02594b98665..623e55aad21 100644
--- a/spec/models/chat_name_spec.rb
+++ b/spec/models/chat_name_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatName do
+RSpec.describe ChatName do
let_it_be(:chat_name) { create(:chat_name) }
subject { chat_name }
diff --git a/spec/models/chat_team_spec.rb b/spec/models/chat_team_spec.rb
index 107fdaccc68..08fd05324aa 100644
--- a/spec/models/chat_team_spec.rb
+++ b/spec/models/chat_team_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatTeam do
+RSpec.describe ChatTeam do
let_it_be(:chat_team) { create(:chat_team) }
subject { chat_team }
diff --git a/spec/models/ci/artifact_blob_spec.rb b/spec/models/ci/artifact_blob_spec.rb
index 99983686670..44f895cc1c5 100644
--- a/spec/models/ci/artifact_blob_spec.rb
+++ b/spec/models/ci/artifact_blob_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::ArtifactBlob do
+RSpec.describe Ci::ArtifactBlob do
let_it_be(:project) { create(:project, :public) }
let_it_be(:build) { create(:ci_build, :artifacts, project: project) }
let(:entry) { build.artifacts_metadata_entry('other_artifacts_0.1.2/another-subdirectory/banana_sample.gif') }
diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb
index 385261e0ee9..3a459e5897a 100644
--- a/spec/models/ci/bridge_spec.rb
+++ b/spec/models/ci/bridge_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Bridge do
+RSpec.describe Ci::Bridge do
let_it_be(:project) { create(:project) }
let_it_be(:target_project) { create(:project, name: 'project', namespace: create(:namespace, name: 'my')) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
@@ -47,8 +47,8 @@ describe Ci::Bridge do
CI_JOB_NAME CI_JOB_STAGE CI_COMMIT_SHA CI_COMMIT_SHORT_SHA
CI_COMMIT_BEFORE_SHA CI_COMMIT_REF_NAME CI_COMMIT_REF_SLUG
CI_PROJECT_ID CI_PROJECT_NAME CI_PROJECT_PATH
- CI_PROJECT_PATH_SLUG CI_PROJECT_NAMESPACE CI_PIPELINE_IID
- CI_CONFIG_PATH CI_PIPELINE_SOURCE CI_COMMIT_MESSAGE
+ CI_PROJECT_PATH_SLUG CI_PROJECT_NAMESPACE CI_PROJECT_ROOT_NAMESPACE
+ CI_PIPELINE_IID CI_CONFIG_PATH CI_PIPELINE_SOURCE CI_COMMIT_MESSAGE
CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION CI_COMMIT_REF_PROTECTED
]
diff --git a/spec/models/ci/build_dependencies_spec.rb b/spec/models/ci/build_dependencies_spec.rb
index 8f2199ac360..4fa1b3eb5a5 100644
--- a/spec/models/ci/build_dependencies_spec.rb
+++ b/spec/models/ci/build_dependencies_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildDependencies do
+RSpec.describe Ci::BuildDependencies do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :repository) }
diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb
index 588e5872cc8..e4d71632957 100644
--- a/spec/models/ci/build_metadata_spec.rb
+++ b/spec/models/ci/build_metadata_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildMetadata do
+RSpec.describe Ci::BuildMetadata do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :repository, group: group, build_timeout: 2000) }
@@ -92,4 +92,33 @@ describe Ci::BuildMetadata do
end
end
end
+
+ describe 'validations' do
+ context 'when attributes are valid' do
+ it 'returns no errors' do
+ metadata.secrets = {
+ DATABASE_PASSWORD: {
+ vault: {
+ engine: { name: 'kv-v2', path: 'kv-v2' },
+ path: 'production/db',
+ field: 'password'
+ }
+ }
+ }
+
+ expect(metadata).to be_valid
+ end
+ end
+
+ context 'when data is invalid' do
+ it 'returns errors' do
+ metadata.secrets = { DATABASE_PASSWORD: { vault: {} } }
+
+ aggregate_failures do
+ expect(metadata).to be_invalid
+ expect(metadata.errors.full_messages).to eq(["Secrets must be a valid json schema"])
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/ci/build_need_spec.rb b/spec/models/ci/build_need_spec.rb
index d1186fa981d..43cce073918 100644
--- a/spec/models/ci/build_need_spec.rb
+++ b/spec/models/ci/build_need_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildNeed, model: true do
+RSpec.describe Ci::BuildNeed, model: true do
let(:build_need) { build(:ci_build_need) }
it { is_expected.to belong_to(:build) }
@@ -17,4 +17,22 @@ describe Ci::BuildNeed, model: true do
it { expect(described_class.artifacts).to contain_exactly(with_artifacts) }
end
+
+ describe 'BulkInsertSafe' do
+ let(:ci_build) { build(:ci_build) }
+
+ it "bulk inserts from Ci::Build model" do
+ ci_build.needs_attributes = [
+ { name: "build", artifacts: true },
+ { name: "build2", artifacts: true },
+ { name: "build3", artifacts: true }
+ ]
+
+ expect(described_class).to receive(:bulk_insert!).and_call_original
+
+ BulkInsertableAssociations.with_bulk_insert do
+ ci_build.save!
+ end
+ end
+ end
end
diff --git a/spec/models/ci/build_report_result_spec.rb b/spec/models/ci/build_report_result_spec.rb
index 078b0d100a1..e78f602feef 100644
--- a/spec/models/ci/build_report_result_spec.rb
+++ b/spec/models/ci/build_report_result_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildReportResult do
+RSpec.describe Ci::BuildReportResult do
let(:build_report_result) { build(:ci_build_report_result, :with_junit_success) }
describe 'associations' do
diff --git a/spec/models/ci/build_runner_session_spec.rb b/spec/models/ci/build_runner_session_spec.rb
index 3e520407884..601c6ad26f9 100644
--- a/spec/models/ci/build_runner_session_spec.rb
+++ b/spec/models/ci/build_runner_session_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildRunnerSession, model: true do
+RSpec.describe Ci::BuildRunnerSession, model: true do
let!(:build) { create(:ci_build, :with_runner_session) }
let(:url) { 'https://new.example.com' }
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 6fdd8463329..857b238981b 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Build do
+RSpec.describe Ci::Build do
let_it_be(:user) { create(:user) }
let_it_be(:group, reload: true) { create(:group) }
let_it_be(:project, reload: true) { create(:project, :repository, group: group) }
@@ -1811,6 +1811,50 @@ describe Ci::Build do
end
end
+ describe '.keep_artifacts!' do
+ let!(:build) { create(:ci_build, artifacts_expire_at: Time.current + 7.days) }
+ let!(:builds_for_update) do
+ Ci::Build.where(id: create_list(:ci_build, 3, artifacts_expire_at: Time.current + 7.days).map(&:id))
+ end
+
+ it 'resets expire_at' do
+ builds_for_update.keep_artifacts!
+
+ builds_for_update.each do |build|
+ expect(build.reload.artifacts_expire_at).to be_nil
+ end
+ end
+
+ it 'does not reset expire_at for other builds' do
+ builds_for_update.keep_artifacts!
+
+ expect(build.reload.artifacts_expire_at).to be_present
+ end
+
+ context 'when having artifacts files' do
+ let!(:artifact) { create(:ci_job_artifact, job: build, expire_in: '7 days') }
+ let!(:artifacts_for_update) do
+ builds_for_update.map do |build|
+ create(:ci_job_artifact, job: build, expire_in: '7 days')
+ end
+ end
+
+ it 'resets dependent objects' do
+ builds_for_update.keep_artifacts!
+
+ artifacts_for_update.each do |artifact|
+ expect(artifact.reload.expire_at).to be_nil
+ end
+ end
+
+ it 'does not reset dependent object for other builds' do
+ builds_for_update.keep_artifacts!
+
+ expect(artifact.reload.expire_at).to be_present
+ end
+ end
+ end
+
describe '#keep_artifacts!' do
let(:build) { create(:ci_build, artifacts_expire_at: Time.current + 7.days) }
@@ -2336,6 +2380,7 @@ describe Ci::Build do
{ key: 'CI_PROJECT_PATH', value: project.full_path, public: true, masked: false },
{ key: 'CI_PROJECT_PATH_SLUG', value: project.full_path_slug, public: true, masked: false },
{ key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true, masked: false },
+ { key: 'CI_PROJECT_ROOT_NAMESPACE', value: project.namespace.root_ancestor.path, public: true, masked: false },
{ key: 'CI_PROJECT_URL', value: project.web_url, public: true, masked: false },
{ key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true, masked: false },
{ key: 'CI_PROJECT_REPOSITORY_LANGUAGES', value: project.repository_languages.map(&:name).join(',').downcase, public: true, masked: false },
@@ -2929,19 +2974,6 @@ describe Ci::Build do
it { is_expected.to include(deployment_variable) }
end
- context 'when build has a freeze period' do
- let(:freeze_variable) { { key: 'CI_DEPLOY_FREEZE', value: 'true', masked: false, public: true } }
-
- before do
- expect_next_instance_of(Ci::FreezePeriodStatus) do |freeze_period|
- expect(freeze_period).to receive(:execute)
- .and_return(true)
- end
- end
-
- it { is_expected.to include(freeze_variable) }
- end
-
context 'when project has default CI config path' do
let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: '.gitlab-ci.yml', public: true, masked: false } }
@@ -3269,17 +3301,6 @@ describe Ci::Build do
expect(build.scoped_variables_hash).not_to include('MY_VAR': 'my value 1')
end
end
-
- context 'when CI instance variables are disabled' do
- before do
- create(:ci_instance_variable, key: 'MY_VAR', value: 'my value 1')
- stub_feature_flags(ci_instance_level_variables: false)
- end
-
- it 'does not include instance level variables' do
- expect(build.scoped_variables_hash).not_to include('MY_VAR': 'my value 1')
- end
- end
end
describe '#any_unmet_prerequisites?' do
@@ -4050,6 +4071,10 @@ describe Ci::Build do
it 'parses blobs and add the results to the terraform report' do
expect { build.collect_terraform_reports!(terraform_reports) }.not_to raise_error
+ terraform_reports.plans.each do |key, hash_value|
+ expect(hash_value.keys).to match_array(%w[create delete job_id job_name job_path update])
+ end
+
expect(terraform_reports.plans).to match(
a_hash_including(
build.id.to_s => a_hash_including(
@@ -4068,9 +4093,19 @@ describe Ci::Build do
create(:ci_job_artifact, :terraform_with_corrupted_data, job: build, project: build.project)
end
- it 'raises an error' do
- expect { build.collect_terraform_reports!(terraform_reports) }.to raise_error(
- Gitlab::Ci::Parsers::Terraform::Tfplan::TfplanParserError
+ it 'adds invalid plan report' do
+ expect { build.collect_terraform_reports!(terraform_reports) }.not_to raise_error
+
+ terraform_reports.plans.each do |key, hash_value|
+ expect(hash_value.keys).to match_array(%w[job_id job_name job_path tf_report_error])
+ end
+
+ expect(terraform_reports.plans).to match(
+ a_hash_including(
+ build.id.to_s => a_hash_including(
+ 'tf_report_error' => :invalid_json_format
+ )
+ )
)
end
end
@@ -4258,15 +4293,15 @@ describe Ci::Build do
end
end
- context 'when `release_steps` feature is required by build' do
+ context 'when `multi_build_steps` feature is required by build' do
before do
expect(build).to receive(:runner_required_feature_names) do
- [:release_steps]
+ [:multi_build_steps]
end
end
context 'when runner provides given feature' do
- let(:runner_features) { { release_steps: true } }
+ let(:runner_features) { { multi_build_steps: true } }
it { is_expected.to be_truthy }
end
diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb
index 85873847fca..dab523f67ab 100644
--- a/spec/models/ci/build_trace_chunk_spec.rb
+++ b/spec/models/ci/build_trace_chunk_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
+RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
include ExclusiveLeaseHelpers
let_it_be(:build) { create(:ci_build, :running) }
diff --git a/spec/models/ci/build_trace_chunks/database_spec.rb b/spec/models/ci/build_trace_chunks/database_spec.rb
index eb94d7dae38..245625b8046 100644
--- a/spec/models/ci/build_trace_chunks/database_spec.rb
+++ b/spec/models/ci/build_trace_chunks/database_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildTraceChunks::Database do
+RSpec.describe Ci::BuildTraceChunks::Database do
let(:data_store) { described_class.new }
describe '#available?' do
diff --git a/spec/models/ci/build_trace_chunks/fog_spec.rb b/spec/models/ci/build_trace_chunks/fog_spec.rb
index b8d78bcd069..7ef3018d87b 100644
--- a/spec/models/ci/build_trace_chunks/fog_spec.rb
+++ b/spec/models/ci/build_trace_chunks/fog_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildTraceChunks::Fog do
+RSpec.describe Ci::BuildTraceChunks::Fog do
let(:data_store) { described_class.new }
before do
diff --git a/spec/models/ci/build_trace_chunks/redis_spec.rb b/spec/models/ci/build_trace_chunks/redis_spec.rb
index 6cff33d24fa..c37b8697a4d 100644
--- a/spec/models/ci/build_trace_chunks/redis_spec.rb
+++ b/spec/models/ci/build_trace_chunks/redis_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildTraceChunks::Redis, :clean_gitlab_redis_shared_state do
+RSpec.describe Ci::BuildTraceChunks::Redis, :clean_gitlab_redis_shared_state do
let(:data_store) { described_class.new }
describe '#available?' do
diff --git a/spec/models/ci/build_trace_section_name_spec.rb b/spec/models/ci/build_trace_section_name_spec.rb
index 11e2d27ff79..b220e67d48e 100644
--- a/spec/models/ci/build_trace_section_name_spec.rb
+++ b/spec/models/ci/build_trace_section_name_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildTraceSectionName, model: true do
+RSpec.describe Ci::BuildTraceSectionName, model: true do
subject { build(:ci_build_trace_section_name) }
it { is_expected.to belong_to(:project) }
diff --git a/spec/models/ci/build_trace_section_spec.rb b/spec/models/ci/build_trace_section_spec.rb
index 5bd3a953ec0..640bd202b3a 100644
--- a/spec/models/ci/build_trace_section_spec.rb
+++ b/spec/models/ci/build_trace_section_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildTraceSection, model: true do
+RSpec.describe Ci::BuildTraceSection, model: true do
it { is_expected.to belong_to(:build)}
it { is_expected.to belong_to(:project)}
it { is_expected.to belong_to(:section_name)}
diff --git a/spec/models/ci/build_trace_spec.rb b/spec/models/ci/build_trace_spec.rb
index 2471a6fa827..3beca0565c6 100644
--- a/spec/models/ci/build_trace_spec.rb
+++ b/spec/models/ci/build_trace_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildTrace do
+RSpec.describe Ci::BuildTrace do
let(:build) { build_stubbed(:ci_build) }
let(:state) { nil }
let(:data) { StringIO.new('the-stream') }
@@ -11,7 +11,7 @@ describe Ci::BuildTrace do
Gitlab::Ci::Trace::Stream.new { data }
end
- subject { described_class.new(build: build, stream: stream, state: state, content_format: content_format) }
+ subject { described_class.new(build: build, stream: stream, state: state) }
shared_examples 'delegates methods' do
it { is_expected.to delegate_method(:state).to(:trace) }
@@ -25,29 +25,11 @@ describe Ci::BuildTrace do
it { is_expected.to delegate_method(:complete?).to(:build).with_prefix }
end
- context 'with :json content format' do
- let(:content_format) { :json }
+ it_behaves_like 'delegates methods'
- it_behaves_like 'delegates methods'
-
- it { is_expected.to be_json }
-
- it 'returns formatted trace' do
- expect(subject.trace.lines).to eq([
- { offset: 0, content: [{ text: 'the-stream' }] }
- ])
- end
- end
-
- context 'with :html content format' do
- let(:content_format) { :html }
-
- it_behaves_like 'delegates methods'
-
- it { is_expected.to be_html }
-
- it 'returns formatted trace' do
- expect(subject.trace.html).to eq('<span>the-stream</span>')
- end
+ it 'returns formatted trace' do
+ expect(subject.lines).to eq([
+ { offset: 0, content: [{ text: 'the-stream' }] }
+ ])
end
end
diff --git a/spec/models/ci/daily_build_group_report_result_spec.rb b/spec/models/ci/daily_build_group_report_result_spec.rb
index f2ce1b5775f..059a5b76b9a 100644
--- a/spec/models/ci/daily_build_group_report_result_spec.rb
+++ b/spec/models/ci/daily_build_group_report_result_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::DailyBuildGroupReportResult do
+RSpec.describe Ci::DailyBuildGroupReportResult do
let(:daily_build_group_report_result) { build(:ci_daily_build_group_report_result)}
describe 'associations' do
diff --git a/spec/models/ci/freeze_period_status_spec.rb b/spec/models/ci/freeze_period_status_spec.rb
index b700ec8c45f..831895cb528 100644
--- a/spec/models/ci/freeze_period_status_spec.rb
+++ b/spec/models/ci/freeze_period_status_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Ci::FreezePeriodStatus do
+RSpec.describe Ci::FreezePeriodStatus do
let(:project) { create :project }
# '0 23 * * 5' == "At 23:00 on Friday."", '0 7 * * 1' == "At 07:00 on Monday.""
let(:friday_2300) { '0 23 * * 5' }
diff --git a/spec/models/ci/group_spec.rb b/spec/models/ci/group_spec.rb
index 868382e3756..dc9aee906ea 100644
--- a/spec/models/ci/group_spec.rb
+++ b/spec/models/ci/group_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Group do
+RSpec.describe Ci::Group do
let_it_be(:project) { create(:project) }
let!(:jobs) { build_list(:ci_build, 1, :success, project: project) }
diff --git a/spec/models/ci/group_variable_spec.rb b/spec/models/ci/group_variable_spec.rb
index 610db9bf0e5..c8eac4d8765 100644
--- a/spec/models/ci/group_variable_spec.rb
+++ b/spec/models/ci/group_variable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::GroupVariable do
+RSpec.describe Ci::GroupVariable do
subject { build(:ci_group_variable) }
it_behaves_like "CI variable"
diff --git a/spec/models/ci/instance_variable_spec.rb b/spec/models/ci/instance_variable_spec.rb
index 4d69b7ac2f8..344ba5bfafd 100644
--- a/spec/models/ci/instance_variable_spec.rb
+++ b/spec/models/ci/instance_variable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::InstanceVariable do
+RSpec.describe Ci::InstanceVariable do
subject { build(:ci_instance_variable) }
it_behaves_like "CI variable"
@@ -15,21 +15,6 @@ describe Ci::InstanceVariable do
subject { build(:ci_instance_variable) }
end
- context 'with instance level variable feature flag disabled' do
- let(:plan_limits) { create(:plan_limits, :default_plan) }
-
- before do
- stub_feature_flags(ci_instance_level_variables_limit: false)
- plan_limits.update(described_class.limit_name => 1)
- create(:ci_instance_variable)
- end
-
- it 'can create new models exceeding the plan limits', :aggregate_failures do
- expect { subject.save }.to change { described_class.count }
- expect(subject.errors[:base]).to be_empty
- end
- end
-
describe '.unprotected' do
subject { described_class.unprotected }
diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb
index 17e00533ac3..b5f9128b7c5 100644
--- a/spec/models/ci/job_artifact_spec.rb
+++ b/spec/models/ci/job_artifact_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::JobArtifact do
+RSpec.describe Ci::JobArtifact do
let(:artifact) { create(:ci_job_artifact, :archive) }
describe "Associations" do
@@ -110,6 +110,21 @@ describe Ci::JobArtifact do
end
end
+ describe '.associated_file_types_for' do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { Ci::JobArtifact.associated_file_types_for(file_type) }
+
+ where(:file_type, :result) do
+ 'codequality' | %w(codequality)
+ 'quality' | nil
+ end
+
+ with_them do
+ it { is_expected.to eq result }
+ end
+ end
+
describe '.erasable' do
subject { described_class.erasable }
@@ -174,18 +189,6 @@ describe Ci::JobArtifact do
end
end
- describe '.for_ref' do
- let(:first_pipeline) { create(:ci_pipeline, ref: 'first_ref') }
- let(:second_pipeline) { create(:ci_pipeline, ref: 'second_ref', project: first_pipeline.project) }
- let!(:first_artifact) { create(:ci_job_artifact, job: create(:ci_build, pipeline: first_pipeline)) }
- let!(:second_artifact) { create(:ci_job_artifact, job: create(:ci_build, pipeline: second_pipeline)) }
-
- it 'returns job artifacts for a given pipeline ref' do
- expect(described_class.for_ref(first_pipeline.ref, first_pipeline.project.id)).to eq([first_artifact])
- expect(described_class.for_ref(second_pipeline.ref, first_pipeline.project.id)).to eq([second_artifact])
- end
- end
-
describe '.for_job_name' do
it 'returns job artifacts for a given job name' do
first_job = create(:ci_build, name: 'first')
@@ -501,4 +504,100 @@ describe Ci::JobArtifact do
end
end
end
+
+ describe '.file_types' do
+ context 'all file types have corresponding limit' do
+ let_it_be(:plan_limits) { create(:plan_limits) }
+
+ where(:file_type) do
+ described_class.file_types.keys
+ end
+
+ with_them do
+ let(:limit_name) { "#{described_class::PLAN_LIMIT_PREFIX}#{file_type}" }
+
+ it { expect(plan_limits.attributes).to include(limit_name), file_type_limit_failure_message(file_type, limit_name) }
+ end
+ end
+ end
+
+ describe '.max_artifact_size' do
+ let(:build) { create(:ci_build) }
+
+ subject(:max_size) { described_class.max_artifact_size(type: artifact_type, project: build.project) }
+
+ context 'when file type is supported' do
+ let(:project_closest_setting) { 1024 }
+ let(:artifact_type) { 'junit' }
+
+ before do
+ stub_feature_flags(ci_max_artifact_size_per_type: flag_enabled)
+ allow(build.project).to receive(:closest_setting).with(:max_artifacts_size).and_return(project_closest_setting)
+ end
+
+ shared_examples_for 'basing off the project closest setting' do
+ it { is_expected.to eq(project_closest_setting.megabytes.to_i) }
+ end
+
+ shared_examples_for 'basing off the plan limit' do
+ it { is_expected.to eq(max_size_for_type.megabytes.to_i) }
+ end
+
+ context 'and feature flag for custom max size per type is enabled' do
+ let(:flag_enabled) { true }
+ let(:limit_name) { "#{described_class::PLAN_LIMIT_PREFIX}#{artifact_type}" }
+
+ let!(:plan_limits) { create(:plan_limits, :default_plan) }
+
+ context 'and plan limit is disabled for the given artifact type' do
+ before do
+ plan_limits.update!(limit_name => 0)
+ end
+
+ it_behaves_like 'basing off the project closest setting'
+
+ context 'and project closest setting results to zero' do
+ let(:project_closest_setting) { 0 }
+
+ it { is_expected.to eq(0) }
+ end
+ end
+
+ context 'and plan limit is enabled for the given artifact type' do
+ before do
+ plan_limits.update!(limit_name => max_size_for_type)
+ end
+
+ context 'and plan limit is smaller than project setting' do
+ let(:max_size_for_type) { project_closest_setting - 1 }
+
+ it_behaves_like 'basing off the plan limit'
+ end
+
+ context 'and plan limit is smaller than project setting' do
+ let(:max_size_for_type) { project_closest_setting + 1 }
+
+ it_behaves_like 'basing off the project closest setting'
+ end
+ end
+ end
+
+ context 'and feature flag for custom max size per type is disabled' do
+ let(:flag_enabled) { false }
+
+ it_behaves_like 'basing off the project closest setting'
+ end
+ end
+ end
+
+ def file_type_limit_failure_message(type, limit_name)
+ <<~MSG
+ The artifact type `#{type}` is missing its counterpart plan limit which is expected to be named `#{limit_name}`.
+
+ Please refer to https://docs.gitlab.com/ee/development/application_limits.html on how to add new plan limit columns.
+
+ Take note that while existing max size plan limits default to 0, succeeding new limits are recommended to have
+ non-zero default values.
+ MSG
+ end
end
diff --git a/spec/models/ci/job_variable_spec.rb b/spec/models/ci/job_variable_spec.rb
index b94a914c784..4aebd3283f0 100644
--- a/spec/models/ci/job_variable_spec.rb
+++ b/spec/models/ci/job_variable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::JobVariable do
+RSpec.describe Ci::JobVariable do
subject { build(:ci_job_variable) }
it_behaves_like "CI variable"
diff --git a/spec/models/ci/legacy_stage_spec.rb b/spec/models/ci/legacy_stage_spec.rb
index f503fc10c08..c53f6abb037 100644
--- a/spec/models/ci/legacy_stage_spec.rb
+++ b/spec/models/ci/legacy_stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::LegacyStage do
+RSpec.describe Ci::LegacyStage do
let(:stage) { build(:ci_stage) }
let(:pipeline) { stage.pipeline }
let(:stage_name) { stage.name }
diff --git a/spec/models/ci/persistent_ref_spec.rb b/spec/models/ci/persistent_ref_spec.rb
index 89dd9b05331..18552317025 100644
--- a/spec/models/ci/persistent_ref_spec.rb
+++ b/spec/models/ci/persistent_ref_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PersistentRef do
+RSpec.describe Ci::PersistentRef do
it 'cleans up persistent refs after pipeline finished' do
pipeline = create(:ci_pipeline, :running)
diff --git a/spec/models/ci/pipeline_config_spec.rb b/spec/models/ci/pipeline_config_spec.rb
index 25f514ee5ab..3d033d33df3 100644
--- a/spec/models/ci/pipeline_config_spec.rb
+++ b/spec/models/ci/pipeline_config_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelineConfig, type: :model do
+RSpec.describe Ci::PipelineConfig, type: :model do
it { is_expected.to belong_to(:pipeline) }
it { is_expected.to validate_presence_of(:pipeline) }
diff --git a/spec/models/ci/pipeline_message_spec.rb b/spec/models/ci/pipeline_message_spec.rb
new file mode 100644
index 00000000000..6c97a025625
--- /dev/null
+++ b/spec/models/ci/pipeline_message_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::PipelineMessage do
+ describe 'validations' do
+ subject { described_class.new(pipeline: pipeline, content: content) }
+
+ let(:pipeline) { create(:ci_pipeline) }
+
+ context 'when message content is longer than the limit' do
+ let(:content) { 'x' * (described_class::MAX_CONTENT_LENGTH + 1) }
+
+ it 'is truncated with ellipsis' do
+ subject.save!
+
+ expect(subject.content).to end_with('x...')
+ expect(subject.content.length).to eq(described_class::MAX_CONTENT_LENGTH)
+ end
+ end
+
+ context 'when message is not present' do
+ let(:content) { '' }
+
+ it 'returns an error' do
+ expect(subject.save).to be_falsey
+ expect(subject.errors[:content]).to be_present
+ end
+ end
+
+ context 'when message content is valid' do
+ let(:content) { 'valid message content' }
+
+ it 'is saved with default error severity' do
+ subject.save!
+
+ expect(subject.content).to eq(content)
+ expect(subject.severity).to eq('error')
+ expect(subject).to be_error
+ end
+
+ it 'is persist the defined severity' do
+ subject.severity = :warning
+
+ subject.save!
+
+ expect(subject.content).to eq(content)
+ expect(subject.severity).to eq('warning')
+ expect(subject).to be_warning
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb
index 4ba70552f01..949d5f7bd04 100644
--- a/spec/models/ci/pipeline_schedule_spec.rb
+++ b/spec/models/ci/pipeline_schedule_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelineSchedule do
+RSpec.describe Ci::PipelineSchedule do
subject { build(:ci_pipeline_schedule) }
it { is_expected.to belong_to(:project) }
diff --git a/spec/models/ci/pipeline_schedule_variable_spec.rb b/spec/models/ci/pipeline_schedule_variable_spec.rb
index c96a24d5042..fd6b1d3dce0 100644
--- a/spec/models/ci/pipeline_schedule_variable_spec.rb
+++ b/spec/models/ci/pipeline_schedule_variable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelineScheduleVariable do
+RSpec.describe Ci::PipelineScheduleVariable do
subject { build(:ci_pipeline_schedule_variable) }
it_behaves_like "CI variable"
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 782a4206c36..ed2466d6413 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Pipeline, :mailer do
+RSpec.describe Ci::Pipeline, :mailer do
include ProjectForksHelper
include StubRequests
@@ -219,6 +219,50 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '.outside_pipeline_family' do
+ subject(:outside_pipeline_family) { described_class.outside_pipeline_family(upstream_pipeline) }
+
+ let(:upstream_pipeline) { create(:ci_pipeline, project: project) }
+ let(:child_pipeline) { create(:ci_pipeline, project: project) }
+
+ let!(:other_pipeline) { create(:ci_pipeline, project: project) }
+
+ before do
+ create(:ci_sources_pipeline,
+ source_job: create(:ci_build, pipeline: upstream_pipeline),
+ source_project: project,
+ pipeline: child_pipeline,
+ project: project)
+ end
+
+ it 'only returns pipelines outside pipeline family' do
+ expect(outside_pipeline_family).to contain_exactly(other_pipeline)
+ end
+ end
+
+ describe '.before_pipeline' do
+ subject(:before_pipeline) { described_class.before_pipeline(child_pipeline) }
+
+ let!(:older_other_pipeline) { create(:ci_pipeline, project: project) }
+
+ let!(:upstream_pipeline) { create(:ci_pipeline, project: project) }
+ let!(:child_pipeline) { create(:ci_pipeline, project: project) }
+
+ let!(:other_pipeline) { create(:ci_pipeline, project: project) }
+
+ before do
+ create(:ci_sources_pipeline,
+ source_job: create(:ci_build, pipeline: upstream_pipeline),
+ source_project: project,
+ pipeline: child_pipeline,
+ project: project)
+ end
+
+ it 'only returns older pipelines outside pipeline family' do
+ expect(before_pipeline).to contain_exactly(older_other_pipeline)
+ end
+ end
+
describe '#merge_request?' do
let(:pipeline) { create(:ci_pipeline, merge_request: merge_request) }
let(:merge_request) { create(:merge_request) }
@@ -1488,6 +1532,35 @@ describe Ci::Pipeline, :mailer do
sha: project.commit.sha)
end
+ describe '#lazy_ref_commit' do
+ let(:another) do
+ create(:ci_pipeline,
+ project: project,
+ ref: 'feature',
+ sha: project.commit('feature').sha)
+ end
+
+ let(:unicode) do
+ create(:ci_pipeline,
+ project: project,
+ ref: 'ü/unicode/multi-byte')
+ end
+
+ it 'returns the latest commit for a ref lazily' do
+ expect(project.repository)
+ .to receive(:list_commits_by_ref_name).once
+ .and_call_original
+
+ pipeline.lazy_ref_commit
+ another.lazy_ref_commit
+ unicode.lazy_ref_commit
+
+ expect(pipeline.lazy_ref_commit.id).to eq pipeline.sha
+ expect(another.lazy_ref_commit.id).to eq another.sha
+ expect(unicode.lazy_ref_commit).to be_nil
+ end
+ end
+
describe '#latest?' do
context 'with latest sha' do
it 'returns true' do
@@ -1496,17 +1569,26 @@ describe Ci::Pipeline, :mailer do
end
context 'with a branch name as the ref' do
- it 'looks up commit with the full ref name' do
- expect(pipeline.project).to receive(:commit).with('refs/heads/master').and_call_original
+ it 'looks up a commit for a branch' do
+ expect(pipeline.ref).to eq 'master'
+ expect(pipeline).to be_latest
+ end
+ end
+
+ context 'with a tag name as a ref' do
+ it 'looks up a commit for a tag' do
+ expect(project.repository.branch_names).not_to include 'v1.0.0'
+ pipeline.update(sha: project.commit('v1.0.0').sha, ref: 'v1.0.0', tag: true)
+
+ expect(pipeline).to be_tag
expect(pipeline).to be_latest
end
end
context 'with not latest sha' do
before do
- pipeline.update(
- sha: project.commit("#{project.default_branch}~1").sha)
+ pipeline.update(sha: project.commit("#{project.default_branch}~1").sha)
end
it 'returns false' do
@@ -1932,6 +2014,23 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '.last_finished_for_ref_id' do
+ let(:project) { create(:project, :repository) }
+ let(:branch) { project.default_branch }
+ let(:ref) { project.ci_refs.take }
+ let(:config_source) { Ci::PipelineEnums.config_sources[:parameter_source] }
+ let!(:pipeline1) { create(:ci_pipeline, :success, project: project, ref: branch) }
+ let!(:pipeline2) { create(:ci_pipeline, :success, project: project, ref: branch) }
+ let!(:pipeline3) { create(:ci_pipeline, :failed, project: project, ref: branch) }
+ let!(:pipeline4) { create(:ci_pipeline, :success, project: project, ref: branch) }
+ let!(:pipeline5) { create(:ci_pipeline, :success, project: project, ref: branch, config_source: config_source) }
+
+ it 'returns the expected pipeline' do
+ result = described_class.last_finished_for_ref_id(ref.id)
+ expect(result).to eq(pipeline4)
+ end
+ end
+
describe '.internal_sources' do
subject { described_class.internal_sources }
@@ -2087,7 +2186,7 @@ describe Ci::Pipeline, :mailer do
it 'raises an exception' do
expect { pipeline.update_legacy_status }
- .to raise_error(HasStatus::UnknownStatusError)
+ .to raise_error(Ci::HasStatus::UnknownStatusError)
end
end
end
@@ -2580,6 +2679,55 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '#same_family_pipeline_ids' do
+ subject(:same_family_pipeline_ids) { pipeline.same_family_pipeline_ids }
+
+ context 'when pipeline is not child nor parent' do
+ it 'returns just the pipeline id' do
+ expect(same_family_pipeline_ids).to contain_exactly(pipeline.id)
+ end
+ end
+
+ context 'when pipeline is child' do
+ let(:parent) { create(:ci_pipeline, project: pipeline.project) }
+ let(:sibling) { create(:ci_pipeline, project: pipeline.project) }
+
+ before do
+ create(:ci_sources_pipeline,
+ source_job: create(:ci_build, pipeline: parent),
+ source_project: parent.project,
+ pipeline: pipeline,
+ project: pipeline.project)
+
+ create(:ci_sources_pipeline,
+ source_job: create(:ci_build, pipeline: parent),
+ source_project: parent.project,
+ pipeline: sibling,
+ project: sibling.project)
+ end
+
+ it 'returns parent sibling and self ids' do
+ expect(same_family_pipeline_ids).to contain_exactly(parent.id, pipeline.id, sibling.id)
+ end
+ end
+
+ context 'when pipeline is parent' do
+ let(:child) { create(:ci_pipeline, project: pipeline.project) }
+
+ before do
+ create(:ci_sources_pipeline,
+ source_job: create(:ci_build, pipeline: pipeline),
+ source_project: pipeline.project,
+ pipeline: child,
+ project: child.project)
+ end
+
+ it 'returns self and child ids' do
+ expect(same_family_pipeline_ids).to contain_exactly(pipeline.id, child.id)
+ end
+ end
+ end
+
describe '#stuck?' do
before do
create(:ci_build, :pending, pipeline: pipeline)
@@ -2602,6 +2750,28 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '#add_error_message' do
+ let(:pipeline) { build_stubbed(:ci_pipeline) }
+
+ it 'adds a new pipeline error message' do
+ pipeline.add_error_message('The error message')
+
+ expect(pipeline.messages.map(&:content)).to contain_exactly('The error message')
+ end
+
+ context 'when feature flag ci_store_pipeline_messages is disabled' do
+ before do
+ stub_feature_flags(ci_store_pipeline_messages: false)
+ end
+
+ it ' does not add pipeline error message' do
+ pipeline.add_error_message('The error message')
+
+ expect(pipeline.messages).to be_empty
+ end
+ end
+ end
+
describe '#has_yaml_errors?' do
context 'when yaml_errors is set' do
before do
@@ -2825,6 +2995,16 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '#batch_lookup_report_artifact_for_file_type' do
+ context 'with code quality report artifact' do
+ let(:pipeline) { create(:ci_pipeline, :with_codequality_report, project: project) }
+
+ it "returns the code quality artifact" do
+ expect(pipeline.batch_lookup_report_artifact_for_file_type(:codequality)).to eq(pipeline.job_artifacts.sample)
+ end
+ end
+ end
+
describe '#latest_report_builds' do
it 'returns build with test artifacts' do
test_build = create(:ci_build, :test_reports, pipeline: pipeline, project: project)
@@ -2891,6 +3071,39 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '#test_report_summary' do
+ subject { pipeline.test_report_summary }
+
+ context 'when pipeline has multiple builds with report results' do
+ let(:pipeline) { create(:ci_pipeline, :success, project: project) }
+
+ before do
+ create(:ci_build, :success, :report_results, name: 'rspec', pipeline: pipeline, project: project)
+ create(:ci_build, :success, :report_results, name: 'java', pipeline: pipeline, project: project)
+ end
+
+ it 'returns test report summary with collected data', :aggregate_failures do
+ expect(subject.total_time).to be(0.84)
+ expect(subject.total_count).to be(4)
+ expect(subject.success_count).to be(0)
+ expect(subject.failed_count).to be(0)
+ expect(subject.error_count).to be(4)
+ expect(subject.skipped_count).to be(0)
+ end
+ end
+
+ context 'when pipeline does not have any builds with report results' do
+ it 'returns empty test report sumary', :aggregate_failures do
+ expect(subject.total_time).to be(0)
+ expect(subject.total_count).to be(0)
+ expect(subject.success_count).to be(0)
+ expect(subject.failed_count).to be(0)
+ expect(subject.error_count).to be(0)
+ expect(subject.skipped_count).to be(0)
+ end
+ end
+ end
+
describe '#test_reports' do
subject { pipeline.test_reports }
@@ -3069,6 +3282,32 @@ describe Ci::Pipeline, :mailer do
end
end
end
+
+ context 'when transitioning to success' do
+ context 'when feature is enabled' do
+ before do
+ stub_feature_flags(keep_latest_artifacts_for_ref: true)
+ end
+
+ it 'calls the PipelineSuccessUnlockArtifactsWorker' do
+ expect(Ci::PipelineSuccessUnlockArtifactsWorker).to receive(:perform_async).with(pipeline.id)
+
+ pipeline.succeed!
+ end
+ end
+
+ context 'when feature is disabled' do
+ before do
+ stub_feature_flags(keep_latest_artifacts_for_ref: false)
+ end
+
+ it 'does not call the PipelineSuccessUnlockArtifactsWorker' do
+ expect(Ci::PipelineSuccessUnlockArtifactsWorker).not_to receive(:perform_async)
+
+ pipeline.succeed!
+ end
+ end
+ end
end
describe '#default_branch?' do
@@ -3133,8 +3372,8 @@ describe Ci::Pipeline, :mailer do
end
end
- describe '#error_messages' do
- subject { pipeline.error_messages }
+ describe '#full_error_messages' do
+ subject { pipeline.full_error_messages }
before do
pipeline.valid?
diff --git a/spec/models/ci/pipeline_variable_spec.rb b/spec/models/ci/pipeline_variable_spec.rb
index e8c7ce088e2..04fcaab4c2d 100644
--- a/spec/models/ci/pipeline_variable_spec.rb
+++ b/spec/models/ci/pipeline_variable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelineVariable do
+RSpec.describe Ci::PipelineVariable do
subject { build(:ci_pipeline_variable) }
it_behaves_like "CI variable"
diff --git a/spec/models/ci/processable_spec.rb b/spec/models/ci/processable_spec.rb
index e67f740279b..35764e2bbbe 100644
--- a/spec/models/ci/processable_spec.rb
+++ b/spec/models/ci/processable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Processable do
+RSpec.describe Ci::Processable do
let_it_be(:project) { create(:project) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
diff --git a/spec/models/ci/ref_spec.rb b/spec/models/ci/ref_spec.rb
index 3d75cb63141..fd4742a8ad2 100644
--- a/spec/models/ci/ref_spec.rb
+++ b/spec/models/ci/ref_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Ref do
+RSpec.describe Ci::Ref do
it { is_expected.to belong_to(:project) }
describe '.ensure_for' do
@@ -62,6 +62,35 @@ describe Ci::Ref do
end
end
+ describe '#last_finished_pipeline_id' do
+ let(:pipeline_status) { :running }
+ let(:config_source) { Ci::PipelineEnums.config_sources[:repository_source] }
+ let(:pipeline) { create(:ci_pipeline, pipeline_status, config_source: config_source) }
+ let(:ci_ref) { pipeline.ci_ref }
+
+ context 'when there are no finished pipelines' do
+ it 'returns nil' do
+ expect(ci_ref.last_finished_pipeline_id).to be_nil
+ end
+ end
+
+ context 'when there are finished pipelines' do
+ let(:pipeline_status) { :success }
+
+ it 'returns the pipeline id' do
+ expect(ci_ref.last_finished_pipeline_id).to eq(pipeline.id)
+ end
+
+ context 'when the pipeline is not a ci_source' do
+ let(:config_source) { Ci::PipelineEnums.config_sources[:parameter_source] }
+
+ it 'returns nil' do
+ expect(ci_ref.last_finished_pipeline_id).to be_nil
+ end
+ end
+ end
+ end
+
describe '#update_status_by!' do
subject { ci_ref.update_status_by!(pipeline) }
diff --git a/spec/models/ci/resource_group_spec.rb b/spec/models/ci/resource_group_spec.rb
index ce8b03282bc..9f72d1a82e5 100644
--- a/spec/models/ci/resource_group_spec.rb
+++ b/spec/models/ci/resource_group_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::ResourceGroup do
+RSpec.describe Ci::ResourceGroup do
describe 'validation' do
it 'valids when key includes allowed character' do
resource_group = build(:ci_resource_group, key: 'test')
diff --git a/spec/models/ci/resource_spec.rb b/spec/models/ci/resource_spec.rb
index 27e512e2c45..90f26ef2b31 100644
--- a/spec/models/ci/resource_spec.rb
+++ b/spec/models/ci/resource_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Resource do
+RSpec.describe Ci::Resource do
describe '.free' do
subject { described_class.free }
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 296240b1602..8247ebf1144 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Runner do
+RSpec.describe Ci::Runner do
it_behaves_like 'having unique enum values'
describe 'validation' do
@@ -713,6 +713,46 @@ describe Ci::Runner do
end
end
+ describe '#belongs_to_more_than_one_project?' do
+ context 'project runner' do
+ let(:project1) { create(:project) }
+ let(:project2) { create(:project) }
+
+ context 'two projects assigned to runner' do
+ let(:runner) { create(:ci_runner, :project, projects: [project1, project2]) }
+
+ it 'returns true' do
+ expect(runner.belongs_to_more_than_one_project?).to be_truthy
+ end
+ end
+
+ context 'one project assigned to runner' do
+ let(:runner) { create(:ci_runner, :project, projects: [project1]) }
+
+ it 'returns false' do
+ expect(runner.belongs_to_more_than_one_project?).to be_falsey
+ end
+ end
+ end
+
+ context 'group runner' do
+ let(:group) { create(:group) }
+ let(:runner) { create(:ci_runner, :group, groups: [group]) }
+
+ it 'returns false' do
+ expect(runner.belongs_to_more_than_one_project?).to be_falsey
+ end
+ end
+
+ context 'shared runner' do
+ let(:runner) { create(:ci_runner, :instance) }
+
+ it 'returns false' do
+ expect(runner.belongs_to_more_than_one_project?).to be_falsey
+ end
+ end
+ end
+
describe '#has_tags?' do
context 'when runner has tags' do
subject { create(:ci_runner, tag_list: ['tag']) }
diff --git a/spec/models/ci/sources/pipeline_spec.rb b/spec/models/ci/sources/pipeline_spec.rb
index 5023747b487..ccf3140650b 100644
--- a/spec/models/ci/sources/pipeline_spec.rb
+++ b/spec/models/ci/sources/pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Sources::Pipeline do
+RSpec.describe Ci::Sources::Pipeline do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:pipeline) }
diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb
index a1549532559..3d873a1b9c1 100644
--- a/spec/models/ci/stage_spec.rb
+++ b/spec/models/ci/stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Stage, :models do
+RSpec.describe Ci::Stage, :models do
let_it_be(:pipeline) { create(:ci_empty_pipeline) }
let(:stage) { create(:ci_stage_entity, pipeline: pipeline, project: pipeline.project) }
@@ -172,7 +172,7 @@ describe Ci::Stage, :models do
it 'raises an exception' do
expect { stage.update_legacy_status }
- .to raise_error(HasStatus::UnknownStatusError)
+ .to raise_error(Ci::HasStatus::UnknownStatusError)
end
end
end
diff --git a/spec/models/ci/trigger_request_spec.rb b/spec/models/ci/trigger_request_spec.rb
index d04349bec92..0d462741089 100644
--- a/spec/models/ci/trigger_request_spec.rb
+++ b/spec/models/ci/trigger_request_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::TriggerRequest do
+RSpec.describe Ci::TriggerRequest do
describe 'validation' do
it 'be invalid if saving a variable' do
trigger = build(:ci_trigger_request, variables: { TRIGGER_KEY_1: 'TRIGGER_VALUE_1' } )
diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb
index 5b0815f8156..4ba6c6e50f7 100644
--- a/spec/models/ci/trigger_spec.rb
+++ b/spec/models/ci/trigger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Trigger do
+RSpec.describe Ci::Trigger do
let(:project) { create :project }
describe 'associations' do
diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb
index 810a0ddfd2e..26a7a2596af 100644
--- a/spec/models/ci/variable_spec.rb
+++ b/spec/models/ci/variable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Variable do
+RSpec.describe Ci::Variable do
subject { build(:ci_variable) }
it_behaves_like "CI variable"
diff --git a/spec/models/clusters/applications/cert_manager_spec.rb b/spec/models/clusters/applications/cert_manager_spec.rb
index d7fd0d06b05..7ca7f533a27 100644
--- a/spec/models/clusters/applications/cert_manager_spec.rb
+++ b/spec/models/clusters/applications/cert_manager_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::CertManager do
+RSpec.describe Clusters::Applications::CertManager do
let(:cert_manager) { create(:clusters_applications_cert_manager) }
include_examples 'cluster application core specs', :clusters_applications_cert_manager
diff --git a/spec/models/clusters/applications/cilium_spec.rb b/spec/models/clusters/applications/cilium_spec.rb
new file mode 100644
index 00000000000..8b01502d5c0
--- /dev/null
+++ b/spec/models/clusters/applications/cilium_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Clusters::Applications::Cilium do
+ let(:cilium) { create(:clusters_applications_cilium) }
+
+ include_examples 'cluster application core specs', :clusters_applications_cilium
+ include_examples 'cluster application status specs', :clusters_applications_cilium
+ include_examples 'cluster application initial status specs'
+
+ describe '#allowed_to_uninstall?' do
+ subject { cilium.allowed_to_uninstall? }
+
+ it { is_expected.to be false }
+ end
+end
diff --git a/spec/models/clusters/applications/crossplane_spec.rb b/spec/models/clusters/applications/crossplane_spec.rb
index ebc675497f4..a41c5f6586b 100644
--- a/spec/models/clusters/applications/crossplane_spec.rb
+++ b/spec/models/clusters/applications/crossplane_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::Crossplane do
+RSpec.describe Clusters::Applications::Crossplane do
let(:crossplane) { create(:clusters_applications_crossplane) }
include_examples 'cluster application core specs', :clusters_applications_crossplane
diff --git a/spec/models/clusters/applications/elastic_stack_spec.rb b/spec/models/clusters/applications/elastic_stack_spec.rb
index 50042a4e29a..62123ffa542 100644
--- a/spec/models/clusters/applications/elastic_stack_spec.rb
+++ b/spec/models/clusters/applications/elastic_stack_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::ElasticStack do
+RSpec.describe Clusters::Applications::ElasticStack do
include KubernetesHelpers
include_examples 'cluster application core specs', :clusters_applications_elastic_stack
@@ -27,6 +27,20 @@ describe Clusters::Applications::ElasticStack do
expect(subject.preinstall).to be_empty
end
+ context 'within values.yaml' do
+ let(:values_yaml_content) {subject.files[:"values.yaml"]}
+
+ it 'contains the disabled index lifecycle management' do
+ expect(values_yaml_content).to include "setup.ilm.enabled: false"
+ end
+
+ it 'contains daily indices with respective template' do
+ expect(values_yaml_content).to include "index: \"filebeat-%{[agent.version]}-%{+yyyy.MM.dd}\""
+ expect(values_yaml_content).to include "setup.template.name: 'filebeat'"
+ expect(values_yaml_content).to include "setup.template.pattern: 'filebeat-*'"
+ end
+ end
+
context 'on a non rbac enabled cluster' do
before do
elastic_stack.cluster.platform_kubernetes.abac!
diff --git a/spec/models/clusters/applications/fluentd_spec.rb b/spec/models/clusters/applications/fluentd_spec.rb
index 4e9548990ed..be7b4a87947 100644
--- a/spec/models/clusters/applications/fluentd_spec.rb
+++ b/spec/models/clusters/applications/fluentd_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::Fluentd do
+RSpec.describe Clusters::Applications::Fluentd do
let(:waf_log_enabled) { true }
let(:cilium_log_enabled) { true }
let(:fluentd) { create(:clusters_applications_fluentd, waf_log_enabled: waf_log_enabled, cilium_log_enabled: cilium_log_enabled) }
diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb
index 87454e1d3e2..6d2ecaa6d47 100644
--- a/spec/models/clusters/applications/helm_spec.rb
+++ b/spec/models/clusters/applications/helm_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::Helm do
+RSpec.describe Clusters::Applications::Helm do
include_examples 'cluster application core specs', :clusters_applications_helm
describe '.available' do
diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb
index 8aee4eec0d3..d1138f5fa2d 100644
--- a/spec/models/clusters/applications/ingress_spec.rb
+++ b/spec/models/clusters/applications/ingress_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::Ingress do
+RSpec.describe Clusters::Applications::Ingress do
let(:ingress) { create(:clusters_applications_ingress) }
it_behaves_like 'having unique enum values'
diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb
index 937db9217f3..3cf24f1a9ef 100644
--- a/spec/models/clusters/applications/jupyter_spec.rb
+++ b/spec/models/clusters/applications/jupyter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::Jupyter do
+RSpec.describe Clusters::Applications::Jupyter do
include_examples 'cluster application core specs', :clusters_applications_jupyter
include_examples 'cluster application status specs', :clusters_applications_jupyter
include_examples 'cluster application version specs', :clusters_applications_jupyter
diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb
index 7ff7644e703..b14161ce8e6 100644
--- a/spec/models/clusters/applications/knative_spec.rb
+++ b/spec/models/clusters/applications/knative_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::Knative do
+RSpec.describe Clusters::Applications::Knative do
let(:knative) { create(:clusters_applications_knative) }
include_examples 'cluster application core specs', :clusters_applications_knative
diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb
index 1ed9e207b6b..1215b38a9a2 100644
--- a/spec/models/clusters/applications/prometheus_spec.rb
+++ b/spec/models/clusters/applications/prometheus_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::Prometheus do
+RSpec.describe Clusters::Applications::Prometheus do
include KubernetesHelpers
include StubRequests
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
index 6ee6711ec4b..fbabfd25b2f 100644
--- a/spec/models/clusters/applications/runner_spec.rb
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::Runner do
+RSpec.describe Clusters::Applications::Runner do
let(:ci_runner) { create(:ci_runner) }
include_examples 'cluster application core specs', :clusters_applications_runner
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 4dd74976028..4807957152c 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
+RSpec.describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
include KubernetesHelpers
@@ -10,6 +10,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
subject { build(:cluster) }
+ it { is_expected.to include_module(HasEnvironmentScope) }
it { is_expected.to belong_to(:user) }
it { is_expected.to belong_to(:management_project).class_name('::Project') }
it { is_expected.to have_many(:cluster_projects) }
@@ -289,6 +290,79 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
describe 'validations' do
subject { cluster.valid? }
+ context 'when validates unique_environment_scope' do
+ context 'for a project cluster' do
+ let(:project) { create(:project) }
+
+ before do
+ create(:cluster, projects: [project], environment_scope: 'product/*')
+ end
+
+ context 'when identical environment scope exists in project' do
+ let(:cluster) { build(:cluster, projects: [project], environment_scope: 'product/*') }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when identical environment scope does not exist in project' do
+ let(:cluster) { build(:cluster, projects: [project], environment_scope: '*') }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when identical environment scope exists in different project' do
+ let(:project2) { create(:project) }
+ let(:cluster) { build(:cluster, projects: [project2], environment_scope: 'product/*') }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'for a group cluster' do
+ let(:group) { create(:group) }
+
+ before do
+ create(:cluster, cluster_type: :group_type, groups: [group], environment_scope: 'product/*')
+ end
+
+ context 'when identical environment scope exists in group' do
+ let(:cluster) { build(:cluster, cluster_type: :group_type, groups: [group], environment_scope: 'product/*') }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when identical environment scope does not exist in group' do
+ let(:cluster) { build(:cluster, cluster_type: :group_type, groups: [group], environment_scope: '*') }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when identical environment scope exists in different group' do
+ let(:cluster) { build(:cluster, :group, environment_scope: 'product/*') }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'for an instance cluster' do
+ before do
+ create(:cluster, :instance, environment_scope: 'product/*')
+ end
+
+ context 'identical environment scope exists' do
+ let(:cluster) { build(:cluster, :instance, environment_scope: 'product/*') }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'identical environment scope does not exist' do
+ let(:cluster) { build(:cluster, :instance, environment_scope: '*') }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+ end
+
context 'when validates name' do
context 'when provided by user' do
let!(:cluster) { build(:cluster, :provided_by_user, name: name) }
@@ -1111,13 +1185,23 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
context 'cluster is enabled' do
let(:cluster) { create(:cluster, :provided_by_user, :group) }
+ let(:gl_k8s_node_double) { double(Gitlab::Kubernetes::Node) }
+ let(:expected_nodes) { nil }
before do
- stub_kubeclient_nodes_and_nodes_metrics(cluster.platform.api_url)
+ stub_kubeclient_discover(cluster.platform.api_url)
+ allow(Gitlab::Kubernetes::Node).to receive(:new).with(cluster).and_return(gl_k8s_node_double)
+ allow(gl_k8s_node_double).to receive(:all).and_return([])
end
context 'connection to the cluster is successful' do
- it { is_expected.to eq(connection_status: :connected, nodes: [kube_node.merge(kube_node_metrics)]) }
+ before do
+ allow(gl_k8s_node_double).to receive(:all).and_return(expected_nodes)
+ end
+
+ let(:expected_nodes) { [kube_node.merge(kube_node_metrics)] }
+
+ it { is_expected.to eq(connection_status: :connected, nodes: expected_nodes) }
end
context 'cluster cannot be reached' do
@@ -1126,7 +1210,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
.and_raise(SocketError)
end
- it { is_expected.to eq(connection_status: :unreachable, nodes: nil) }
+ it { is_expected.to eq(connection_status: :unreachable, nodes: expected_nodes) }
end
context 'cluster cannot be authenticated to' do
@@ -1135,7 +1219,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
.and_raise(OpenSSL::X509::CertificateError.new("Certificate error"))
end
- it { is_expected.to eq(connection_status: :authentication_failure, nodes: nil) }
+ it { is_expected.to eq(connection_status: :authentication_failure, nodes: expected_nodes) }
end
describe 'Kubeclient::HttpError' do
@@ -1147,18 +1231,18 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
.and_raise(Kubeclient::HttpError.new(error_code, error_message, nil))
end
- it { is_expected.to eq(connection_status: :authentication_failure, nodes: nil) }
+ it { is_expected.to eq(connection_status: :authentication_failure, nodes: expected_nodes) }
context 'generic timeout' do
let(:error_message) { 'Timed out connecting to server'}
- it { is_expected.to eq(connection_status: :unreachable, nodes: nil) }
+ it { is_expected.to eq(connection_status: :unreachable, nodes: expected_nodes) }
end
context 'gateway timeout' do
let(:error_message) { '504 Gateway Timeout for GET https://kubernetes.example.com/api/v1'}
- it { is_expected.to eq(connection_status: :unreachable, nodes: nil) }
+ it { is_expected.to eq(connection_status: :unreachable, nodes: expected_nodes) }
end
end
@@ -1168,12 +1252,12 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
.and_raise(StandardError)
end
- it { is_expected.to eq(connection_status: :unknown_failure, nodes: nil) }
+ it { is_expected.to eq(connection_status: :unknown_failure, nodes: expected_nodes) }
it 'notifies Sentry' do
expect(Gitlab::ErrorTracking).to receive(:track_exception)
.with(instance_of(StandardError), hash_including(cluster_id: cluster.id))
- .twice
+ .once
subject
end
diff --git a/spec/models/clusters/clusters_hierarchy_spec.rb b/spec/models/clusters/clusters_hierarchy_spec.rb
index 1957e1fc5ee..5ac561eb2d0 100644
--- a/spec/models/clusters/clusters_hierarchy_spec.rb
+++ b/spec/models/clusters/clusters_hierarchy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::ClustersHierarchy do
+RSpec.describe Clusters::ClustersHierarchy do
describe '#base_and_ancestors' do
def base_and_ancestors(clusterable, include_management_project: true)
described_class.new(clusterable, include_management_project: include_management_project).base_and_ancestors
diff --git a/spec/models/clusters/group_spec.rb b/spec/models/clusters/group_spec.rb
index ba145342cb8..3b541c40938 100644
--- a/spec/models/clusters/group_spec.rb
+++ b/spec/models/clusters/group_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Group do
+RSpec.describe Clusters::Group do
it { is_expected.to belong_to(:cluster) }
it { is_expected.to belong_to(:group) }
end
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index f0e6dd53664..adccc72d13d 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Platforms::Kubernetes do
+RSpec.describe Clusters::Platforms::Kubernetes do
include KubernetesHelpers
it { is_expected.to belong_to(:cluster) }
@@ -204,6 +204,52 @@ describe Clusters::Platforms::Kubernetes do
end
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::KubeClient) }
+
+ context 'ca_pem is a single certificate' do
+ let(:ca_pem) { File.read(Rails.root.join('spec/fixtures/clusters/ca_certificate.pem')) }
+ let(:kubernetes) do
+ build(:cluster_platform_kubernetes,
+ :configured,
+ namespace: 'a-namespace',
+ cluster: cluster,
+ ca_pem: ca_pem)
+ end
+
+ it 'adds it to cert_store' do
+ cert = OpenSSL::X509::Certificate.new(ca_pem)
+ cert_store = kubernetes.kubeclient.kubeclient_options[:ssl_options][:cert_store]
+
+ expect(cert_store.verify(cert)).to be true
+ end
+ end
+
+ context 'ca_pem is a chain' do
+ let(:cert_chain) { File.read(Rails.root.join('spec/fixtures/clusters/chain_certificates.pem')) }
+ let(:kubernetes) do
+ build(:cluster_platform_kubernetes,
+ :configured,
+ namespace: 'a-namespace',
+ cluster: cluster,
+ ca_pem: cert_chain)
+ end
+
+ it 'includes chain of certificates' do
+ cert1_file = File.read(Rails.root.join('spec/fixtures/clusters/root_certificate.pem'))
+ cert1 = OpenSSL::X509::Certificate.new(cert1_file)
+
+ cert2_file = File.read(Rails.root.join('spec/fixtures/clusters/intermediate_certificate.pem'))
+ cert2 = OpenSSL::X509::Certificate.new(cert2_file)
+
+ cert3_file = File.read(Rails.root.join('spec/fixtures/clusters/ca_certificate.pem'))
+ cert3 = OpenSSL::X509::Certificate.new(cert3_file)
+
+ cert_store = kubernetes.kubeclient.kubeclient_options[:ssl_options][:cert_store]
+
+ expect(cert_store.verify(cert1)).to be true
+ expect(cert_store.verify(cert2)).to be true
+ expect(cert_store.verify(cert3)).to be true
+ end
+ end
end
describe '#rbac?' do
diff --git a/spec/models/clusters/project_spec.rb b/spec/models/clusters/project_spec.rb
index 671af085d10..e16dfa47898 100644
--- a/spec/models/clusters/project_spec.rb
+++ b/spec/models/clusters/project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Project do
+RSpec.describe Clusters::Project do
it { is_expected.to belong_to(:cluster) }
it { is_expected.to belong_to(:project) }
it { is_expected.to have_many(:kubernetes_namespaces) }
diff --git a/spec/models/clusters/providers/aws_spec.rb b/spec/models/clusters/providers/aws_spec.rb
index 05d6e63288e..3b4a48cc5be 100644
--- a/spec/models/clusters/providers/aws_spec.rb
+++ b/spec/models/clusters/providers/aws_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Providers::Aws do
+RSpec.describe Clusters::Providers::Aws do
it { is_expected.to belong_to(:cluster) }
it { is_expected.to validate_length_of(:key_name).is_at_least(1).is_at_most(255) }
diff --git a/spec/models/clusters/providers/gcp_spec.rb b/spec/models/clusters/providers/gcp_spec.rb
index e2fd777d131..ad9ada04875 100644
--- a/spec/models/clusters/providers/gcp_spec.rb
+++ b/spec/models/clusters/providers/gcp_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Providers::Gcp do
+RSpec.describe Clusters::Providers::Gcp do
it { is_expected.to belong_to(:cluster) }
it { is_expected.to validate_presence_of(:zone) }
diff --git a/spec/models/commit_collection_spec.rb b/spec/models/commit_collection_spec.rb
index d49b71db5f8..f4e86f3292b 100644
--- a/spec/models/commit_collection_spec.rb
+++ b/spec/models/commit_collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CommitCollection do
+RSpec.describe CommitCollection do
let(:project) { create(:project, :repository) }
let(:commit) { project.commit("c1c67abbaf91f624347bb3ae96eabe3a1b742478") }
@@ -75,6 +75,18 @@ describe CommitCollection do
end
end
+ describe '#with_markdown_cache' do
+ let(:commits) { [commit] }
+ let(:collection) { described_class.new(project, commits) }
+
+ it 'preloads commits cache markdown' do
+ aggregate_failures do
+ expect(Commit).to receive(:preload_markdown_cache!).with(commits)
+ expect(collection.with_markdown_cache).to eq(collection)
+ end
+ end
+ end
+
describe 'enrichment methods' do
let(:gitaly_commit) { commit }
let(:hash_commit) { Commit.from_hash(gitaly_commit.to_hash, project) }
diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb
index 245e47fa17b..3fb8708c884 100644
--- a/spec/models/commit_range_spec.rb
+++ b/spec/models/commit_range_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CommitRange do
+RSpec.describe CommitRange do
describe 'modules' do
subject { described_class }
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index ddda04faaf1..cfa87b3e39e 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Commit do
+RSpec.describe Commit do
let_it_be(:project) { create(:project, :public, :repository) }
let_it_be(:personal_snippet) { create(:personal_snippet, :repository) }
let_it_be(:project_snippet) { create(:project_snippet, :repository) }
@@ -675,7 +675,10 @@ eos
end
describe '#work_in_progress?' do
- ['squash! ', 'fixup! ', 'wip: ', 'WIP: ', '[WIP] '].each do |wip_prefix|
+ [
+ 'squash! ', 'fixup! ', 'wip: ', 'WIP: ', '[WIP] ',
+ 'draft: ', 'Draft - ', '[Draft] ', '(draft) ', 'Draft: '
+ ].each do |wip_prefix|
it "detects the '#{wip_prefix}' prefix" do
commit.message = "#{wip_prefix}#{commit.message}"
@@ -689,6 +692,12 @@ eos
expect(commit).to be_work_in_progress
end
+ it "detects WIP for a commit just saying 'draft'" do
+ commit.message = "draft"
+
+ expect(commit).to be_work_in_progress
+ end
+
it "doesn't detect WIP for a commit that begins with 'FIXUP! '" do
commit.message = "FIXUP! #{commit.message}"
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 85fc503a1ca..cd0110a787b 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CommitStatus do
+RSpec.describe CommitStatus do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:pipeline) do
diff --git a/spec/models/commit_with_pipeline_spec.rb b/spec/models/commit_with_pipeline_spec.rb
index e0bb29fec7b..ff451527929 100644
--- a/spec/models/commit_with_pipeline_spec.rb
+++ b/spec/models/commit_with_pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CommitWithPipeline do
+RSpec.describe CommitWithPipeline do
let(:project) { create(:project, :public, :repository) }
let(:commit) { described_class.new(project.commit) }
diff --git a/spec/models/compare_spec.rb b/spec/models/compare_spec.rb
index 43c3580bed2..d395aa359e5 100644
--- a/spec/models/compare_spec.rb
+++ b/spec/models/compare_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Compare do
+RSpec.describe Compare do
include RepoHelpers
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/models/concerns/access_requestable_spec.rb b/spec/models/concerns/access_requestable_spec.rb
index 5c1694e3737..24eb3e8a1e6 100644
--- a/spec/models/concerns/access_requestable_spec.rb
+++ b/spec/models/concerns/access_requestable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AccessRequestable do
+RSpec.describe AccessRequestable do
describe 'Group' do
describe '#request_access' do
let(:group) { create(:group, :public) }
diff --git a/spec/models/concerns/approvable_base_spec.rb b/spec/models/concerns/approvable_base_spec.rb
new file mode 100644
index 00000000000..8fda8bccf09
--- /dev/null
+++ b/spec/models/concerns/approvable_base_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ApprovableBase do
+ describe '#approved_by?' do
+ let(:merge_request) { create(:merge_request) }
+ let(:user) { create(:user) }
+
+ subject { merge_request.approved_by?(user) }
+
+ context 'when a user has not approved' do
+ it 'returns false' do
+ is_expected.to be_falsy
+ end
+ end
+
+ context 'when a user has approved' do
+ let!(:approval) { create(:approval, merge_request: merge_request, user: user) }
+
+ it 'returns false' do
+ is_expected.to be_truthy
+ end
+ end
+
+ context 'when a user is nil' do
+ let(:user) { nil }
+
+ it 'returns false' do
+ is_expected.to be_falsy
+ end
+ end
+ end
+end
diff --git a/spec/models/concerns/atomic_internal_id_spec.rb b/spec/models/concerns/atomic_internal_id_spec.rb
index 93bf7ec10dd..8c3537f1dcc 100644
--- a/spec/models/concerns/atomic_internal_id_spec.rb
+++ b/spec/models/concerns/atomic_internal_id_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AtomicInternalId do
+RSpec.describe AtomicInternalId do
let(:milestone) { build(:milestone) }
let(:iid) { double('iid', to_i: 42) }
let(:external_iid) { 100 }
diff --git a/spec/models/concerns/avatarable_spec.rb b/spec/models/concerns/avatarable_spec.rb
index 96e867dbc97..8a8eeea39dc 100644
--- a/spec/models/concerns/avatarable_spec.rb
+++ b/spec/models/concerns/avatarable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Avatarable do
+RSpec.describe Avatarable do
let(:project) { create(:project, :with_avatar) }
let(:gitlab_host) { "https://gitlab.example.com" }
diff --git a/spec/models/concerns/awardable_spec.rb b/spec/models/concerns/awardable_spec.rb
index 29f911fcb04..b5b3772ecb6 100644
--- a/spec/models/concerns/awardable_spec.rb
+++ b/spec/models/concerns/awardable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Awardable do
+RSpec.describe Awardable do
let!(:issue) { create(:issue) }
let!(:award_emoji) { create(:award_emoji, :downvote, awardable: issue) }
diff --git a/spec/models/concerns/batch_destroy_dependent_associations_spec.rb b/spec/models/concerns/batch_destroy_dependent_associations_spec.rb
index d2373926802..a8fcb714c64 100644
--- a/spec/models/concerns/batch_destroy_dependent_associations_spec.rb
+++ b/spec/models/concerns/batch_destroy_dependent_associations_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BatchDestroyDependentAssociations do
+RSpec.describe BatchDestroyDependentAssociations do
class TestProject < ActiveRecord::Base
self.table_name = 'projects'
diff --git a/spec/models/concerns/blob_language_from_git_attributes_spec.rb b/spec/models/concerns/blob_language_from_git_attributes_spec.rb
index 4cb8f042b1d..c07ee15e841 100644
--- a/spec/models/concerns/blob_language_from_git_attributes_spec.rb
+++ b/spec/models/concerns/blob_language_from_git_attributes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobLanguageFromGitAttributes do
+RSpec.describe BlobLanguageFromGitAttributes do
include FakeBlobHelpers
let(:project) { build(:project, :repository) }
diff --git a/spec/models/concerns/blocks_json_serialization_spec.rb b/spec/models/concerns/blocks_json_serialization_spec.rb
index 32870461019..d811b47fa35 100644
--- a/spec/models/concerns/blocks_json_serialization_spec.rb
+++ b/spec/models/concerns/blocks_json_serialization_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlocksJsonSerialization do
+RSpec.describe BlocksJsonSerialization do
before do
stub_const('DummyModel', Class.new)
DummyModel.class_eval do
diff --git a/spec/models/concerns/bulk_insert_safe_spec.rb b/spec/models/concerns/bulk_insert_safe_spec.rb
index 07d6cee487f..82b0c00b396 100644
--- a/spec/models/concerns/bulk_insert_safe_spec.rb
+++ b/spec/models/concerns/bulk_insert_safe_spec.rb
@@ -2,57 +2,7 @@
require 'spec_helper'
-describe BulkInsertSafe do
- class BulkInsertItem < ActiveRecord::Base
- include BulkInsertSafe
- include ShaAttribute
-
- validates :name, :enum_value, :secret_value, :sha_value, :jsonb_value, presence: true
-
- ENUM_VALUES = {
- case_1: 1
- }.freeze
-
- sha_attribute :sha_value
-
- enum enum_value: ENUM_VALUES
-
- attr_encrypted :secret_value,
- mode: :per_attribute_iv,
- algorithm: 'aes-256-gcm',
- key: Settings.attr_encrypted_db_key_base_32,
- insecure_mode: false
-
- default_value_for :enum_value, 'case_1'
- default_value_for :secret_value, 'my-secret'
- default_value_for :sha_value, '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12'
- default_value_for :jsonb_value, { "key" => "value" }
-
- def self.valid_list(count)
- Array.new(count) { |n| new(name: "item-#{n}") }
- end
-
- def self.invalid_list(count)
- Array.new(count) { new }
- end
- end
-
- module InheritedUnsafeMethods
- extend ActiveSupport::Concern
-
- included do
- after_save -> { "unsafe" }
- end
- end
-
- module InheritedSafeMethods
- extend ActiveSupport::Concern
-
- included do
- after_initialize -> { "safe" }
- end
- end
-
+RSpec.describe BulkInsertSafe do
before(:all) do
ActiveRecord::Schema.define do
create_table :bulk_insert_items, force: true do |t|
@@ -66,100 +16,155 @@ describe BulkInsertSafe do
t.index :name, unique: true
end
end
-
- BulkInsertItem.reset_column_information
end
after(:all) do
ActiveRecord::Schema.define do
drop_table :bulk_insert_items, force: true
end
+ end
+
+ let_it_be(:bulk_insert_item_class) do
+ Class.new(ActiveRecord::Base) do
+ self.table_name = 'bulk_insert_items'
- BulkInsertItem.reset_column_information
+ include BulkInsertSafe
+ include ShaAttribute
+
+ validates :name, :enum_value, :secret_value, :sha_value, :jsonb_value, presence: true
+
+ sha_attribute :sha_value
+
+ enum enum_value: { case_1: 1 }
+
+ attr_encrypted :secret_value,
+ mode: :per_attribute_iv,
+ algorithm: 'aes-256-gcm',
+ key: Settings.attr_encrypted_db_key_base_32,
+ insecure_mode: false
+
+ default_value_for :enum_value, 'case_1'
+ default_value_for :secret_value, 'my-secret'
+ default_value_for :sha_value, '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12'
+ default_value_for :jsonb_value, { "key" => "value" }
+
+ def self.name
+ 'BulkInsertItem'
+ end
+
+ def self.valid_list(count)
+ Array.new(count) { |n| new(name: "item-#{n}") }
+ end
+
+ def self.invalid_list(count)
+ Array.new(count) { new }
+ end
+ end
end
- describe BulkInsertItem do
- it_behaves_like 'a BulkInsertSafe model', described_class do
- let(:valid_items_for_bulk_insertion) { described_class.valid_list(10) }
- let(:invalid_items_for_bulk_insertion) { described_class.invalid_list(10) }
+ describe 'BulkInsertItem' do
+ it_behaves_like 'a BulkInsertSafe model' do
+ let(:target_class) { bulk_insert_item_class.dup }
+ let(:valid_items_for_bulk_insertion) { target_class.valid_list(10) }
+ let(:invalid_items_for_bulk_insertion) { target_class.invalid_list(10) }
end
context 'when inheriting class methods' do
+ let(:inherited_unsafe_methods_module) do
+ Module.new do
+ extend ActiveSupport::Concern
+
+ included do
+ after_save -> { "unsafe" }
+ end
+ end
+ end
+
+ let(:inherited_safe_methods_module) do
+ Module.new do
+ extend ActiveSupport::Concern
+
+ included do
+ after_initialize -> { "safe" }
+ end
+ end
+ end
+
it 'raises an error when method is not bulk-insert safe' do
- expect { described_class.include(InheritedUnsafeMethods) }
- .to raise_error(described_class::MethodNotAllowedError)
+ expect { bulk_insert_item_class.include(inherited_unsafe_methods_module) }
+ .to raise_error(bulk_insert_item_class::MethodNotAllowedError)
end
it 'does not raise an error when method is bulk-insert safe' do
- expect { described_class.include(InheritedSafeMethods) }.not_to raise_error
+ expect { bulk_insert_item_class.include(inherited_safe_methods_module) }.not_to raise_error
end
end
context 'primary keys' do
it 'raises error if primary keys are set prior to insertion' do
- item = described_class.new(name: 'valid', id: 10)
+ item = bulk_insert_item_class.new(name: 'valid', id: 10)
- expect { described_class.bulk_insert!([item]) }
- .to raise_error(described_class::PrimaryKeySetError)
+ expect { bulk_insert_item_class.bulk_insert!([item]) }
+ .to raise_error(bulk_insert_item_class::PrimaryKeySetError)
end
end
describe '.bulk_insert!' do
it 'inserts items in the given number of batches' do
- items = described_class.valid_list(10)
+ items = bulk_insert_item_class.valid_list(10)
expect(ActiveRecord::InsertAll).to receive(:new).twice.and_call_original
- described_class.bulk_insert!(items, batch_size: 5)
+ bulk_insert_item_class.bulk_insert!(items, batch_size: 5)
end
it 'items can be properly fetched from database' do
- items = described_class.valid_list(10)
+ items = bulk_insert_item_class.valid_list(10)
- described_class.bulk_insert!(items)
+ bulk_insert_item_class.bulk_insert!(items)
- attribute_names = described_class.attribute_names - %w[id created_at updated_at]
- expect(described_class.last(items.size).pluck(*attribute_names)).to eq(
+ attribute_names = bulk_insert_item_class.attribute_names - %w[id created_at updated_at]
+ expect(bulk_insert_item_class.last(items.size).pluck(*attribute_names)).to eq(
items.pluck(*attribute_names))
end
it 'rolls back the transaction when any item is invalid' do
# second batch is bad
- all_items = described_class.valid_list(10) +
- described_class.invalid_list(10)
+ all_items = bulk_insert_item_class.valid_list(10) +
+ bulk_insert_item_class.invalid_list(10)
expect do
- described_class.bulk_insert!(all_items, batch_size: 2) rescue nil
- end.not_to change { described_class.count }
+ bulk_insert_item_class.bulk_insert!(all_items, batch_size: 2) rescue nil
+ end.not_to change { bulk_insert_item_class.count }
end
it 'does nothing and returns an empty array when items are empty' do
- expect(described_class.bulk_insert!([])).to eq([])
- expect(described_class.count).to eq(0)
+ expect(bulk_insert_item_class.bulk_insert!([])).to eq([])
+ expect(bulk_insert_item_class.count).to eq(0)
end
context 'with returns option set' do
context 'when is set to :ids' do
it 'return an array with the primary key values for all inserted records' do
- items = described_class.valid_list(1)
+ items = bulk_insert_item_class.valid_list(1)
- expect(described_class.bulk_insert!(items, returns: :ids)).to contain_exactly(a_kind_of(Integer))
+ expect(bulk_insert_item_class.bulk_insert!(items, returns: :ids)).to contain_exactly(a_kind_of(Integer))
end
end
context 'when is set to nil' do
it 'returns an empty array' do
- items = described_class.valid_list(1)
+ items = bulk_insert_item_class.valid_list(1)
- expect(described_class.bulk_insert!(items, returns: nil)).to eq([])
+ expect(bulk_insert_item_class.bulk_insert!(items, returns: nil)).to eq([])
end
end
context 'when is set to anything else' do
it 'raises an error' do
- items = described_class.valid_list(1)
+ items = bulk_insert_item_class.valid_list(1)
- expect { described_class.bulk_insert!([items], returns: [:id, :name]) }
+ expect { bulk_insert_item_class.bulk_insert!([items], returns: [:id, :name]) }
.to raise_error(ArgumentError, "returns needs to be :ids or nil")
end
end
@@ -167,20 +172,20 @@ describe BulkInsertSafe do
end
context 'when duplicate items are to be inserted' do
- let!(:existing_object) { described_class.create!(name: 'duplicate', secret_value: 'old value') }
- let(:new_object) { described_class.new(name: 'duplicate', secret_value: 'new value') }
+ let!(:existing_object) { bulk_insert_item_class.create!(name: 'duplicate', secret_value: 'old value') }
+ let(:new_object) { bulk_insert_item_class.new(name: 'duplicate', secret_value: 'new value') }
describe '.bulk_insert!' do
context 'when skip_duplicates is set to false' do
it 'raises an exception' do
- expect { described_class.bulk_insert!([new_object], skip_duplicates: false) }
+ expect { bulk_insert_item_class.bulk_insert!([new_object], skip_duplicates: false) }
.to raise_error(ActiveRecord::RecordNotUnique)
end
end
context 'when skip_duplicates is set to true' do
it 'does not update existing object' do
- described_class.bulk_insert!([new_object], skip_duplicates: true)
+ bulk_insert_item_class.bulk_insert!([new_object], skip_duplicates: true)
expect(existing_object.reload.secret_value).to eq('old value')
end
@@ -189,7 +194,7 @@ describe BulkInsertSafe do
describe '.bulk_upsert!' do
it 'updates existing object' do
- described_class.bulk_upsert!([new_object], unique_by: %w[name])
+ bulk_insert_item_class.bulk_upsert!([new_object], unique_by: %w[name])
expect(existing_object.reload.secret_value).to eq('new value')
end
diff --git a/spec/models/concerns/bulk_insertable_associations_spec.rb b/spec/models/concerns/bulk_insertable_associations_spec.rb
index 6359b2c57ef..5a40639e493 100644
--- a/spec/models/concerns/bulk_insertable_associations_spec.rb
+++ b/spec/models/concerns/bulk_insertable_associations_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BulkInsertableAssociations do
+RSpec.describe BulkInsertableAssociations do
class BulkFoo < ApplicationRecord
include BulkInsertSafe
diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb
index c46ebcf324c..5f8c65e429e 100644
--- a/spec/models/concerns/cache_markdown_field_spec.rb
+++ b/spec/models/concerns/cache_markdown_field_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CacheMarkdownField, :clean_gitlab_redis_cache do
+RSpec.describe CacheMarkdownField, :clean_gitlab_redis_cache do
let(:ar_class) do
Class.new(ActiveRecord::Base) do
self.table_name = 'issues'
diff --git a/spec/models/concerns/cacheable_attributes_spec.rb b/spec/models/concerns/cacheable_attributes_spec.rb
index 6694b2aba22..f2877bed9cf 100644
--- a/spec/models/concerns/cacheable_attributes_spec.rb
+++ b/spec/models/concerns/cacheable_attributes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CacheableAttributes do
+RSpec.describe CacheableAttributes do
let(:minimal_test_class) do
Class.new do
include ActiveModel::Model
diff --git a/spec/models/concerns/case_sensitivity_spec.rb b/spec/models/concerns/case_sensitivity_spec.rb
index 9819f656f0d..521b47c63fd 100644
--- a/spec/models/concerns/case_sensitivity_spec.rb
+++ b/spec/models/concerns/case_sensitivity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CaseSensitivity do
+RSpec.describe CaseSensitivity do
describe '.iwhere' do
let(:connection) { ActiveRecord::Base.connection }
let(:model) do
diff --git a/spec/models/concerns/checksummable_spec.rb b/spec/models/concerns/checksummable_spec.rb
index 017077bd297..b469b2e5c18 100644
--- a/spec/models/concerns/checksummable_spec.rb
+++ b/spec/models/concerns/checksummable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Checksummable do
+RSpec.describe Checksummable do
describe ".hexdigest" do
let(:fake_class) do
Class.new do
diff --git a/spec/models/concerns/chronic_duration_attribute_spec.rb b/spec/models/concerns/chronic_duration_attribute_spec.rb
index e41d75568f7..e6dbf403b63 100644
--- a/spec/models/concerns/chronic_duration_attribute_spec.rb
+++ b/spec/models/concerns/chronic_duration_attribute_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-shared_examples 'ChronicDurationAttribute reader' do
+RSpec.shared_examples 'ChronicDurationAttribute reader' do
it 'contains dynamically created reader method' do
expect(subject.class).to be_public_method_defined(virtual_field)
end
@@ -22,7 +22,7 @@ shared_examples 'ChronicDurationAttribute reader' do
end
end
-shared_examples 'ChronicDurationAttribute writer' do
+RSpec.shared_examples 'ChronicDurationAttribute writer' do
it 'contains dynamically created writer method' do
expect(subject.class).to be_public_method_defined("#{virtual_field}=")
end
@@ -94,7 +94,7 @@ shared_examples 'ChronicDurationAttribute writer' do
end
end
-describe 'ChronicDurationAttribute' do
+RSpec.describe 'ChronicDurationAttribute' do
context 'when default value is not set' do
let(:source_field) {:maximum_timeout}
let(:virtual_field) {:maximum_timeout_human_readable}
@@ -118,7 +118,7 @@ describe 'ChronicDurationAttribute' do
end
end
-describe 'ChronicDurationAttribute - reader' do
+RSpec.describe 'ChronicDurationAttribute - reader' do
let(:source_field) {:timeout}
let(:virtual_field) {:timeout_human_readable}
diff --git a/spec/models/concerns/ci/has_ref_spec.rb b/spec/models/concerns/ci/has_ref_spec.rb
index b98f915018b..69f2fdb21e1 100644
--- a/spec/models/concerns/ci/has_ref_spec.rb
+++ b/spec/models/concerns/ci/has_ref_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::HasRef do
+RSpec.describe Ci::HasRef do
describe '#branch?' do
let(:build) { create(:ci_build) }
diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/ci/has_status_spec.rb
index 68047f24ec3..fe46b63781d 100644
--- a/spec/models/concerns/has_status_spec.rb
+++ b/spec/models/concerns/ci/has_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe HasStatus do
+RSpec.describe Ci::HasStatus do
describe '.slow_composite_status' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/models/concerns/ci/has_variable_spec.rb b/spec/models/concerns/ci/has_variable_spec.rb
index c132fe47c3c..b5390281064 100644
--- a/spec/models/concerns/ci/has_variable_spec.rb
+++ b/spec/models/concerns/ci/has_variable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::HasVariable do
+RSpec.describe Ci::HasVariable do
subject { build(:ci_variable) }
it { is_expected.to validate_presence_of(:key) }
diff --git a/spec/models/concerns/ci/maskable_spec.rb b/spec/models/concerns/ci/maskable_spec.rb
index 01861b39165..840a08b6060 100644
--- a/spec/models/concerns/ci/maskable_spec.rb
+++ b/spec/models/concerns/ci/maskable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::Maskable do
+RSpec.describe Ci::Maskable do
let(:variable) { build(:ci_variable) }
describe 'masked value validations' do
diff --git a/spec/models/concerns/delete_with_limit_spec.rb b/spec/models/concerns/delete_with_limit_spec.rb
index 52085f970f3..0259a1ea4fb 100644
--- a/spec/models/concerns/delete_with_limit_spec.rb
+++ b/spec/models/concerns/delete_with_limit_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeleteWithLimit do
+RSpec.describe DeleteWithLimit do
describe '.delete_with_limit' do
it 'deletes a limited amount of rows' do
create_list(:web_hook_log, 4)
diff --git a/spec/models/concerns/deployment_platform_spec.rb b/spec/models/concerns/deployment_platform_spec.rb
index 9164c3a75c5..2bb6aa27e21 100644
--- a/spec/models/concerns/deployment_platform_spec.rb
+++ b/spec/models/concerns/deployment_platform_spec.rb
@@ -2,12 +2,247 @@
require 'spec_helper'
-describe DeploymentPlatform do
+RSpec.describe DeploymentPlatform do
let(:project) { create(:project) }
describe '#deployment_platform' do
subject { project.deployment_platform }
+ context 'multiple clusters' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+
+ shared_examples 'matching environment scope' do
+ it 'returns environment specific cluster' do
+ is_expected.to eq(cluster.platform_kubernetes)
+ end
+ end
+
+ shared_examples 'not matching environment scope' do
+ it 'returns default cluster' do
+ is_expected.to eq(default_cluster.platform_kubernetes)
+ end
+ end
+
+ context 'multiple clusters use the same management project' do
+ let(:management_project) { create(:project, group: group) }
+
+ let!(:default_cluster) do
+ create(:cluster_for_group, groups: [group], environment_scope: '*', management_project: management_project)
+ end
+
+ let!(:cluster) do
+ create(:cluster_for_group, groups: [group], environment_scope: 'review/*', management_project: management_project)
+ end
+
+ let(:environment) { 'review/name' }
+
+ subject { management_project.deployment_platform(environment: environment) }
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when project does not have a cluster but has group clusters' do
+ let!(:default_cluster) do
+ create(:cluster, :provided_by_user,
+ cluster_type: :group_type, groups: [group], environment_scope: '*')
+ end
+
+ let!(:cluster) do
+ create(:cluster, :provided_by_user,
+ cluster_type: :group_type, environment_scope: 'review/*', groups: [group])
+ end
+
+ let(:environment) { 'review/name' }
+
+ subject { project.deployment_platform(environment: environment) }
+
+ context 'when environment scope is exactly matched' do
+ before do
+ cluster.update!(environment_scope: 'review/name')
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when environment scope is matched by wildcard' do
+ before do
+ cluster.update!(environment_scope: 'review/*')
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when environment scope does not match' do
+ before do
+ cluster.update!(environment_scope: 'review/*/special')
+ end
+
+ it_behaves_like 'not matching environment scope'
+ end
+
+ context 'when group belongs to a parent group' do
+ let(:parent_group) { create(:group) }
+ let(:group) { create(:group, parent: parent_group) }
+
+ context 'when parent_group has a cluster with default scope' do
+ let!(:parent_group_cluster) do
+ create(:cluster, :provided_by_user,
+ cluster_type: :group_type, environment_scope: '*', groups: [parent_group])
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when parent_group has a cluster that is an exact match' do
+ let!(:parent_group_cluster) do
+ create(:cluster, :provided_by_user,
+ cluster_type: :group_type, environment_scope: 'review/name', groups: [parent_group])
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+ end
+ end
+
+ context 'with instance clusters' do
+ let!(:default_cluster) do
+ create(:cluster, :provided_by_user, :instance, environment_scope: '*')
+ end
+
+ let!(:cluster) do
+ create(:cluster, :provided_by_user, :instance, environment_scope: 'review/*')
+ end
+
+ let(:environment) { 'review/name' }
+
+ subject { project.deployment_platform(environment: environment) }
+
+ context 'when environment scope is exactly matched' do
+ before do
+ cluster.update!(environment_scope: 'review/name')
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when environment scope is matched by wildcard' do
+ before do
+ cluster.update!(environment_scope: 'review/*')
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when environment scope does not match' do
+ before do
+ cluster.update!(environment_scope: 'review/*/special')
+ end
+
+ it_behaves_like 'not matching environment scope'
+ end
+ end
+
+ context 'when environment is specified' do
+ let!(:default_cluster) { create(:cluster, :provided_by_user, projects: [project], environment_scope: '*') }
+ let!(:cluster) { create(:cluster, :provided_by_user, environment_scope: 'review/*', projects: [project]) }
+
+ let!(:group_default_cluster) do
+ create(:cluster, :provided_by_user,
+ cluster_type: :group_type, groups: [group], environment_scope: '*')
+ end
+
+ let(:environment) { 'review/name' }
+
+ subject { project.deployment_platform(environment: environment) }
+
+ context 'when environment scope is exactly matched' do
+ before do
+ cluster.update!(environment_scope: 'review/name')
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when environment scope is matched by wildcard' do
+ before do
+ cluster.update!(environment_scope: 'review/*')
+ end
+
+ it_behaves_like 'matching environment scope'
+ end
+
+ context 'when environment scope does not match' do
+ before do
+ cluster.update!(environment_scope: 'review/*/special')
+ end
+
+ it_behaves_like 'not matching environment scope'
+ end
+
+ context 'when environment scope has _' do
+ it 'does not treat it as wildcard' do
+ cluster.update!(environment_scope: 'foo_bar/*')
+
+ is_expected.to eq(default_cluster.platform_kubernetes)
+ end
+
+ context 'when environment name contains an underscore' do
+ let(:environment) { 'foo_bar/test' }
+
+ it 'matches literally for _' do
+ cluster.update!(environment_scope: 'foo_bar/*')
+
+ is_expected.to eq(cluster.platform_kubernetes)
+ end
+ end
+ end
+
+ # The environment name and scope cannot have % at the moment,
+ # but we're considering relaxing it and we should also make sure
+ # it doesn't break in case some data sneaked in somehow as we're
+ # not checking this integrity in database level.
+ context 'when environment scope has %' do
+ it 'does not treat it as wildcard' do
+ cluster.update_attribute(:environment_scope, '*%*')
+
+ is_expected.to eq(default_cluster.platform_kubernetes)
+ end
+
+ context 'when environment name contains a percent char' do
+ let(:environment) { 'foo%bar/test' }
+
+ it 'matches literally for %' do
+ cluster.update_attribute(:environment_scope, 'foo%bar/*')
+
+ is_expected.to eq(cluster.platform_kubernetes)
+ end
+ end
+ end
+
+ context 'when perfectly matched cluster exists' do
+ let!(:perfectly_matched_cluster) { create(:cluster, :provided_by_user, projects: [project], environment_scope: 'review/name') }
+
+ it 'returns perfectly matched cluster as highest precedence' do
+ is_expected.to eq(perfectly_matched_cluster.platform_kubernetes)
+ end
+ end
+ end
+
+ context 'with multiple clusters and multiple environments' do
+ let!(:cluster_1) { create(:cluster, :provided_by_user, projects: [project], environment_scope: 'staging/*') }
+ let!(:cluster_2) { create(:cluster, :provided_by_user, projects: [project], environment_scope: 'test/*') }
+
+ let(:environment_1) { 'staging/name' }
+ let(:environment_2) { 'test/name' }
+
+ it 'returns the appropriate cluster' do
+ expect(project.deployment_platform(environment: environment_1)).to eq(cluster_1.platform_kubernetes)
+ expect(project.deployment_platform(environment: environment_2)).to eq(cluster_2.platform_kubernetes)
+ end
+ end
+ end
+
context 'with no Kubernetes configuration on CI/CD, no Kubernetes Service' do
it { is_expected.to be_nil }
end
diff --git a/spec/models/concerns/deprecated_assignee_spec.rb b/spec/models/concerns/deprecated_assignee_spec.rb
index e394de0aa34..630d9ea601f 100644
--- a/spec/models/concerns/deprecated_assignee_spec.rb
+++ b/spec/models/concerns/deprecated_assignee_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeprecatedAssignee do
+RSpec.describe DeprecatedAssignee do
let(:user) { create(:user) }
describe '#assignee_id=' do
diff --git a/spec/models/concerns/discussion_on_diff_spec.rb b/spec/models/concerns/discussion_on_diff_spec.rb
index f091861bd41..dd5d422f12d 100644
--- a/spec/models/concerns/discussion_on_diff_spec.rb
+++ b/spec/models/concerns/discussion_on_diff_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiscussionOnDiff do
+RSpec.describe DiscussionOnDiff do
subject { create(:diff_note_on_merge_request, line_number: 18).to_discussion }
describe "#truncated_diff_lines" do
diff --git a/spec/models/concerns/each_batch_spec.rb b/spec/models/concerns/each_batch_spec.rb
index ee3d9aea505..3c93c8a7a79 100644
--- a/spec/models/concerns/each_batch_spec.rb
+++ b/spec/models/concerns/each_batch_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EachBatch do
+RSpec.describe EachBatch do
describe '.each_batch' do
let(:model) do
Class.new(ActiveRecord::Base) do
diff --git a/spec/models/concerns/editable_spec.rb b/spec/models/concerns/editable_spec.rb
index 4a4a3ca5687..1d26629d0aa 100644
--- a/spec/models/concerns/editable_spec.rb
+++ b/spec/models/concerns/editable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Editable do
+RSpec.describe Editable do
describe '#edited?' do
let(:issue) { create(:issue, last_edited_at: nil) }
let(:edited_issue) { create(:issue, created_at: 3.days.ago, last_edited_at: 2.days.ago) }
diff --git a/spec/models/concerns/expirable_spec.rb b/spec/models/concerns/expirable_spec.rb
index f4f5eab5b86..b20d759fc3f 100644
--- a/spec/models/concerns/expirable_spec.rb
+++ b/spec/models/concerns/expirable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Expirable do
+RSpec.describe Expirable do
describe 'ProjectMember' do
let(:no_expire) { create(:project_member) }
let(:expire_later) { create(:project_member, expires_at: Time.current + 6.days) }
diff --git a/spec/models/concerns/faster_cache_keys_spec.rb b/spec/models/concerns/faster_cache_keys_spec.rb
index 7830acbae3d..ab6e809b3f7 100644
--- a/spec/models/concerns/faster_cache_keys_spec.rb
+++ b/spec/models/concerns/faster_cache_keys_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe FasterCacheKeys do
+RSpec.describe FasterCacheKeys do
describe '#cache_key' do
it 'returns a String' do
# We're using a fixed string here so it's easier to set an expectation for
diff --git a/spec/models/concerns/featurable_spec.rb b/spec/models/concerns/featurable_spec.rb
index 89720e3652c..cc01820cc97 100644
--- a/spec/models/concerns/featurable_spec.rb
+++ b/spec/models/concerns/featurable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Featurable do
+RSpec.describe Featurable do
let_it_be(:user) { create(:user) }
let(:project) { create(:project) }
let(:feature_class) { subject.class }
diff --git a/spec/models/concerns/feature_gate_spec.rb b/spec/models/concerns/feature_gate_spec.rb
index 276d3d9e1d5..6106708a32d 100644
--- a/spec/models/concerns/feature_gate_spec.rb
+++ b/spec/models/concerns/feature_gate_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe FeatureGate do
+RSpec.describe FeatureGate do
describe 'User' do
describe '#flipper_id' do
context 'when user is not persisted' do
diff --git a/spec/models/concerns/from_union_spec.rb b/spec/models/concerns/from_union_spec.rb
index 735e14b47ec..9819a6ec3de 100644
--- a/spec/models/concerns/from_union_spec.rb
+++ b/spec/models/concerns/from_union_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe FromUnion do
+RSpec.describe FromUnion do
describe '.from_union' do
let(:model) do
Class.new(ActiveRecord::Base) do
diff --git a/spec/models/concerns/group_descendant_spec.rb b/spec/models/concerns/group_descendant_spec.rb
index 47419770d0f..b29fa910ee6 100644
--- a/spec/models/concerns/group_descendant_spec.rb
+++ b/spec/models/concerns/group_descendant_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupDescendant do
+RSpec.describe GroupDescendant do
let(:parent) { create(:group) }
let(:subgroup) { create(:group, parent: parent) }
let(:subsub_group) { create(:group, parent: subgroup) }
diff --git a/spec/models/concerns/has_environment_scope_spec.rb b/spec/models/concerns/has_environment_scope_spec.rb
index a6e1ba59263..0cc997709c9 100644
--- a/spec/models/concerns/has_environment_scope_spec.rb
+++ b/spec/models/concerns/has_environment_scope_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe HasEnvironmentScope do
+RSpec.describe HasEnvironmentScope do
subject { build(:ci_variable) }
it { is_expected.to allow_value('*').for(:environment_scope) }
diff --git a/spec/models/concerns/has_user_type_spec.rb b/spec/models/concerns/has_user_type_spec.rb
index f12eee414f9..9496bb57b8b 100644
--- a/spec/models/concerns/has_user_type_spec.rb
+++ b/spec/models/concerns/has_user_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe User do
+RSpec.describe User do
specify 'types consistency checks', :aggregate_failures do
expect(described_class::USER_TYPES.keys)
.to match_array(%w[human ghost alert_bot project_bot support_bot service_user visual_review_bot migration_bot])
diff --git a/spec/models/concerns/ignorable_columns_spec.rb b/spec/models/concerns/ignorable_columns_spec.rb
index 018b1296c62..a5eff154a0b 100644
--- a/spec/models/concerns/ignorable_columns_spec.rb
+++ b/spec/models/concerns/ignorable_columns_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IgnorableColumns do
+RSpec.describe IgnorableColumns do
let(:record_class) do
Class.new(ApplicationRecord) do
include IgnorableColumns
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 74ee7a87b7b..96d3e2b7b1b 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issuable do
+RSpec.describe Issuable do
include ProjectForksHelper
let(:issuable_class) { Issue }
@@ -416,6 +416,27 @@ describe Issuable do
describe '#to_hook_data' do
let(:builder) { double }
+ context 'when old_associations is empty' do
+ let(:label) { create(:label) }
+
+ before do
+ issue.update!(labels: [label])
+ issue.assignees << user
+ issue.spend_time(duration: 2, user_id: user.id, spent_at: Time.current)
+ expect(Gitlab::HookData::IssuableBuilder)
+ .to receive(:new).with(issue).and_return(builder)
+ end
+
+ it 'delegates to Gitlab::HookData::IssuableBuilder#build and does not set labels, assignees, nor total_time_spent' do
+ expect(builder).to receive(:build).with(
+ user: user,
+ changes: {})
+
+ # In some cases, old_associations is empty, e.g. on a close event
+ issue.to_hook_data(user)
+ end
+ end
+
context 'labels are updated' do
let(:labels) { create_list(:label, 2) }
diff --git a/spec/models/concerns/limitable_spec.rb b/spec/models/concerns/limitable_spec.rb
index ca0a257be7a..753e2a8ee5e 100644
--- a/spec/models/concerns/limitable_spec.rb
+++ b/spec/models/concerns/limitable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Limitable do
+RSpec.describe Limitable do
let(:minimal_test_class) do
Class.new do
include ActiveModel::Model
diff --git a/spec/models/concerns/loaded_in_group_list_spec.rb b/spec/models/concerns/loaded_in_group_list_spec.rb
index 509811822e0..c37943022ba 100644
--- a/spec/models/concerns/loaded_in_group_list_spec.rb
+++ b/spec/models/concerns/loaded_in_group_list_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LoadedInGroupList do
+RSpec.describe LoadedInGroupList do
let(:parent) { create(:group) }
subject(:found_group) { Group.with_selects_for_list.find_by(id: parent.id) }
diff --git a/spec/models/concerns/manual_inverse_association_spec.rb b/spec/models/concerns/manual_inverse_association_spec.rb
index ee32e3b165b..1349d2cc680 100644
--- a/spec/models/concerns/manual_inverse_association_spec.rb
+++ b/spec/models/concerns/manual_inverse_association_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ManualInverseAssociation do
+RSpec.describe ManualInverseAssociation do
let(:model) do
Class.new(MergeRequest) do
belongs_to :manual_association, class_name: 'MergeRequestDiff', foreign_key: :latest_merge_request_diff_id
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index 03fd1c69654..758b5aa2ce4 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mentionable do
+RSpec.describe Mentionable do
before do
stub_const('Example', Class.new)
Example.class_eval do
@@ -67,7 +67,7 @@ describe Mentionable do
end
end
-describe Issue, "Mentionable" do
+RSpec.describe Issue, "Mentionable" do
describe '#mentioned_users' do
let!(:user) { create(:user, username: 'stranger') }
let!(:user2) { create(:user, username: 'john') }
@@ -222,7 +222,7 @@ describe Issue, "Mentionable" do
end
end
-describe Commit, 'Mentionable' do
+RSpec.describe Commit, 'Mentionable' do
let(:project) { create(:project, :public, :repository) }
let(:commit) { project.commit }
@@ -291,7 +291,7 @@ describe Commit, 'Mentionable' do
end
end
-describe MergeRequest, 'Mentionable' do
+RSpec.describe MergeRequest, 'Mentionable' do
describe '#store_mentions!' do
it_behaves_like 'mentions in description', :merge_request
it_behaves_like 'mentions in notes', :merge_request do
@@ -312,7 +312,7 @@ describe MergeRequest, 'Mentionable' do
end
end
-describe Snippet, 'Mentionable' do
+RSpec.describe Snippet, 'Mentionable' do
describe '#store_mentions!' do
it_behaves_like 'mentions in description', :project_snippet
it_behaves_like 'mentions in notes', :project_snippet do
@@ -329,7 +329,7 @@ describe Snippet, 'Mentionable' do
end
end
-describe PersonalSnippet, 'Mentionable' do
+RSpec.describe PersonalSnippet, 'Mentionable' do
describe '#store_mentions!' do
it_behaves_like 'mentions in description', :personal_snippet
it_behaves_like 'mentions in notes', :personal_snippet do
@@ -346,7 +346,7 @@ describe PersonalSnippet, 'Mentionable' do
end
end
-describe DesignManagement::Design do
+RSpec.describe DesignManagement::Design do
describe '#store_mentions!' do
it_behaves_like 'mentions in notes', :design do
let(:note) { create(:diff_note_on_design) }
diff --git a/spec/models/concerns/milestoneable_spec.rb b/spec/models/concerns/milestoneable_spec.rb
index 0b19c0542ee..15352a1453c 100644
--- a/spec/models/concerns/milestoneable_spec.rb
+++ b/spec/models/concerns/milestoneable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Milestoneable do
+RSpec.describe Milestoneable do
let(:user) { create(:user) }
let(:milestone) { create(:milestone, project: project) }
diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb
index 8c43a12aa15..58cd054efd5 100644
--- a/spec/models/concerns/milestoneish_spec.rb
+++ b/spec/models/concerns/milestoneish_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Milestone, 'Milestoneish' do
+RSpec.describe Milestone, 'Milestoneish' do
let(:author) { create(:user) }
let(:assignee) { create(:user) }
let(:non_member) { create(:user) }
diff --git a/spec/models/concerns/noteable_spec.rb b/spec/models/concerns/noteable_spec.rb
index 5c8c5425ca7..bb7374bf46c 100644
--- a/spec/models/concerns/noteable_spec.rb
+++ b/spec/models/concerns/noteable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Noteable do
+RSpec.describe Noteable do
let!(:active_diff_note1) { create(:diff_note_on_merge_request) }
let(:project) { active_diff_note1.project }
subject { active_diff_note1.noteable }
@@ -262,4 +262,44 @@ describe Noteable do
end
end
end
+
+ describe "#has_any_diff_note_positions?" do
+ let(:source_branch) { "compare-with-merge-head-source" }
+ let(:target_branch) { "compare-with-merge-head-target" }
+ let(:merge_request) { create(:merge_request, source_branch: source_branch, target_branch: target_branch) }
+
+ let!(:note) do
+ path = "files/markdown/ruby-style-guide.md"
+
+ position = Gitlab::Diff::Position.new(
+ old_path: path,
+ new_path: path,
+ new_line: 508,
+ diff_refs: merge_request.diff_refs
+ )
+
+ create(:diff_note_on_merge_request, project: merge_request.project, position: position, noteable: merge_request)
+ end
+
+ before do
+ MergeRequests::MergeToRefService.new(merge_request.project, merge_request.author).execute(merge_request)
+ Discussions::CaptureDiffNotePositionsService.new(merge_request).execute
+ end
+
+ it "returns true when it has diff note positions" do
+ expect(merge_request.has_any_diff_note_positions?).to be(true)
+ end
+
+ it "returns false when it has notes but no diff note positions" do
+ DiffNotePosition.where(note: note).find_each(&:delete)
+
+ expect(merge_request.has_any_diff_note_positions?).to be(false)
+ end
+
+ it "returns false when it has no notes" do
+ merge_request.notes.find_each(&:destroy)
+
+ expect(merge_request.has_any_diff_note_positions?).to be(false)
+ end
+ end
end
diff --git a/spec/models/concerns/optionally_search_spec.rb b/spec/models/concerns/optionally_search_spec.rb
index e1eb4cf8cd2..c8e2e6da51f 100644
--- a/spec/models/concerns/optionally_search_spec.rb
+++ b/spec/models/concerns/optionally_search_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe OptionallySearch do
+RSpec.describe OptionallySearch do
describe '.search' do
let(:model) do
Class.new do
diff --git a/spec/models/concerns/participable_spec.rb b/spec/models/concerns/participable_spec.rb
index 3d5937c4fc6..3376e337dc9 100644
--- a/spec/models/concerns/participable_spec.rb
+++ b/spec/models/concerns/participable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Participable do
+RSpec.describe Participable do
let(:model) do
Class.new do
include Participable
diff --git a/spec/models/concerns/partitioned_table_spec.rb b/spec/models/concerns/partitioned_table_spec.rb
new file mode 100644
index 00000000000..3343b273ba2
--- /dev/null
+++ b/spec/models/concerns/partitioned_table_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe PartitionedTable do
+ describe '.partitioned_by' do
+ subject { my_class.partitioned_by(key, strategy: :monthly) }
+
+ let(:key) { :foo }
+
+ let(:my_class) do
+ Class.new do
+ include PartitionedTable
+ end
+ end
+
+ it 'assigns the MonthlyStrategy as the partitioning strategy' do
+ subject
+
+ expect(my_class.partitioning_strategy).to be_a(Gitlab::Database::Partitioning::MonthlyStrategy)
+ end
+
+ it 'passes the partitioning key to the strategy instance' do
+ subject
+
+ expect(my_class.partitioning_strategy.partitioning_key).to eq(key)
+ end
+
+ it 'registers itself with the PartitionCreator' do
+ expect(Gitlab::Database::Partitioning::PartitionCreator).to receive(:register).with(my_class)
+
+ subject
+ end
+ end
+end
diff --git a/spec/models/concerns/presentable_spec.rb b/spec/models/concerns/presentable_spec.rb
index 9db868dd348..871e122e409 100644
--- a/spec/models/concerns/presentable_spec.rb
+++ b/spec/models/concerns/presentable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Presentable do
+RSpec.describe Presentable do
let(:build) { Ci::Build.new }
describe '#present' do
diff --git a/spec/models/concerns/project_api_compatibility_spec.rb b/spec/models/concerns/project_api_compatibility_spec.rb
index f5722f88aac..7a69406cb71 100644
--- a/spec/models/concerns/project_api_compatibility_spec.rb
+++ b/spec/models/concerns/project_api_compatibility_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectAPICompatibility do
+RSpec.describe ProjectAPICompatibility do
let(:project) { create(:project) }
# git_strategy
diff --git a/spec/models/concerns/project_features_compatibility_spec.rb b/spec/models/concerns/project_features_compatibility_spec.rb
index 8346c4ad4cc..ba70ff563a8 100644
--- a/spec/models/concerns/project_features_compatibility_spec.rb
+++ b/spec/models/concerns/project_features_compatibility_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectFeaturesCompatibility do
+RSpec.describe ProjectFeaturesCompatibility do
let(:project) { create(:project) }
let(:features_enabled) { %w(issues wiki builds merge_requests snippets) }
let(:features) { features_enabled + %w(repository pages) }
diff --git a/spec/models/concerns/prometheus_adapter_spec.rb b/spec/models/concerns/prometheus_adapter_spec.rb
index fdc98ba74b8..e795e2b06cb 100644
--- a/spec/models/concerns/prometheus_adapter_spec.rb
+++ b/spec/models/concerns/prometheus_adapter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PrometheusAdapter, :use_clean_rails_memory_store_caching do
+RSpec.describe PrometheusAdapter, :use_clean_rails_memory_store_caching do
include PrometheusHelpers
include ReactiveCachingHelpers
diff --git a/spec/models/concerns/protected_ref_access_spec.rb b/spec/models/concerns/protected_ref_access_spec.rb
index f63ad958ed3..750a5eba303 100644
--- a/spec/models/concerns/protected_ref_access_spec.rb
+++ b/spec/models/concerns/protected_ref_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProtectedRefAccess do
+RSpec.describe ProtectedRefAccess do
include ExternalAuthorizationServiceHelpers
subject(:protected_ref_access) do
diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb
index cfca383e0b0..b12ad82920f 100644
--- a/spec/models/concerns/reactive_caching_spec.rb
+++ b/spec/models/concerns/reactive_caching_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ReactiveCaching, :use_clean_rails_memory_store_caching do
+RSpec.describe ReactiveCaching, :use_clean_rails_memory_store_caching do
include ExclusiveLeaseHelpers
include ReactiveCachingHelpers
@@ -285,38 +285,30 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do
go!
end
- context 'when calculated object size exceeds default reactive_cache_hard_limit' do
- let(:calculation) { -> { 'a' * 2 * 1.megabyte } }
+ context 'when reactive_cache_hard_limit is set' do
+ let(:test_class) { Class.new(cache_class_test) { self.reactive_cache_hard_limit = 1.megabyte } }
+ let(:instance) { test_class.new(666, &calculation) }
+
+ context 'when cache size is over the overridden limit' do
+ let(:calculation) { -> { 'a' * 2 * 1.megabyte } }
- shared_examples 'ExceededReactiveCacheLimit' do
it 'raises ExceededReactiveCacheLimit exception and does not cache new data' do
expect { go! }.to raise_exception(ReactiveCaching::ExceededReactiveCacheLimit)
expect(read_reactive_cache(instance)).not_to eq(calculation.call)
end
- end
- context 'when reactive_cache_hard_limit feature flag is enabled' do
- it_behaves_like 'ExceededReactiveCacheLimit'
-
- context 'when reactive_cache_hard_limit is overridden' do
- let(:test_class) { Class.new(cache_class_test) { self.reactive_cache_hard_limit = 3.megabytes } }
- let(:instance) { test_class.new(666, &calculation) }
+ context 'when reactive_cache_limit_enabled? is overridden to return false' do
+ before do
+ allow(instance).to receive(:reactive_cache_limit_enabled?).and_return(false)
+ end
it_behaves_like 'successful cache'
-
- context 'when cache size is over the overridden limit' do
- let(:calculation) { -> { 'a' * 4 * 1.megabyte } }
-
- it_behaves_like 'ExceededReactiveCacheLimit'
- end
end
end
- context 'when reactive_cache_limit feature flag is disabled' do
- before do
- stub_feature_flags(reactive_cache_limit: false)
- end
+ context 'when cache size is within the overridden limit' do
+ let(:calculation) { -> { 'Smaller than 1Mb reactive_cache_hard_limit' } }
it_behaves_like 'successful cache'
end
@@ -377,7 +369,7 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do
it { expect(subject.reactive_cache_refresh_interval).to be_a(ActiveSupport::Duration) }
it { expect(subject.reactive_cache_lifetime).to be_a(ActiveSupport::Duration) }
it { expect(subject.reactive_cache_key).to respond_to(:call) }
- it { expect(subject.reactive_cache_hard_limit).to be_a(Integer) }
+ it { expect(subject.reactive_cache_hard_limit).to be_nil }
it { expect(subject.reactive_cache_worker_finder).to respond_to(:call) }
end
end
diff --git a/spec/models/concerns/redactable_spec.rb b/spec/models/concerns/redactable_spec.rb
index 3f6a2e2410c..bb59e04adf1 100644
--- a/spec/models/concerns/redactable_spec.rb
+++ b/spec/models/concerns/redactable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Redactable do
+RSpec.describe Redactable do
before do
stub_commonmark_sourcepos_disabled
end
diff --git a/spec/models/concerns/redis_cacheable_spec.rb b/spec/models/concerns/redis_cacheable_spec.rb
index 1cf6afcc167..c270f23defb 100644
--- a/spec/models/concerns/redis_cacheable_spec.rb
+++ b/spec/models/concerns/redis_cacheable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RedisCacheable do
+RSpec.describe RedisCacheable do
let(:model) do
Struct.new(:id, :attributes) do
def read_attribute(attribute)
diff --git a/spec/models/concerns/resolvable_discussion_spec.rb b/spec/models/concerns/resolvable_discussion_spec.rb
index 95553fb13a6..c91ddfee944 100644
--- a/spec/models/concerns/resolvable_discussion_spec.rb
+++ b/spec/models/concerns/resolvable_discussion_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Discussion, ResolvableDiscussion do
+RSpec.describe Discussion, ResolvableDiscussion do
subject { described_class.new([first_note, second_note, third_note]) }
let(:first_note) { create(:discussion_note_on_merge_request) }
diff --git a/spec/models/concerns/resolvable_note_spec.rb b/spec/models/concerns/resolvable_note_spec.rb
index 12e50ac807e..69c58a5cfe5 100644
--- a/spec/models/concerns/resolvable_note_spec.rb
+++ b/spec/models/concerns/resolvable_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Note, ResolvableNote do
+RSpec.describe Note, ResolvableNote do
let(:project) { create(:project, :repository) }
let(:merge_request) { create(:merge_request, source_project: project) }
diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb
index c891fdcb6b5..15d754861b2 100644
--- a/spec/models/concerns/routable_spec.rb
+++ b/spec/models/concerns/routable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Group, 'Routable' do
+RSpec.describe Group, 'Routable' do
let!(:group) { create(:group, name: 'foo') }
describe 'Validations' do
@@ -164,7 +164,7 @@ describe Group, 'Routable' do
end
end
-describe Project, 'Routable' do
+RSpec.describe Project, 'Routable' do
describe '#full_path' do
let(:project) { build_stubbed(:project) }
diff --git a/spec/models/concerns/safe_url_spec.rb b/spec/models/concerns/safe_url_spec.rb
index e523e6a15e4..3d38c05bf11 100644
--- a/spec/models/concerns/safe_url_spec.rb
+++ b/spec/models/concerns/safe_url_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SafeUrl do
+RSpec.describe SafeUrl do
describe '#safe_url' do
let(:safe_url_test_class) do
Class.new do
diff --git a/spec/models/concerns/schedulable_spec.rb b/spec/models/concerns/schedulable_spec.rb
index 38ae2112e01..875c2d80e55 100644
--- a/spec/models/concerns/schedulable_spec.rb
+++ b/spec/models/concerns/schedulable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Schedulable do
+RSpec.describe Schedulable do
shared_examples 'before_save callback' do
it 'updates next_run_at' do
expect { object.save! }.to change { object.next_run_at }
diff --git a/spec/models/concerns/sha256_attribute_spec.rb b/spec/models/concerns/sha256_attribute_spec.rb
index 213723c2dcb..c247865d77f 100644
--- a/spec/models/concerns/sha256_attribute_spec.rb
+++ b/spec/models/concerns/sha256_attribute_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Sha256Attribute do
+RSpec.describe Sha256Attribute do
let(:model) { Class.new { include Sha256Attribute } }
before do
diff --git a/spec/models/concerns/sha_attribute_spec.rb b/spec/models/concerns/sha_attribute_spec.rb
index 0d4dbfb215e..50748efcda4 100644
--- a/spec/models/concerns/sha_attribute_spec.rb
+++ b/spec/models/concerns/sha_attribute_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ShaAttribute do
+RSpec.describe ShaAttribute do
let(:model) { Class.new { include ShaAttribute } }
before do
diff --git a/spec/models/concerns/sortable_spec.rb b/spec/models/concerns/sortable_spec.rb
index a1fe5c0928d..bbfdaeec64c 100644
--- a/spec/models/concerns/sortable_spec.rb
+++ b/spec/models/concerns/sortable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Sortable do
+RSpec.describe Sortable do
describe '.order_by' do
let(:arel_table) { Group.arel_table }
let(:relation) { Group.all }
diff --git a/spec/models/concerns/spammable_spec.rb b/spec/models/concerns/spammable_spec.rb
index a8d27e174b7..d4fcb2e99eb 100644
--- a/spec/models/concerns/spammable_spec.rb
+++ b/spec/models/concerns/spammable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Spammable do
+RSpec.describe Spammable do
let(:issue) { create(:issue, description: 'Test Desc.') }
describe 'Associations' do
diff --git a/spec/models/concerns/stepable_spec.rb b/spec/models/concerns/stepable_spec.rb
index 51356c3eaf6..e442e4f0664 100644
--- a/spec/models/concerns/stepable_spec.rb
+++ b/spec/models/concerns/stepable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Stepable do
+RSpec.describe Stepable do
let(:described_class) do
Class.new do
include Stepable
diff --git a/spec/models/concerns/strip_attribute_spec.rb b/spec/models/concerns/strip_attribute_spec.rb
index 5c0d1042e06..812f0a015f7 100644
--- a/spec/models/concerns/strip_attribute_spec.rb
+++ b/spec/models/concerns/strip_attribute_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe StripAttribute do
+RSpec.describe StripAttribute do
let(:milestone) { create(:milestone) }
describe ".strip_attributes" do
diff --git a/spec/models/concerns/subscribable_spec.rb b/spec/models/concerns/subscribable_spec.rb
index f189cd7633c..2a43e748e58 100644
--- a/spec/models/concerns/subscribable_spec.rb
+++ b/spec/models/concerns/subscribable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Subscribable, 'Subscribable' do
+RSpec.describe Subscribable, 'Subscribable' do
let(:project) { create(:project) }
let(:resource) { create(:issue, project: project) }
let(:user_1) { create(:user) }
diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb
index 36eb8fdaba4..e0e764fc63c 100644
--- a/spec/models/concerns/token_authenticatable_spec.rb
+++ b/spec/models/concerns/token_authenticatable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-shared_examples 'TokenAuthenticatable' do
+RSpec.shared_examples 'TokenAuthenticatable' do
describe 'dynamically defined methods' do
it { expect(described_class).to respond_to("find_by_#{token_field}") }
it { is_expected.to respond_to("ensure_#{token_field}") }
@@ -11,7 +11,7 @@ shared_examples 'TokenAuthenticatable' do
end
end
-describe User, 'TokenAuthenticatable' do
+RSpec.describe User, 'TokenAuthenticatable' do
let(:token_field) { :feed_token }
it_behaves_like 'TokenAuthenticatable'
@@ -23,7 +23,7 @@ describe User, 'TokenAuthenticatable' do
end
end
-describe ApplicationSetting, 'TokenAuthenticatable' do
+RSpec.describe ApplicationSetting, 'TokenAuthenticatable' do
let(:token_field) { :runners_registration_token }
let(:settings) { described_class.new }
@@ -100,7 +100,7 @@ describe ApplicationSetting, 'TokenAuthenticatable' do
end
end
-describe PersonalAccessToken, 'TokenAuthenticatable' do
+RSpec.describe PersonalAccessToken, 'TokenAuthenticatable' do
shared_examples 'changes personal access token' do
it 'sets new token' do
subject
@@ -205,7 +205,7 @@ describe PersonalAccessToken, 'TokenAuthenticatable' do
end
end
-describe Ci::Build, 'TokenAuthenticatable' do
+RSpec.describe Ci::Build, 'TokenAuthenticatable' do
let(:token_field) { :token }
let(:build) { FactoryBot.build(:ci_build) }
diff --git a/spec/models/concerns/token_authenticatable_strategies/base_spec.rb b/spec/models/concerns/token_authenticatable_strategies/base_spec.rb
index 7332da309d5..bccef9b9554 100644
--- a/spec/models/concerns/token_authenticatable_strategies/base_spec.rb
+++ b/spec/models/concerns/token_authenticatable_strategies/base_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TokenAuthenticatableStrategies::Base do
+RSpec.describe TokenAuthenticatableStrategies::Base do
let(:instance) { double(:instance) }
let(:field) { double(:field) }
diff --git a/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb b/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb
index 70f41981b3b..f6b8cf7def4 100644
--- a/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb
+++ b/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TokenAuthenticatableStrategies::Encrypted do
+RSpec.describe TokenAuthenticatableStrategies::Encrypted do
let(:model) { double(:model) }
let(:instance) { double(:instance) }
diff --git a/spec/models/concerns/uniquify_spec.rb b/spec/models/concerns/uniquify_spec.rb
index 9ba35702ba6..9b79e4d4154 100644
--- a/spec/models/concerns/uniquify_spec.rb
+++ b/spec/models/concerns/uniquify_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Uniquify do
+RSpec.describe Uniquify do
let(:uniquify) { described_class.new }
describe "#string" do
diff --git a/spec/models/concerns/usage_statistics_spec.rb b/spec/models/concerns/usage_statistics_spec.rb
index f99f0a13317..15ccd08eda9 100644
--- a/spec/models/concerns/usage_statistics_spec.rb
+++ b/spec/models/concerns/usage_statistics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UsageStatistics do
+RSpec.describe UsageStatistics do
describe '.distinct_count_by' do
let_it_be(:issue_1) { create(:issue) }
let_it_be(:issue_2) { create(:issue) }
diff --git a/spec/models/concerns/where_composite_spec.rb b/spec/models/concerns/where_composite_spec.rb
index 1c0951d90d0..fb23e6bfe1d 100644
--- a/spec/models/concerns/where_composite_spec.rb
+++ b/spec/models/concerns/where_composite_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WhereComposite do
+RSpec.describe WhereComposite do
describe '.where_composite' do
let_it_be(:test_table_name) { "test_table_#{SecureRandom.hex(10)}" }
diff --git a/spec/models/concerns/x509_serial_number_attribute_spec.rb b/spec/models/concerns/x509_serial_number_attribute_spec.rb
index 18a1d85204c..88550823748 100644
--- a/spec/models/concerns/x509_serial_number_attribute_spec.rb
+++ b/spec/models/concerns/x509_serial_number_attribute_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe X509SerialNumberAttribute do
+RSpec.describe X509SerialNumberAttribute do
let(:model) { Class.new { include X509SerialNumberAttribute } }
before do
diff --git a/spec/models/container_registry/event_spec.rb b/spec/models/container_registry/event_spec.rb
index 54ff218f2a8..21a3ab5363a 100644
--- a/spec/models/container_registry/event_spec.rb
+++ b/spec/models/container_registry/event_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerRegistry::Event do
+RSpec.describe ContainerRegistry::Event do
using RSpec::Parameterized::TableSyntax
let_it_be(:group) { create(:group, name: 'group') }
diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb
index 4f23a905e93..953f92d103b 100644
--- a/spec/models/container_repository_spec.rb
+++ b/spec/models/container_repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerRepository do
+RSpec.describe ContainerRepository do
let(:group) { create(:group, name: 'group') }
let(:project) { create(:project, path: 'test', group: group) }
diff --git a/spec/models/custom_emoji_spec.rb b/spec/models/custom_emoji_spec.rb
new file mode 100644
index 00000000000..2b569b6097d
--- /dev/null
+++ b/spec/models/custom_emoji_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe CustomEmoji do
+ describe 'Associations' do
+ it { is_expected.to belong_to(:namespace) }
+ it { is_expected.to have_db_column(:file) }
+ it { is_expected.to validate_length_of(:name).is_at_most(36) }
+ it { is_expected.to validate_presence_of(:name) }
+ end
+
+ describe 'exclusion of duplicated emoji' do
+ let(:emoji_name) { Gitlab::Emoji.emojis_names.sample }
+
+ it 'disallows emoji names of built-in emoji' do
+ new_emoji = build(:custom_emoji, name: emoji_name)
+
+ expect(new_emoji).not_to be_valid
+ expect(new_emoji.errors.messages).to eq(name: ["#{emoji_name} is already being used for another emoji"])
+ end
+
+ it 'disallows duplicate custom emoji names within namespace' do
+ old_emoji = create(:custom_emoji)
+ new_emoji = build(:custom_emoji, name: old_emoji.name, namespace: old_emoji.namespace)
+
+ expect(new_emoji).not_to be_valid
+ expect(new_emoji.errors.messages).to eq(name: ["has already been taken"])
+ end
+ end
+end
diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb
index f6ab8e0ece6..8900c49a662 100644
--- a/spec/models/cycle_analytics/code_spec.rb
+++ b/spec/models/cycle_analytics/code_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'CycleAnalytics#code' do
+RSpec.describe 'CycleAnalytics#code' do
extend CycleAnalyticsHelpers::TestGeneration
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb
index b4ab763e0e6..9372ef5f0e6 100644
--- a/spec/models/cycle_analytics/issue_spec.rb
+++ b/spec/models/cycle_analytics/issue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'CycleAnalytics#issue' do
+RSpec.describe 'CycleAnalytics#issue' do
extend CycleAnalyticsHelpers::TestGeneration
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb
index 6765b2e2cbc..364694a11e1 100644
--- a/spec/models/cycle_analytics/plan_spec.rb
+++ b/spec/models/cycle_analytics/plan_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'CycleAnalytics#plan' do
+RSpec.describe 'CycleAnalytics#plan' do
extend CycleAnalyticsHelpers::TestGeneration
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb
index 2f2bcd63acd..cf4d57d6b73 100644
--- a/spec/models/cycle_analytics/production_spec.rb
+++ b/spec/models/cycle_analytics/production_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'CycleAnalytics#production' do
+RSpec.describe 'CycleAnalytics#production' do
extend CycleAnalyticsHelpers::TestGeneration
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/models/cycle_analytics/project_level_spec.rb b/spec/models/cycle_analytics/project_level_spec.rb
index bb296351a29..c2d421c03d8 100644
--- a/spec/models/cycle_analytics/project_level_spec.rb
+++ b/spec/models/cycle_analytics/project_level_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CycleAnalytics::ProjectLevel do
+RSpec.describe CycleAnalytics::ProjectLevel do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:from_date) { 10.days.ago }
let_it_be(:user) { project.owner }
diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb
index 25e8f1441d3..6ebbcebd71d 100644
--- a/spec/models/cycle_analytics/review_spec.rb
+++ b/spec/models/cycle_analytics/review_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'CycleAnalytics#review' do
+RSpec.describe 'CycleAnalytics#review' do
extend CycleAnalyticsHelpers::TestGeneration
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb
index effbc7056cc..024625d229f 100644
--- a/spec/models/cycle_analytics/staging_spec.rb
+++ b/spec/models/cycle_analytics/staging_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'CycleAnalytics#staging' do
+RSpec.describe 'CycleAnalytics#staging' do
extend CycleAnalyticsHelpers::TestGeneration
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb
index 7e7ba4d9994..7010d69f8a4 100644
--- a/spec/models/cycle_analytics/test_spec.rb
+++ b/spec/models/cycle_analytics/test_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'CycleAnalytics#test' do
+RSpec.describe 'CycleAnalytics#test' do
extend CycleAnalyticsHelpers::TestGeneration
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb
index ec6cfb6b826..00114a94b56 100644
--- a/spec/models/deploy_key_spec.rb
+++ b/spec/models/deploy_key_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeployKey, :mailer do
+RSpec.describe DeployKey, :mailer do
describe "Associations" do
it { is_expected.to have_many(:deploy_keys_projects) }
it { is_expected.to have_many(:projects) }
diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb
index 1dbae78a01d..7dd4d3129de 100644
--- a/spec/models/deploy_keys_project_spec.rb
+++ b/spec/models/deploy_keys_project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeployKeysProject do
+RSpec.describe DeployKeysProject do
describe "Associations" do
it { is_expected.to belong_to(:deploy_key) }
it { is_expected.to belong_to(:project) }
@@ -13,6 +13,21 @@ describe DeployKeysProject do
it { is_expected.to validate_presence_of(:deploy_key) }
end
+ describe '.with_deploy_keys' do
+ subject(:scoped_query) { described_class.with_deploy_keys.last }
+
+ it 'includes deploy_keys in query' do
+ project = create(:project)
+ create(:deploy_keys_project, project: project, deploy_key: create(:deploy_key))
+
+ includes_query_count = ActiveRecord::QueryRecorder.new { scoped_query }.count
+ deploy_key_query_count = ActiveRecord::QueryRecorder.new { scoped_query.deploy_key }.count
+
+ expect(includes_query_count).to eq(2)
+ expect(deploy_key_query_count).to eq(0)
+ end
+ end
+
describe "Destroying" do
let(:project) { create(:project) }
subject { create(:deploy_keys_project, project: project) }
diff --git a/spec/models/deploy_token_spec.rb b/spec/models/deploy_token_spec.rb
index 819e2850644..9fd3751be13 100644
--- a/spec/models/deploy_token_spec.rb
+++ b/spec/models/deploy_token_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeployToken do
+RSpec.describe DeployToken do
subject(:deploy_token) { create(:deploy_token) }
it { is_expected.to have_many :project_deploy_tokens }
diff --git a/spec/models/deployment_cluster_spec.rb b/spec/models/deployment_cluster_spec.rb
index 8bb09e9a510..dc9cbe4b082 100644
--- a/spec/models/deployment_cluster_spec.rb
+++ b/spec/models/deployment_cluster_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeploymentCluster do
+RSpec.describe DeploymentCluster do
let(:cluster) { create(:cluster) }
let(:deployment) { create(:deployment) }
let(:kubernetes_namespace) { 'an-example-namespace' }
diff --git a/spec/models/deployment_merge_request_spec.rb b/spec/models/deployment_merge_request_spec.rb
index fd5be52d47c..29834691fa4 100644
--- a/spec/models/deployment_merge_request_spec.rb
+++ b/spec/models/deployment_merge_request_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeploymentMergeRequest do
+RSpec.describe DeploymentMergeRequest do
let(:mr) { create(:merge_request, :merged) }
let(:deployment) { create(:deployment, :success, project: project) }
let(:project) { mr.project }
diff --git a/spec/models/deployment_metrics_spec.rb b/spec/models/deployment_metrics_spec.rb
index 5a4ae0bbe79..d0474777eb7 100644
--- a/spec/models/deployment_metrics_spec.rb
+++ b/spec/models/deployment_metrics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeploymentMetrics do
+RSpec.describe DeploymentMetrics do
describe '#has_metrics?' do
subject { described_class.new(deployment.project, deployment).has_metrics? }
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index ac2a4c9877d..b320390711e 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Deployment do
+RSpec.describe Deployment do
subject { build(:deployment) }
it { is_expected.to belong_to(:project).required }
diff --git a/spec/models/description_version_spec.rb b/spec/models/description_version_spec.rb
index 5ec34c0cde4..7c094f7a0a0 100644
--- a/spec/models/description_version_spec.rb
+++ b/spec/models/description_version_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DescriptionVersion do
+RSpec.describe DescriptionVersion do
describe 'associations' do
it { is_expected.to belong_to :issue }
it { is_expected.to belong_to :merge_request }
diff --git a/spec/models/design_management/action_spec.rb b/spec/models/design_management/action_spec.rb
index 753c31b1549..59c58191718 100644
--- a/spec/models/design_management/action_spec.rb
+++ b/spec/models/design_management/action_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe DesignManagement::Action do
+RSpec.describe DesignManagement::Action do
describe 'relations' do
it { is_expected.to belong_to(:design) }
it { is_expected.to belong_to(:version) }
diff --git a/spec/models/design_management/design_action_spec.rb b/spec/models/design_management/design_action_spec.rb
index da4ad41dfcb..958b1dd9124 100644
--- a/spec/models/design_management/design_action_spec.rb
+++ b/spec/models/design_management/design_action_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe DesignManagement::DesignAction do
+RSpec.describe DesignManagement::DesignAction do
describe 'validations' do
describe 'the design' do
let(:fail_validation) { raise_error(/design/i) }
diff --git a/spec/models/design_management/design_at_version_spec.rb b/spec/models/design_management/design_at_version_spec.rb
index f6fa8df243c..2c640ee5c2c 100644
--- a/spec/models/design_management/design_at_version_spec.rb
+++ b/spec/models/design_management/design_at_version_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DesignManagement::DesignAtVersion do
+RSpec.describe DesignManagement::DesignAtVersion do
include DesignManagementTestHelpers
let_it_be(:issue, reload: true) { create(:issue) }
diff --git a/spec/models/design_management/design_collection_spec.rb b/spec/models/design_management/design_collection_spec.rb
index bd48f742042..c5e290da759 100644
--- a/spec/models/design_management/design_collection_spec.rb
+++ b/spec/models/design_management/design_collection_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe DesignManagement::DesignCollection do
+RSpec.describe DesignManagement::DesignCollection do
include DesignManagementTestHelpers
let_it_be(:issue, reload: true) { create(:issue) }
diff --git a/spec/models/design_management/design_spec.rb b/spec/models/design_management/design_spec.rb
index bc1f54f057e..345147390c0 100644
--- a/spec/models/design_management/design_spec.rb
+++ b/spec/models/design_management/design_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DesignManagement::Design do
+RSpec.describe DesignManagement::Design do
include DesignManagementTestHelpers
let_it_be(:issue) { create(:issue) }
diff --git a/spec/models/design_management/repository_spec.rb b/spec/models/design_management/repository_spec.rb
index 996316eeec9..0115e0c139c 100644
--- a/spec/models/design_management/repository_spec.rb
+++ b/spec/models/design_management/repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DesignManagement::Repository do
+RSpec.describe DesignManagement::Repository do
let(:project) { create(:project) }
let(:repository) { described_class.new(project) }
diff --git a/spec/models/design_management/version_spec.rb b/spec/models/design_management/version_spec.rb
index ab6958ea94a..cd52f4129dc 100644
--- a/spec/models/design_management/version_spec.rb
+++ b/spec/models/design_management/version_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe DesignManagement::Version do
+RSpec.describe DesignManagement::Version do
let_it_be(:issue) { create(:issue) }
describe 'relations' do
diff --git a/spec/models/design_user_mention_spec.rb b/spec/models/design_user_mention_spec.rb
index 03c77c73c8d..944a82c5edf 100644
--- a/spec/models/design_user_mention_spec.rb
+++ b/spec/models/design_user_mention_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DesignUserMention do
+RSpec.describe DesignUserMention do
describe 'associations' do
it { is_expected.to belong_to(:design) }
it { is_expected.to belong_to(:note) }
diff --git a/spec/models/dev_ops_score/metric_spec.rb b/spec/models/dev_ops_score/metric_spec.rb
index 89212d5ca26..60001d0667d 100644
--- a/spec/models/dev_ops_score/metric_spec.rb
+++ b/spec/models/dev_ops_score/metric_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DevOpsScore::Metric do
+RSpec.describe DevOpsScore::Metric do
let(:conv_dev_index) { create(:dev_ops_score_metric) }
describe '#percentage_score' do
diff --git a/spec/models/diff_discussion_spec.rb b/spec/models/diff_discussion_spec.rb
index cfeb4382927..26b311fe629 100644
--- a/spec/models/diff_discussion_spec.rb
+++ b/spec/models/diff_discussion_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffDiscussion do
+RSpec.describe DiffDiscussion do
include RepoHelpers
subject { described_class.new([diff_note]) }
diff --git a/spec/models/diff_note_position_spec.rb b/spec/models/diff_note_position_spec.rb
index d93e0af5526..aa9e6b4e824 100644
--- a/spec/models/diff_note_position_spec.rb
+++ b/spec/models/diff_note_position_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffNotePosition, type: :model do
+RSpec.describe DiffNotePosition, type: :model do
describe '.create_or_update_by' do
context 'when a diff note' do
let(:note) { create(:diff_note_on_merge_request) }
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index 8bfe2ac7a6c..8a6176bf045 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffNote do
+RSpec.describe DiffNote do
include RepoHelpers
let_it_be(:merge_request) { create(:merge_request) }
diff --git a/spec/models/diff_viewer/base_spec.rb b/spec/models/diff_viewer/base_spec.rb
index 0a1c4c5560e..57c62788ee9 100644
--- a/spec/models/diff_viewer/base_spec.rb
+++ b/spec/models/diff_viewer/base_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffViewer::Base do
+RSpec.describe DiffViewer::Base do
include FakeBlobHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/models/diff_viewer/server_side_spec.rb b/spec/models/diff_viewer/server_side_spec.rb
index 0a14eae26f3..686dd1249be 100644
--- a/spec/models/diff_viewer/server_side_spec.rb
+++ b/spec/models/diff_viewer/server_side_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffViewer::ServerSide do
+RSpec.describe DiffViewer::ServerSide do
let_it_be(:project) { create(:project, :repository) }
let(:commit) { project.commit_by(oid: '570e7b2abdd848b95f2f578043fc23bd6f6fd24d') }
let!(:diff_file) { commit.diffs.diff_file_with_new_path('files/ruby/popen.rb') }
diff --git a/spec/models/discussion_spec.rb b/spec/models/discussion_spec.rb
index 950bdec4d00..021940be0c2 100644
--- a/spec/models/discussion_spec.rb
+++ b/spec/models/discussion_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Discussion do
+RSpec.describe Discussion do
subject { described_class.new([first_note, second_note, third_note]) }
let(:first_note) { create(:diff_note_on_merge_request) }
diff --git a/spec/models/draft_note_spec.rb b/spec/models/draft_note_spec.rb
index b880d3c5b97..64b06bf5c8f 100644
--- a/spec/models/draft_note_spec.rb
+++ b/spec/models/draft_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DraftNote do
+RSpec.describe DraftNote do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/models/email_spec.rb b/spec/models/email_spec.rb
index f7b194abcee..ffdc621dd4c 100644
--- a/spec/models/email_spec.rb
+++ b/spec/models/email_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Email do
+RSpec.describe Email do
describe 'modules' do
subject { described_class }
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index b93da518b68..c449a3c3c47 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Environment, :use_clean_rails_memory_store_caching do
+RSpec.describe Environment, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
using RSpec::Parameterized::TableSyntax
include RepoHelpers
@@ -18,6 +18,7 @@ describe Environment, :use_clean_rails_memory_store_caching do
it { is_expected.to belong_to(:project).required }
it { is_expected.to have_many(:deployments) }
it { is_expected.to have_many(:metrics_dashboard_annotations) }
+ it { is_expected.to have_many(:alert_management_alerts) }
it { is_expected.to delegate_method(:stop_action).to(:last_deployment) }
it { is_expected.to delegate_method(:manual_actions).to(:last_deployment) }
@@ -847,6 +848,20 @@ describe Environment, :use_clean_rails_memory_store_caching do
subject { environment.calculate_reactive_cache }
+ it 'overrides default reactive_cache_hard_limit to 10 Mb' do
+ expect(described_class.reactive_cache_hard_limit).to eq(10.megabyte)
+ end
+
+ it 'overrides reactive_cache_limit_enabled? with a FF' do
+ environment_with_enabled_ff = FactoryBot.build(:environment)
+ environment_with_disabled_ff = FactoryBot.build(:environment)
+
+ stub_feature_flags(reactive_caching_limit_environment: environment_with_enabled_ff.project)
+
+ expect(environment_with_enabled_ff.send(:reactive_cache_limit_enabled?)).to be_truthy
+ expect(environment_with_disabled_ff.send(:reactive_cache_limit_enabled?)).to be_falsey
+ end
+
it 'returns cache data from the deployment platform' do
expect(environment.deployment_platform).to receive(:calculate_reactive_cache_for)
.with(environment).and_return(pods: %w(pod1 pod2))
diff --git a/spec/models/environment_status_spec.rb b/spec/models/environment_status_spec.rb
index 10283b54796..7eefb8f714a 100644
--- a/spec/models/environment_status_spec.rb
+++ b/spec/models/environment_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EnvironmentStatus do
+RSpec.describe EnvironmentStatus do
include ProjectForksHelper
let(:deployment) { create(:deployment, :succeed, :review_app) }
diff --git a/spec/models/error_tracking/project_error_tracking_setting_spec.rb b/spec/models/error_tracking/project_error_tracking_setting_spec.rb
index b564c48a9c1..72ed11f6c74 100644
--- a/spec/models/error_tracking/project_error_tracking_setting_spec.rb
+++ b/spec/models/error_tracking/project_error_tracking_setting_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ErrorTracking::ProjectErrorTrackingSetting do
+RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
include ReactiveCachingHelpers
include Gitlab::Routing
diff --git a/spec/models/event_collection_spec.rb b/spec/models/event_collection_spec.rb
index 6d1954700bf..aca2a8c3a2f 100644
--- a/spec/models/event_collection_spec.rb
+++ b/spec/models/event_collection_spec.rb
@@ -2,7 +2,9 @@
require 'spec_helper'
-describe EventCollection do
+RSpec.describe EventCollection do
+ include DesignManagementTestHelpers
+
describe '#to_a' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project_empty_repo, group: group) }
@@ -10,6 +12,10 @@ describe EventCollection do
let_it_be(:user) { create(:user) }
let_it_be(:merge_request) { create(:merge_request) }
+ before do
+ enable_design_management
+ end
+
context 'with project events' do
let_it_be(:push_event_payloads) do
Array.new(9) do
@@ -21,11 +27,13 @@ describe EventCollection do
let_it_be(:merge_request_events) { create_list(:event, 10, :commented, project: project, target: merge_request) }
let_it_be(:closed_issue_event) { create(:closed_issue_event, project: project, author: user) }
let_it_be(:wiki_page_event) { create(:wiki_page_event, project: project) }
+ let_it_be(:design_event) { create(:design_event, project: project) }
let(:push_events) { push_event_payloads.map(&:event) }
it 'returns an Array of events', :aggregate_failures do
most_recent_20_events = [
wiki_page_event,
+ design_event,
closed_issue_event,
*push_events,
*merge_request_events
@@ -36,40 +44,23 @@ describe EventCollection do
expect(events).to match_array(most_recent_20_events)
end
- context 'the wiki_events feature flag is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it 'omits the wiki page events when using to_a' do
- events = described_class.new(projects).to_a
-
- expect(events).not_to include(wiki_page_event)
- end
-
- it 'omits the wiki page events when using all_project_events' do
- events = described_class.new(projects).all_project_events
+ it 'includes the wiki page events when using to_a' do
+ events = described_class.new(projects).to_a
- expect(events).not_to include(wiki_page_event)
- end
+ expect(events).to include(wiki_page_event)
end
- context 'the wiki_events feature flag is enabled' do
- before do
- stub_feature_flags(wiki_events: true)
- end
-
- it 'includes the wiki page events when using to_a' do
- events = described_class.new(projects).to_a
+ it 'includes the design events' do
+ collection = described_class.new(projects)
- expect(events).to include(wiki_page_event)
- end
+ expect(collection.to_a).to include(design_event)
+ expect(collection.all_project_events).to include(design_event)
+ end
- it 'includes the wiki page events when using all_project_events' do
- events = described_class.new(projects).all_project_events
+ it 'includes the wiki page events when using all_project_events' do
+ events = described_class.new(projects).all_project_events
- expect(events).to include(wiki_page_event)
- end
+ expect(events).to include(wiki_page_event)
end
it 'applies a limit to the number of events' do
@@ -81,7 +72,7 @@ describe EventCollection do
it 'can paginate through events' do
events = described_class.new(projects, offset: 20).to_a
- expect(events.length).to eq(1)
+ expect(events.length).to eq(2)
end
it 'returns an empty Array when crossing the maximum page number' do
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 14066b1e9d2..96baeab6809 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Event do
+RSpec.describe Event do
describe "Associations" do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:target) }
@@ -643,15 +643,6 @@ describe Event do
end
end
- describe '.not_design' do
- it 'does not contain the design events' do
- non_design_events = events.reject(&:design?)
-
- expect(events).not_to match_array(non_design_events)
- expect(described_class.not_design).to match_array(non_design_events)
- end
- end
-
describe '.for_wiki_page' do
it 'only contains the wiki page events' do
wiki_events = events.select(&:wiki_page?)
@@ -661,15 +652,6 @@ describe Event do
end
end
- describe '.not_wiki_page' do
- it 'does not contain the wiki page events' do
- non_wiki_events = events.reject(&:wiki_page?)
-
- expect(events).not_to match_array(non_wiki_events)
- expect(described_class.not_wiki_page).to match_array(non_wiki_events)
- end
- end
-
describe '.for_wiki_meta' do
it 'finds events for a given wiki page metadata object' do
event = events.select(&:wiki_page?).first
diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb
index b8d85d49b07..47b13ff50cf 100644
--- a/spec/models/external_issue_spec.rb
+++ b/spec/models/external_issue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExternalIssue do
+RSpec.describe ExternalIssue do
let(:project) { double('project', id: 1, to_reference: 'namespace1/project1') }
let(:issue) { described_class.new('EXT-1234', project) }
diff --git a/spec/models/external_pull_request_spec.rb b/spec/models/external_pull_request_spec.rb
index e85d5b2f6c7..e0822fc177a 100644
--- a/spec/models/external_pull_request_spec.rb
+++ b/spec/models/external_pull_request_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExternalPullRequest do
+RSpec.describe ExternalPullRequest do
let(:project) { create(:project) }
let(:source_branch) { 'the-branch' }
let(:status) { :open }
diff --git a/spec/models/fork_network_member_spec.rb b/spec/models/fork_network_member_spec.rb
index d7a0dd5be65..b34eb7964ca 100644
--- a/spec/models/fork_network_member_spec.rb
+++ b/spec/models/fork_network_member_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ForkNetworkMember do
+RSpec.describe ForkNetworkMember do
describe 'validations' do
it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_presence_of(:fork_network) }
diff --git a/spec/models/fork_network_spec.rb b/spec/models/fork_network_spec.rb
index 5ec0f8d6b02..c2ef1fdcb5f 100644
--- a/spec/models/fork_network_spec.rb
+++ b/spec/models/fork_network_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ForkNetwork do
+RSpec.describe ForkNetwork do
include ProjectForksHelper
describe '#add_root_as_member' do
diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb
index c8ed898122b..6fe5a1407a9 100644
--- a/spec/models/generic_commit_status_spec.rb
+++ b/spec/models/generic_commit_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GenericCommitStatus do
+RSpec.describe GenericCommitStatus do
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:external_url) { 'http://example.gitlab.com/status' }
diff --git a/spec/models/gpg_key_spec.rb b/spec/models/gpg_key_spec.rb
index b9c914e2506..7ecde04e3df 100644
--- a/spec/models/gpg_key_spec.rb
+++ b/spec/models/gpg_key_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GpgKey do
+RSpec.describe GpgKey do
describe "associations" do
it { is_expected.to belong_to(:user) }
it { is_expected.to have_many(:subkeys) }
diff --git a/spec/models/gpg_key_subkey_spec.rb b/spec/models/gpg_key_subkey_spec.rb
index 5f80cc02924..c1d9e2bde43 100644
--- a/spec/models/gpg_key_subkey_spec.rb
+++ b/spec/models/gpg_key_subkey_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GpgKeySubkey do
+RSpec.describe GpgKeySubkey do
subject { build(:gpg_key_subkey) }
describe 'associations' do
diff --git a/spec/models/grafana_integration_spec.rb b/spec/models/grafana_integration_spec.rb
index 662e8b1dd61..79f102919ac 100644
--- a/spec/models/grafana_integration_spec.rb
+++ b/spec/models/grafana_integration_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GrafanaIntegration do
+RSpec.describe GrafanaIntegration do
describe 'associations' do
it { is_expected.to belong_to(:project) }
end
diff --git a/spec/models/group_custom_attribute_spec.rb b/spec/models/group_custom_attribute_spec.rb
index 7d60c74b62b..1d8afa71377 100644
--- a/spec/models/group_custom_attribute_spec.rb
+++ b/spec/models/group_custom_attribute_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupCustomAttribute do
+RSpec.describe GroupCustomAttribute do
describe 'assocations' do
it { is_expected.to belong_to(:group) }
end
diff --git a/spec/models/group_deploy_key_spec.rb b/spec/models/group_deploy_key_spec.rb
index 3ba56c7e504..3fe71cc4699 100644
--- a/spec/models/group_deploy_key_spec.rb
+++ b/spec/models/group_deploy_key_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupDeployKey do
+RSpec.describe GroupDeployKey do
it { is_expected.to validate_presence_of(:user) }
it 'is of type DeployKey' do
diff --git a/spec/models/group_group_link_spec.rb b/spec/models/group_group_link_spec.rb
index 54e622b2f22..03cc9d7e64c 100644
--- a/spec/models/group_group_link_spec.rb
+++ b/spec/models/group_group_link_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupGroupLink do
+RSpec.describe GroupGroupLink do
let_it_be(:group) { create(:group) }
let_it_be(:shared_group) { create(:group) }
let_it_be(:group_group_link) do
diff --git a/spec/models/group_import_state_spec.rb b/spec/models/group_import_state_spec.rb
index 9d9cb1e8391..4404ef64966 100644
--- a/spec/models/group_import_state_spec.rb
+++ b/spec/models/group_import_state_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupImportState do
+RSpec.describe GroupImportState do
describe 'validations' do
let_it_be(:group) { create(:group) }
diff --git a/spec/models/group_label_spec.rb b/spec/models/group_label_spec.rb
index a3a5c631c3d..ec9244d5eb5 100644
--- a/spec/models/group_label_spec.rb
+++ b/spec/models/group_label_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupLabel do
+RSpec.describe GroupLabel do
describe 'relationships' do
it { is_expected.to belong_to(:group) }
end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 93cb6d83489..4184f2d07cc 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Group do
+RSpec.describe Group do
let!(:group) { create(:group) }
describe 'associations' do
@@ -1313,4 +1313,231 @@ describe Group do
expect(groups).to contain_exactly(parent_group1, parent_group2, child_group1, child_group2, child_group3)
end
end
+
+ describe '#shared_runners_allowed?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:shared_runners_enabled, :allow_descendants_override, :expected_shared_runners_allowed) do
+ true | false | true
+ true | true | true
+ false | false | false
+ false | true | true
+ end
+
+ with_them do
+ let!(:group) { create(:group, shared_runners_enabled: shared_runners_enabled, allow_descendants_override_disabled_shared_runners: allow_descendants_override) }
+
+ it 'returns the expected result' do
+ expect(group.shared_runners_allowed?).to eq(expected_shared_runners_allowed)
+ end
+ end
+ end
+
+ describe '#parent_allows_shared_runners?' do
+ context 'when parent group is present' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:shared_runners_enabled, :allow_descendants_override, :expected_shared_runners_allowed) do
+ true | false | true
+ true | true | true
+ false | false | false
+ false | true | true
+ end
+
+ with_them do
+ let!(:parent_group) { create(:group, shared_runners_enabled: shared_runners_enabled, allow_descendants_override_disabled_shared_runners: allow_descendants_override) }
+ let!(:group) { create(:group, parent: parent_group) }
+
+ it 'returns the expected result' do
+ expect(group.parent_allows_shared_runners?).to eq(expected_shared_runners_allowed)
+ end
+ end
+ end
+
+ context 'when parent group is missing' do
+ let!(:group) { create(:group) }
+
+ it 'returns true' do
+ expect(group.parent_allows_shared_runners?).to be_truthy
+ end
+ end
+ end
+
+ describe '#parent_enabled_shared_runners?' do
+ subject { group.parent_enabled_shared_runners? }
+
+ context 'when parent group is present' do
+ context 'When shared Runners are disabled' do
+ let!(:parent_group) { create(:group, :shared_runners_disabled) }
+ let!(:group) { create(:group, parent: parent_group) }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'When shared Runners are enabled' do
+ let!(:parent_group) { create(:group) }
+ let!(:group) { create(:group, parent: parent_group) }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'when parent group is missing' do
+ let!(:group) { create(:group) }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ describe '#enable_shared_runners!' do
+ subject { group.enable_shared_runners! }
+
+ context 'group that its ancestors have shared runners disabled' do
+ let_it_be(:parent) { create(:group, :shared_runners_disabled) }
+ let_it_be(:group) { create(:group, :shared_runners_disabled, parent: parent) }
+ let_it_be(:project) { create(:project, shared_runners_enabled: false, group: group) }
+
+ it 'raises error and does not enable shared Runners' do
+ expect { subject }
+ .to raise_error(described_class::UpdateSharedRunnersError, 'Shared Runners disabled for the parent group')
+ .and not_change { parent.reload.shared_runners_enabled }
+ .and not_change { group.reload.shared_runners_enabled }
+ .and not_change { project.reload.shared_runners_enabled }
+ end
+ end
+
+ context 'root group with shared runners disabled' do
+ let_it_be(:group) { create(:group, :shared_runners_disabled) }
+ let_it_be(:sub_group) { create(:group, :shared_runners_disabled, parent: group) }
+ let_it_be(:project) { create(:project, shared_runners_enabled: false, group: sub_group) }
+
+ it 'enables shared Runners only for itself' do
+ expect { subject }
+ .to change { group.reload.shared_runners_enabled }.from(false).to(true)
+ .and not_change { sub_group.reload.shared_runners_enabled }
+ .and not_change { project.reload.shared_runners_enabled }
+ end
+ end
+ end
+
+ describe '#disable_shared_runners!' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:sub_group) { create(:group, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners, parent: group) }
+ let_it_be(:sub_group_2) { create(:group, parent: group) }
+ let_it_be(:project) { create(:project, group: group, shared_runners_enabled: true) }
+ let_it_be(:project_2) { create(:project, group: sub_group_2, shared_runners_enabled: true) }
+
+ subject { group.disable_shared_runners! }
+
+ it 'disables shared Runners for all descendant groups and projects' do
+ expect { subject }
+ .to change { group.reload.shared_runners_enabled }.from(true).to(false)
+ .and not_change { group.reload.allow_descendants_override_disabled_shared_runners }
+ .and not_change { sub_group.reload.shared_runners_enabled }
+ .and not_change { sub_group.reload.allow_descendants_override_disabled_shared_runners }
+ .and change { sub_group_2.reload.shared_runners_enabled }.from(true).to(false)
+ .and not_change { sub_group_2.reload.allow_descendants_override_disabled_shared_runners }
+ .and change { project.reload.shared_runners_enabled }.from(true).to(false)
+ .and change { project_2.reload.shared_runners_enabled }.from(true).to(false)
+ end
+ end
+
+ describe '#allow_descendants_override_disabled_shared_runners!' do
+ subject { group.allow_descendants_override_disabled_shared_runners! }
+
+ context 'top level group' do
+ let_it_be(:group) { create(:group, :shared_runners_disabled) }
+ let_it_be(:sub_group) { create(:group, :shared_runners_disabled, parent: group) }
+ let_it_be(:project) { create(:project, shared_runners_enabled: false, group: sub_group) }
+
+ it 'enables allow descendants to override only for itself' do
+ expect { subject }
+ .to change { group.reload.allow_descendants_override_disabled_shared_runners }.from(false).to(true)
+ .and not_change { group.reload.shared_runners_enabled }
+ .and not_change { sub_group.reload.allow_descendants_override_disabled_shared_runners }
+ .and not_change { sub_group.reload.shared_runners_enabled }
+ .and not_change { project.reload.shared_runners_enabled }
+ end
+ end
+
+ context 'group that its ancestors have shared Runners disabled but allows to override' do
+ let_it_be(:parent) { create(:group, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners) }
+ let_it_be(:group) { create(:group, :shared_runners_disabled, parent: parent) }
+ let_it_be(:project) { create(:project, shared_runners_enabled: false, group: group) }
+
+ it 'enables allow descendants to override' do
+ expect { subject }
+ .to not_change { parent.reload.allow_descendants_override_disabled_shared_runners }
+ .and not_change { parent.reload.shared_runners_enabled }
+ .and change { group.reload.allow_descendants_override_disabled_shared_runners }.from(false).to(true)
+ .and not_change { group.reload.shared_runners_enabled }
+ .and not_change { project.reload.shared_runners_enabled }
+ end
+ end
+
+ context 'when parent does not allow' do
+ let_it_be(:parent) { create(:group, :shared_runners_disabled, allow_descendants_override_disabled_shared_runners: false ) }
+ let_it_be(:group) { create(:group, :shared_runners_disabled, allow_descendants_override_disabled_shared_runners: false, parent: parent) }
+
+ it 'raises error and does not allow descendants to override' do
+ expect { subject }
+ .to raise_error(described_class::UpdateSharedRunnersError, 'Group level shared Runners not allowed')
+ .and not_change { parent.reload.allow_descendants_override_disabled_shared_runners }
+ .and not_change { parent.reload.shared_runners_enabled }
+ .and not_change { group.reload.allow_descendants_override_disabled_shared_runners }
+ .and not_change { group.reload.shared_runners_enabled }
+ end
+ end
+
+ context 'top level group that has shared Runners enabled' do
+ let_it_be(:group) { create(:group, shared_runners_enabled: true) }
+ let_it_be(:sub_group) { create(:group, :shared_runners_disabled, parent: group) }
+ let_it_be(:project) { create(:project, shared_runners_enabled: false, group: sub_group) }
+
+ it 'raises error and does not change config' do
+ expect { subject }
+ .to raise_error(described_class::UpdateSharedRunnersError, 'Shared Runners enabled')
+ .and not_change { group.reload.allow_descendants_override_disabled_shared_runners }
+ .and not_change { group.reload.shared_runners_enabled }
+ .and not_change { sub_group.reload.allow_descendants_override_disabled_shared_runners }
+ .and not_change { sub_group.reload.shared_runners_enabled }
+ .and not_change { project.reload.shared_runners_enabled }
+ end
+ end
+ end
+
+ describe '#disallow_descendants_override_disabled_shared_runners!' do
+ subject { group.disallow_descendants_override_disabled_shared_runners! }
+
+ context 'top level group' do
+ let_it_be(:group) { create(:group, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners ) }
+ let_it_be(:sub_group) { create(:group, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners, parent: group) }
+ let_it_be(:project) { create(:project, shared_runners_enabled: true, group: sub_group) }
+
+ it 'disables allow project to override for descendants and disables project shared Runners' do
+ expect { subject }
+ .to not_change { group.reload.shared_runners_enabled }
+ .and change { group.reload.allow_descendants_override_disabled_shared_runners }.from(true).to(false)
+ .and not_change { sub_group.reload.shared_runners_enabled }
+ .and change { sub_group.reload.allow_descendants_override_disabled_shared_runners }.from(true).to(false)
+ .and change { project.reload.shared_runners_enabled }.from(true).to(false)
+ end
+ end
+
+ context 'top level group that has shared Runners enabled' do
+ let_it_be(:group) { create(:group, shared_runners_enabled: true) }
+ let_it_be(:sub_group) { create(:group, :shared_runners_disabled, parent: group) }
+ let_it_be(:project) { create(:project, shared_runners_enabled: false, group: sub_group) }
+
+ it 'results error and does not change config' do
+ expect { subject }
+ .to raise_error(described_class::UpdateSharedRunnersError, 'Shared Runners enabled')
+ .and not_change { group.reload.allow_descendants_override_disabled_shared_runners }
+ .and not_change { group.reload.shared_runners_enabled }
+ .and not_change { sub_group.reload.allow_descendants_override_disabled_shared_runners }
+ .and not_change { sub_group.reload.shared_runners_enabled }
+ .and not_change { project.reload.shared_runners_enabled }
+ end
+ end
+ end
end
diff --git a/spec/models/guest_spec.rb b/spec/models/guest_spec.rb
index 57eb077031c..975b64cb855 100644
--- a/spec/models/guest_spec.rb
+++ b/spec/models/guest_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Guest do
+RSpec.describe Guest do
let_it_be(:public_project, reload: true) { create(:project, :public) }
let_it_be(:private_project) { create(:project, :private) }
let_it_be(:internal_project) { create(:project, :internal) }
diff --git a/spec/models/hooks/active_hook_filter_spec.rb b/spec/models/hooks/active_hook_filter_spec.rb
index 1249c793f7f..1f693ce9fde 100644
--- a/spec/models/hooks/active_hook_filter_spec.rb
+++ b/spec/models/hooks/active_hook_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ActiveHookFilter do
+RSpec.describe ActiveHookFilter do
subject(:filter) { described_class.new(hook) }
describe '#matches?' do
diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb
index ccf8171049d..69fbc4c3b4f 100644
--- a/spec/models/hooks/project_hook_spec.rb
+++ b/spec/models/hooks/project_hook_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectHook do
+RSpec.describe ProjectHook do
describe 'associations' do
it { is_expected.to belong_to :project }
end
diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb
index 936c2fbad27..f7045d7ac5e 100644
--- a/spec/models/hooks/service_hook_spec.rb
+++ b/spec/models/hooks/service_hook_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ServiceHook do
+RSpec.describe ServiceHook do
describe 'associations' do
it { is_expected.to belong_to :service }
end
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index 2e836c19e3c..e56d08c1847 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe SystemHook do
+RSpec.describe SystemHook do
context 'default attributes' do
let(:system_hook) { build(:system_hook) }
diff --git a/spec/models/hooks/web_hook_log_spec.rb b/spec/models/hooks/web_hook_log_spec.rb
index 128601794cf..8dd9cf9e84a 100644
--- a/spec/models/hooks/web_hook_log_spec.rb
+++ b/spec/models/hooks/web_hook_log_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WebHookLog do
+RSpec.describe WebHookLog do
it { is_expected.to belong_to(:web_hook) }
it { is_expected.to serialize(:request_headers).as(Hash) }
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index 025c11d6407..3fc1ad6eb0d 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WebHook do
+RSpec.describe WebHook do
let(:hook) { build(:project_hook) }
describe 'associations' do
diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb
index 9f120775a3c..696d33b7beb 100644
--- a/spec/models/identity_spec.rb
+++ b/spec/models/identity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Identity do
+RSpec.describe Identity do
describe 'relations' do
it { is_expected.to belong_to(:user) }
end
diff --git a/spec/models/import_export_upload_spec.rb b/spec/models/import_export_upload_spec.rb
index 18a714f4d98..46a611852ab 100644
--- a/spec/models/import_export_upload_spec.rb
+++ b/spec/models/import_export_upload_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ImportExportUpload do
+RSpec.describe ImportExportUpload do
subject { described_class.new(project: create(:project)) }
shared_examples 'stores the Import/Export file' do |method|
diff --git a/spec/models/import_failure_spec.rb b/spec/models/import_failure_spec.rb
index d286a4ad314..cdef125e890 100644
--- a/spec/models/import_failure_spec.rb
+++ b/spec/models/import_failure_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ImportFailure do
+RSpec.describe ImportFailure do
describe 'Scopes' do
let_it_be(:project) { create(:project) }
let_it_be(:correlation_id) { 'ABC' }
diff --git a/spec/models/incident_management/project_incident_management_setting_spec.rb b/spec/models/incident_management/project_incident_management_setting_spec.rb
index ac3f97e2d89..effd89e970c 100644
--- a/spec/models/incident_management/project_incident_management_setting_spec.rb
+++ b/spec/models/incident_management/project_incident_management_setting_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IncidentManagement::ProjectIncidentManagementSetting do
+RSpec.describe IncidentManagement::ProjectIncidentManagementSetting do
let_it_be(:project) { create(:project, :repository, create_templates: :issue) }
describe 'Associations' do
@@ -108,4 +108,42 @@ describe IncidentManagement::ProjectIncidentManagementSetting do
it_behaves_like 'no content'
end
end
+
+ describe '#pagerduty_token' do
+ let(:active) { true }
+
+ subject do
+ create(:project_incident_management_setting, project: project, pagerduty_active: active, pagerduty_token: token)
+ end
+
+ context 'when token already set' do
+ let(:token) { SecureRandom.hex }
+
+ it 'reads the token' do
+ expect(subject.pagerduty_token).to eq(token)
+ expect(subject.encrypted_pagerduty_token).not_to be_nil
+ expect(subject.encrypted_pagerduty_token_iv).not_to be_nil
+ end
+ end
+
+ context 'when not set' do
+ let(:token) { nil }
+
+ context 'when PagerDuty webhook is active' do
+ it 'generates a token before validation' do
+ expect(subject).to be_valid
+ expect(subject.pagerduty_token).to match(/\A\h{32}\z/)
+ end
+ end
+
+ context 'when PagerDuty webhook is not active' do
+ let(:active) { false }
+
+ it 'does not generate a token before validation' do
+ expect(subject).to be_valid
+ expect(subject.pagerduty_token).to be_nil
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/instance_configuration_spec.rb b/spec/models/instance_configuration_spec.rb
index 747e9dc2faa..383e548c324 100644
--- a/spec/models/instance_configuration_spec.rb
+++ b/spec/models/instance_configuration_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe InstanceConfiguration do
+RSpec.describe InstanceConfiguration do
context 'without cache' do
describe '#settings' do
describe '#ssh_algorithms_hashes' do
diff --git a/spec/models/integration_spec.rb b/spec/models/integration_spec.rb
index 3042fd15a7b..87ba0f3f7e6 100644
--- a/spec/models/integration_spec.rb
+++ b/spec/models/integration_spec.rb
@@ -3,8 +3,9 @@
require 'spec_helper'
RSpec.describe Integration do
- let(:project_1) { create(:project) }
- let(:project_2) { create(:project) }
+ let!(:project_1) { create(:project) }
+ let!(:project_2) { create(:project) }
+ let!(:project_3) { create(:project) }
let(:instance_integration) { create(:jira_service, :instance) }
before do
@@ -18,4 +19,10 @@ RSpec.describe Integration do
expect(Project.with_custom_integration_for(instance_integration)).to contain_exactly(project_2)
end
end
+
+ describe '#ids_without_integration' do
+ it 'returns projects ids without an integration' do
+ expect(Project.ids_without_integration(instance_integration, 100)).to contain_exactly(project_3.id)
+ end
+ end
end
diff --git a/spec/models/internal_id_spec.rb b/spec/models/internal_id_spec.rb
index 0dfb59cf43a..751e8724872 100644
--- a/spec/models/internal_id_spec.rb
+++ b/spec/models/internal_id_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe InternalId do
+RSpec.describe InternalId do
let(:project) { create(:project) }
let(:usage) { :issues }
let(:issue) { build(:issue, project: project) }
diff --git a/spec/models/issue/metrics_spec.rb b/spec/models/issue/metrics_spec.rb
index dc22d26e2f9..966e4321378 100644
--- a/spec/models/issue/metrics_spec.rb
+++ b/spec/models/issue/metrics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issue::Metrics do
+RSpec.describe Issue::Metrics do
let(:project) { create(:project) }
subject { create(:issue, project: project) }
diff --git a/spec/models/issue_assignee_spec.rb b/spec/models/issue_assignee_spec.rb
index 2d59ba15101..df8e91cd133 100644
--- a/spec/models/issue_assignee_spec.rb
+++ b/spec/models/issue_assignee_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssueAssignee do
+RSpec.describe IssueAssignee do
let(:issue) { create(:issue) }
subject { issue.issue_assignees.build(assignee: create(:user)) }
@@ -15,4 +15,37 @@ describe IssueAssignee do
describe 'validations' do
it { is_expected.to validate_uniqueness_of(:assignee).scoped_to(:issue_id) }
end
+
+ describe 'scopes' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:project_issue) { create(:issue, project: project, assignee_ids: [user.id]) }
+
+ before do
+ issue.update!(assignee_ids: [user.id])
+ end
+
+ context 'in_projects' do
+ it 'returns issue assignees for given project' do
+ expect(IssueAssignee.count).to eq 2
+
+ assignees = IssueAssignee.in_projects([project])
+
+ expect(assignees.count).to eq 1
+ expect(assignees.first.user_id).to eq project_issue.issue_assignees.first.user_id
+ expect(assignees.first.issue_id).to eq project_issue.issue_assignees.first.issue_id
+ end
+ end
+
+ context 'on_issues' do
+ it 'returns issue assignees for given issues' do
+ expect(IssueAssignee.count).to eq 2
+
+ assignees = IssueAssignee.on_issues([project_issue])
+
+ expect(assignees.count).to eq 1
+ expect(assignees.first.issue_id).to eq project_issue.issue_assignees.first.issue_id
+ end
+ end
+ end
end
diff --git a/spec/models/issue_collection_spec.rb b/spec/models/issue_collection_spec.rb
index 7fc635f100f..d67bd8debce 100644
--- a/spec/models/issue_collection_spec.rb
+++ b/spec/models/issue_collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssueCollection do
+RSpec.describe IssueCollection do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:issue1) { create(:issue, project: project) }
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 291cccd72db..80041d2e859 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issue do
+RSpec.describe Issue do
include ExternalAuthorizationServiceHelpers
describe "Associations" do
@@ -95,29 +95,6 @@ describe Issue do
end
end
- describe 'locking' do
- using RSpec::Parameterized::TableSyntax
-
- where(:lock_version) do
- [
- [0],
- ["0"]
- ]
- end
-
- with_them do
- it 'works when an issue has a NULL lock_version' do
- issue = create(:issue)
-
- described_class.where(id: issue.id).update_all('lock_version = NULL')
-
- issue.update!(lock_version: lock_version, title: 'locking test')
-
- expect(issue.reload.title).to eq('locking test')
- end
- end
- end
-
describe '.simple_sorts' do
it 'includes all keys' do
expect(described_class.simple_sorts.keys).to include(
@@ -406,6 +383,22 @@ describe Issue do
end
end
+ describe '#from_service_desk?' do
+ subject { issue.from_service_desk? }
+
+ context 'when issue author is support bot' do
+ let(:issue) { create(:issue, author: ::User.support_bot) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when issue author is not support bot' do
+ let(:issue) { create(:issue) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
describe '#suggested_branch_name' do
let(:repository) { double }
@@ -1002,6 +995,16 @@ describe Issue do
end
end
+ describe '.service_desk' do
+ it 'returns the service desk issue' do
+ service_desk_issue = create(:issue, author: ::User.support_bot)
+ regular_issue = create(:issue)
+
+ expect(described_class.service_desk).to include(service_desk_issue)
+ expect(described_class.service_desk).not_to include(regular_issue)
+ end
+ end
+
it_behaves_like 'throttled touch' do
subject { create(:issue, updated_at: 1.hour.ago) }
end
diff --git a/spec/models/iteration_spec.rb b/spec/models/iteration_spec.rb
index ae14adf9106..ef638330208 100644
--- a/spec/models/iteration_spec.rb
+++ b/spec/models/iteration_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Iteration do
+RSpec.describe Iteration do
let_it_be(:project) { create(:project) }
let_it_be(:group) { create(:group) }
@@ -45,6 +45,14 @@ describe Iteration do
it { is_expected.to be_valid }
end
+ context 'when updated iteration dates overlap with its own dates' do
+ it 'is valid' do
+ existing_iteration.start_date = 5.days.from_now
+
+ expect(existing_iteration).to be_valid
+ end
+ end
+
context 'when dates overlap' do
context 'same group' do
context 'when start_date is in range' do
@@ -138,6 +146,25 @@ describe Iteration do
end
end
+ context 'time scopes' do
+ let_it_be(:project) { create(:project, :empty_repo) }
+ let_it_be(:iteration_1) { create(:iteration, :skip_future_date_validation, project: project, start_date: 3.days.ago, due_date: 1.day.from_now) }
+ let_it_be(:iteration_2) { create(:iteration, :skip_future_date_validation, project: project, start_date: 10.days.ago, due_date: 4.days.ago) }
+ let_it_be(:iteration_3) { create(:iteration, project: project, start_date: 4.days.from_now, due_date: 1.week.from_now) }
+
+ describe 'start_date_passed' do
+ it 'returns iterations where start_date is in the past but due_date is in the future' do
+ expect(described_class.start_date_passed).to contain_exactly(iteration_1)
+ end
+ end
+
+ describe 'due_date_passed' do
+ it 'returns iterations where due date is in the past' do
+ expect(described_class.due_date_passed).to contain_exactly(iteration_2)
+ end
+ end
+ end
+
describe '.within_timeframe' do
let_it_be(:now) { Time.current }
let_it_be(:project) { create(:project, :empty_repo) }
diff --git a/spec/models/jira_import_state_spec.rb b/spec/models/jira_import_state_spec.rb
index d2535636c63..e982b7353ba 100644
--- a/spec/models/jira_import_state_spec.rb
+++ b/spec/models/jira_import_state_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe JiraImportState do
+RSpec.describe JiraImportState do
describe "associations" do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:user) }
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 8cdedbcdedf..1e14864676c 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Key, :mailer do
+RSpec.describe Key, :mailer do
describe "Associations" do
it { is_expected.to belong_to(:user) }
end
diff --git a/spec/models/label_link_spec.rb b/spec/models/label_link_spec.rb
index 7a179dcb419..a95481f3083 100644
--- a/spec/models/label_link_spec.rb
+++ b/spec/models/label_link_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LabelLink do
+RSpec.describe LabelLink do
it { expect(build(:label_link)).to be_valid }
it { is_expected.to belong_to(:label) }
diff --git a/spec/models/label_note_spec.rb b/spec/models/label_note_spec.rb
index 34560acfa9e..0bf202ce2b1 100644
--- a/spec/models/label_note_spec.rb
+++ b/spec/models/label_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LabelNote do
+RSpec.describe LabelNote do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let_it_be(:label) { create(:label, project: project) }
diff --git a/spec/models/label_priority_spec.rb b/spec/models/label_priority_spec.rb
index 1a93468290f..db961d5a4e6 100644
--- a/spec/models/label_priority_spec.rb
+++ b/spec/models/label_priority_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LabelPriority do
+RSpec.describe LabelPriority do
describe 'relationships' do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:label) }
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index dc878c2d3c0..e1abfd9d8e5 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Label do
+RSpec.describe Label do
describe 'modules' do
it { is_expected.to include_module(Referable) }
it { is_expected.to include_module(Subscribable) }
diff --git a/spec/models/legacy_diff_discussion_spec.rb b/spec/models/legacy_diff_discussion_spec.rb
index 49ea319fbd1..4f90fe7d7f0 100644
--- a/spec/models/legacy_diff_discussion_spec.rb
+++ b/spec/models/legacy_diff_discussion_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LegacyDiffDiscussion do
+RSpec.describe LegacyDiffDiscussion do
subject { create(:legacy_diff_note_on_merge_request).to_discussion }
describe '#reply_attributes' do
diff --git a/spec/models/lfs_download_object_spec.rb b/spec/models/lfs_download_object_spec.rb
index d7522fbb969..d1c323cd177 100644
--- a/spec/models/lfs_download_object_spec.rb
+++ b/spec/models/lfs_download_object_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LfsDownloadObject do
+RSpec.describe LfsDownloadObject do
let(:oid) { 'cd293be6cea034bd45a0352775a219ef5dc7825ce55d1f7dae9762d80ce64411' }
let(:link) { 'http://www.example.com' }
let(:size) { 1 }
diff --git a/spec/models/lfs_file_lock_spec.rb b/spec/models/lfs_file_lock_spec.rb
index 0a47ded43fb..d3f79c7c7cf 100644
--- a/spec/models/lfs_file_lock_spec.rb
+++ b/spec/models/lfs_file_lock_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LfsFileLock do
+RSpec.describe LfsFileLock do
let_it_be(:lfs_file_lock, reload: true) { create(:lfs_file_lock) }
subject { lfs_file_lock }
diff --git a/spec/models/lfs_object_spec.rb b/spec/models/lfs_object_spec.rb
index 09a64dabb08..36d45f17392 100644
--- a/spec/models/lfs_object_spec.rb
+++ b/spec/models/lfs_object_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LfsObject do
+RSpec.describe LfsObject do
context 'scopes' do
describe '.not_existing_in_project' do
it 'contains only lfs objects not linked to the project' do
diff --git a/spec/models/lfs_objects_project_spec.rb b/spec/models/lfs_objects_project_spec.rb
index 31300828a43..71009a6f28f 100644
--- a/spec/models/lfs_objects_project_spec.rb
+++ b/spec/models/lfs_objects_project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LfsObjectsProject do
+RSpec.describe LfsObjectsProject do
let_it_be(:project) { create(:project) }
subject do
diff --git a/spec/models/license_template_spec.rb b/spec/models/license_template_spec.rb
index 7037277e580..515f728f515 100644
--- a/spec/models/license_template_spec.rb
+++ b/spec/models/license_template_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LicenseTemplate do
+RSpec.describe LicenseTemplate do
describe '#content' do
it 'calls a proc exactly once if provided' do
content_proc = -> { 'bar' }
diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb
index bc9124e73af..37158584062 100644
--- a/spec/models/list_spec.rb
+++ b/spec/models/list_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe List do
+RSpec.describe List do
it_behaves_like 'having unique enum values'
describe 'relationships' do
diff --git a/spec/models/list_user_preference_spec.rb b/spec/models/list_user_preference_spec.rb
index 10a7bf41f4e..fde0481e301 100644
--- a/spec/models/list_user_preference_spec.rb
+++ b/spec/models/list_user_preference_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ListUserPreference do
+RSpec.describe ListUserPreference do
let_it_be(:user) { create(:user) }
let_it_be(:list) { create(:list) }
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 7c40bb24b56..f155c240fb2 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Member do
+RSpec.describe Member do
include ExclusiveLeaseHelpers
using RSpec::Parameterized::TableSyntax
@@ -88,6 +88,28 @@ describe Member do
expect(child_member).to be_valid
end
end
+
+ context 'project bots' do
+ let_it_be(:project_bot) { create(:user, :project_bot) }
+ let(:new_member) { build(:project_member, user_id: project_bot.id) }
+
+ context 'not a member of any group or project' do
+ it 'is valid' do
+ expect(new_member).to be_valid
+ end
+ end
+
+ context 'already member of a project' do
+ before do
+ unrelated_project = create(:project)
+ unrelated_project.add_maintainer(project_bot)
+ end
+
+ it 'is not valid' do
+ expect(new_member).not_to be_valid
+ end
+ end
+ end
end
describe 'Scopes & finders' do
diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb
index fdb71b7ec7d..9af620e70a5 100644
--- a/spec/models/members/group_member_spec.rb
+++ b/spec/models/members/group_member_spec.rb
@@ -2,52 +2,20 @@
require 'spec_helper'
-describe GroupMember do
+RSpec.describe GroupMember do
context 'scopes' do
- shared_examples '.count_users_by_group_id' do
- it 'counts users by group ID' do
- user_1 = create(:user)
- user_2 = create(:user)
- group_1 = create(:group)
- group_2 = create(:group)
-
- group_1.add_owner(user_1)
- group_1.add_owner(user_2)
- group_2.add_owner(user_1)
-
- expect(described_class.count_users_by_group_id).to eq(group_1.id => 2,
- group_2.id => 1)
- end
- end
-
- describe '.count_users_by_group_id with optimized_count_users_by_group_id feature flag on' do
- before do
- stub_feature_flags(optimized_count_users_by_group_id: true)
- end
-
- it_behaves_like '.count_users_by_group_id'
-
- it 'does not JOIN users' do
- scope = described_class.all
- expect(scope).not_to receive(:joins).with(:user)
-
- scope.count_users_by_group_id
- end
- end
-
- describe '.count_users_by_group_id with optimized_count_users_by_group_id feature flag off' do
- before do
- stub_feature_flags(optimized_count_users_by_group_id: false)
- end
-
- it_behaves_like '.count_users_by_group_id'
-
- it 'does JOIN users' do
- scope = described_class.all
- expect(scope).to receive(:joins).with(:user).and_call_original
-
- scope.count_users_by_group_id
- end
+ it 'counts users by group ID' do
+ user_1 = create(:user)
+ user_2 = create(:user)
+ group_1 = create(:group)
+ group_2 = create(:group)
+
+ group_1.add_owner(user_1)
+ group_1.add_owner(user_2)
+ group_2.add_owner(user_1)
+
+ expect(described_class.count_users_by_group_id).to eq(group_1.id => 2,
+ group_2.id => 1)
end
describe '.of_ldap_type' do
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index fdb9457b211..f25f8933184 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectMember do
+RSpec.describe ProjectMember do
describe 'associations' do
it { is_expected.to belong_to(:project).with_foreign_key(:source_id) }
end
diff --git a/spec/models/merge_request/metrics_spec.rb b/spec/models/merge_request/metrics_spec.rb
index bd97cabc11e..4d9e768ecc6 100644
--- a/spec/models/merge_request/metrics_spec.rb
+++ b/spec/models/merge_request/metrics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequest::Metrics do
+RSpec.describe MergeRequest::Metrics do
describe 'associations' do
it { is_expected.to belong_to(:merge_request) }
it { is_expected.to belong_to(:latest_closed_by).class_name('User') }
diff --git a/spec/models/merge_request_assignee_spec.rb b/spec/models/merge_request_assignee_spec.rb
index d6aab15d990..d287392bf7f 100644
--- a/spec/models/merge_request_assignee_spec.rb
+++ b/spec/models/merge_request_assignee_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestAssignee do
+RSpec.describe MergeRequestAssignee do
let(:merge_request) { create(:merge_request) }
subject { merge_request.merge_request_assignees.build(assignee: create(:user)) }
@@ -15,4 +15,26 @@ describe MergeRequestAssignee do
describe 'validations' do
it { is_expected.to validate_uniqueness_of(:assignee).scoped_to(:merge_request_id) }
end
+
+ describe 'scopes' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:project_merge_request) { create(:merge_request, target_project: project, source_project: project, assignee_ids: [user.id]) }
+
+ before do
+ merge_request.update!(assignee_ids: [user.id])
+ end
+
+ context 'in_projects' do
+ it 'returns issue assignees for given project' do
+ expect(MergeRequestAssignee.count).to eq 2
+
+ assignees = MergeRequestAssignee.in_projects([project])
+
+ expect(assignees.count).to eq 1
+ expect(assignees.first.user_id).to eq project_merge_request.merge_request_assignees.first.user_id
+ expect(assignees.first.merge_request_id).to eq project_merge_request.merge_request_assignees.first.merge_request_id
+ end
+ end
+ end
end
diff --git a/spec/models/merge_request_context_commit_diff_file_spec.rb b/spec/models/merge_request_context_commit_diff_file_spec.rb
index 37d44662326..7a098639b57 100644
--- a/spec/models/merge_request_context_commit_diff_file_spec.rb
+++ b/spec/models/merge_request_context_commit_diff_file_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestContextCommitDiffFile do
+RSpec.describe MergeRequestContextCommitDiffFile do
describe 'associations' do
it { is_expected.to belong_to(:merge_request_context_commit) }
end
diff --git a/spec/models/merge_request_context_commit_spec.rb b/spec/models/merge_request_context_commit_spec.rb
index 5a1bf9874ac..29ef2fab9ad 100644
--- a/spec/models/merge_request_context_commit_spec.rb
+++ b/spec/models/merge_request_context_commit_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestContextCommit do
+RSpec.describe MergeRequestContextCommit do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.project }
let(:raw_repository) { project.repository.raw_repository }
diff --git a/spec/models/merge_request_diff_commit_spec.rb b/spec/models/merge_request_diff_commit_spec.rb
index 62430b08c5c..5ea0145e60f 100644
--- a/spec/models/merge_request_diff_commit_spec.rb
+++ b/spec/models/merge_request_diff_commit_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestDiffCommit do
+RSpec.describe MergeRequestDiffCommit do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.project }
diff --git a/spec/models/merge_request_diff_file_spec.rb b/spec/models/merge_request_diff_file_spec.rb
index 40f7be5dc8f..25971f63338 100644
--- a/spec/models/merge_request_diff_file_spec.rb
+++ b/spec/models/merge_request_diff_file_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestDiffFile do
+RSpec.describe MergeRequestDiffFile do
it_behaves_like 'a BulkInsertSafe model', MergeRequestDiffFile do
let(:valid_items_for_bulk_insertion) { build_list(:merge_request_diff_file, 10) }
let(:invalid_items_for_bulk_insertion) { [] } # class does not have any validations defined
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index 0839dde696a..d153ccedf8c 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestDiff do
+RSpec.describe MergeRequestDiff do
using RSpec::Parameterized::TableSyntax
include RepoHelpers
@@ -162,7 +162,8 @@ describe MergeRequestDiff do
let(:uploader) { ExternalDiffUploader }
let(:file_store) { uploader::Store::LOCAL }
let(:remote_store) { uploader::Store::REMOTE }
- let(:diff) { create(:merge_request).merge_request_diff }
+ let(:merge_request) { create(:merge_request) }
+ let(:diff) { merge_request.merge_request_diff }
it 'converts from in-database to external file storage' do
expect(diff).not_to be_stored_externally
@@ -177,6 +178,30 @@ describe MergeRequestDiff do
expect(diff.external_diff_store).to eq(file_store)
end
+ it 'safely handles a transaction error when migrating to external storage' do
+ expect(diff).not_to be_stored_externally
+ expect(diff.external_diff).not_to be_exists
+
+ stub_external_diffs_setting(enabled: true)
+
+ expect(diff).not_to receive(:save!)
+ expect(Gitlab::Database)
+ .to receive(:bulk_insert)
+ .with('merge_request_diff_files', anything)
+ .and_raise(ActiveRecord::Rollback)
+
+ expect { diff.migrate_files_to_external_storage! }.not_to change(diff, :merge_request_diff_files)
+
+ diff.reload
+
+ expect(diff).not_to be_stored_externally
+
+ # The diff is written outside of the transaction, which is desirable to
+ # avoid long transaction times when migrating, but it does mean we can
+ # leave the file dangling on failure
+ expect(diff.external_diff).to be_exists
+ end
+
it 'converts from in-database to external object storage' do
expect(diff).not_to be_stored_externally
@@ -209,6 +234,33 @@ describe MergeRequestDiff do
diff.migrate_files_to_external_storage!
end
+
+ context 'diff adds an empty file' do
+ let(:project) { create(:project, :test_repo) }
+ let(:merge_request) do
+ create(
+ :merge_request,
+ source_project: project,
+ target_project: project,
+ source_branch: 'empty-file',
+ target_branch: 'master'
+ )
+ end
+
+ it 'migrates the diff to object storage' do
+ create_file_in_repo(project, 'master', 'empty-file', 'empty-file', '')
+
+ expect(diff).not_to be_stored_externally
+
+ stub_external_diffs_setting(enabled: true)
+ stub_external_diffs_object_storage(uploader, direct_upload: true)
+
+ diff.migrate_files_to_external_storage!
+
+ expect(diff).to be_stored_externally
+ expect(diff.external_diff_store).to eq(remote_store)
+ end
+ end
end
describe '#migrate_files_to_database!' do
@@ -476,7 +528,7 @@ describe MergeRequestDiff do
include_examples 'merge request diffs'
end
- describe 'external diffs always enabled' do
+ describe 'external diffs on disk always enabled' do
before do
stub_external_diffs_setting(enabled: true, when: 'always')
end
@@ -484,6 +536,63 @@ describe MergeRequestDiff do
include_examples 'merge request diffs'
end
+ describe 'external diffs in object storage always enabled' do
+ let(:uploader) { ExternalDiffUploader }
+ let(:remote_store) { uploader::Store::REMOTE }
+
+ subject(:diff) { merge_request.merge_request_diff }
+
+ before do
+ stub_external_diffs_setting(enabled: true, when: 'always')
+ stub_external_diffs_object_storage(uploader, direct_upload: true)
+ end
+
+ # We can't use the full merge request diffs shared examples here because
+ # reading from the fake object store isn't implemented yet
+
+ context 'empty diff' do
+ let(:merge_request) { create(:merge_request, :without_diffs) }
+
+ it 'creates an empty diff' do
+ expect(diff.state).to eq('empty')
+ expect(diff).not_to be_stored_externally
+ end
+ end
+
+ context 'normal diff' do
+ let(:merge_request) { create(:merge_request) }
+
+ it 'creates a diff in object storage' do
+ expect(diff).to be_stored_externally
+ expect(diff.state).to eq('collected')
+ expect(diff.external_diff_store).to eq(remote_store)
+ end
+ end
+
+ context 'diff adding an empty file' do
+ let(:project) { create(:project, :test_repo) }
+ let(:merge_request) do
+ create(
+ :merge_request,
+ source_project: project,
+ target_project: project,
+ source_branch: 'empty-file',
+ target_branch: 'master'
+ )
+ end
+
+ it 'creates a diff in object storage' do
+ create_file_in_repo(project, 'master', 'empty-file', 'empty-file', '')
+
+ diff.reload
+
+ expect(diff).to be_stored_externally
+ expect(diff.state).to eq('collected')
+ expect(diff.external_diff_store).to eq(remote_store)
+ end
+ end
+ end
+
describe 'exernal diffs enabled for outdated diffs' do
before do
stub_external_diffs_setting(enabled: true, when: 'outdated')
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 582cdc7b419..06febddef0c 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequest do
+RSpec.describe MergeRequest do
include RepoHelpers
include ProjectForksHelper
include ReactiveCachingHelpers
@@ -12,6 +12,8 @@ describe MergeRequest do
subject { create(:merge_request) }
describe 'associations' do
+ subject { build_stubbed(:merge_request) }
+
it { is_expected.to belong_to(:target_project).class_name('Project') }
it { is_expected.to belong_to(:source_project).class_name('Project') }
it { is_expected.to belong_to(:merge_user).class_name("User") }
@@ -55,29 +57,6 @@ describe MergeRequest do
end
end
- describe 'locking' do
- using RSpec::Parameterized::TableSyntax
-
- where(:lock_version) do
- [
- [0],
- ["0"]
- ]
- end
-
- with_them do
- it 'works when a merge request has a NULL lock_version' do
- merge_request = create(:merge_request)
-
- described_class.where(id: merge_request.id).update_all('lock_version = NULL')
-
- merge_request.update!(lock_version: lock_version, title: 'locking test')
-
- expect(merge_request.reload.title).to eq('locking test')
- end
- end
- end
-
describe '#squash_in_progress?' do
let(:repo_path) do
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
@@ -195,6 +174,8 @@ describe MergeRequest do
end
describe 'validation' do
+ subject { build_stubbed(:merge_request) }
+
it { is_expected.to validate_presence_of(:target_branch) }
it { is_expected.to validate_presence_of(:source_branch) }
@@ -280,6 +261,21 @@ describe MergeRequest do
expect(MergeRequest::Metrics.count).to eq(1)
end
+
+ it 'does not create duplicated metrics records when MR is concurrently updated' do
+ merge_request = create(:merge_request)
+
+ merge_request.metrics.destroy
+
+ instance1 = MergeRequest.find(merge_request.id)
+ instance2 = MergeRequest.find(merge_request.id)
+
+ instance1.ensure_metrics
+ instance2.ensure_metrics
+
+ metrics_records = MergeRequest::Metrics.where(merge_request_id: merge_request.id)
+ expect(metrics_records.size).to eq(1)
+ end
end
end
@@ -1092,13 +1088,43 @@ describe MergeRequest do
end
describe "#work_in_progress?" do
- ['WIP ', 'WIP:', 'WIP: ', '[WIP]', '[WIP] ', ' [WIP] WIP [WIP] WIP: WIP '].each do |wip_prefix|
+ subject { build_stubbed(:merge_request) }
+
+ [
+ 'WIP:', 'WIP: ', '[WIP]', '[WIP] ', ' [WIP] WIP: [WIP] WIP:',
+ 'draft:', 'Draft: ', '[Draft]', '[DRAFT] ', 'Draft - '
+ ].each do |wip_prefix|
it "detects the '#{wip_prefix}' prefix" do
subject.title = "#{wip_prefix}#{subject.title}"
+
expect(subject.work_in_progress?).to eq true
end
end
+ it "detects merge request title just saying 'wip'" do
+ subject.title = "wip"
+
+ expect(subject.work_in_progress?).to eq true
+ end
+
+ it "detects merge request title just saying 'draft'" do
+ subject.title = "draft"
+
+ expect(subject.work_in_progress?).to eq true
+ end
+
+ it 'does not detect WIP in the middle of the title' do
+ subject.title = 'Something with WIP in the middle'
+
+ expect(subject.work_in_progress?).to eq false
+ end
+
+ it 'does not detect Draft in the middle of the title' do
+ subject.title = 'Something with Draft in the middle'
+
+ expect(subject.work_in_progress?).to eq false
+ end
+
it "doesn't detect WIP for words starting with WIP" do
subject.title = "Wipwap #{subject.title}"
expect(subject.work_in_progress?).to eq false
@@ -1115,7 +1141,12 @@ describe MergeRequest do
end
describe "#wipless_title" do
- ['WIP ', 'WIP:', 'WIP: ', '[WIP]', '[WIP] ', '[WIP] WIP [WIP] WIP: WIP '].each do |wip_prefix|
+ subject { build_stubbed(:merge_request) }
+
+ [
+ 'WIP:', 'WIP: ', '[WIP]', '[WIP] ', '[WIP] WIP: [WIP] WIP:',
+ 'draft:', 'Draft: ', '[Draft]', '[DRAFT] ', 'Draft - '
+ ].each do |wip_prefix|
it "removes the '#{wip_prefix}' prefix" do
wipless_title = subject.title
subject.title = "#{wip_prefix}#{subject.title}"
@@ -1133,14 +1164,14 @@ describe MergeRequest do
end
describe "#wip_title" do
- it "adds the WIP: prefix to the title" do
- wip_title = "WIP: #{subject.title}"
+ it "adds the Draft: prefix to the title" do
+ wip_title = "Draft: #{subject.title}"
expect(subject.wip_title).to eq wip_title
end
- it "does not add the WIP: prefix multiple times" do
- wip_title = "WIP: #{subject.title}"
+ it "does not add the Draft: prefix multiple times" do
+ wip_title = "Draft: #{subject.title}"
subject.title = subject.wip_title
subject.title = subject.wip_title
@@ -1170,6 +1201,12 @@ describe MergeRequest do
expect(subject.can_remove_source_branch?(user)).to be_falsey
end
+ it "can't be removed because source project has been deleted" do
+ subject.source_project = nil
+
+ expect(subject.can_remove_source_branch?(user)).to be_falsey
+ end
+
it "can't remove a root ref" do
subject.update(source_branch: 'master', target_branch: 'feature')
@@ -1196,6 +1233,29 @@ describe MergeRequest do
end
end
+ describe "#source_branch_exists?" do
+ let(:merge_request) { subject }
+ let(:repository) { merge_request.source_project.repository }
+
+ context 'when the source project is set' do
+ it 'memoizes the value and returns the result' do
+ expect(repository).to receive(:branch_exists?).once.with(merge_request.source_branch).and_return(true)
+
+ 2.times { expect(merge_request.source_branch_exists?).to eq(true) }
+ end
+ end
+
+ context 'when the source project is not set' do
+ before do
+ merge_request.source_project = nil
+ end
+
+ it 'returns false' do
+ expect(merge_request.source_branch_exists?).to eq(false)
+ end
+ end
+ end
+
describe '#default_merge_commit_message' do
it 'includes merge information as the title' do
request = build(:merge_request, source_branch: 'source', target_branch: 'target')
@@ -2426,7 +2486,7 @@ describe MergeRequest do
context 'when working in progress' do
before do
- subject.title = 'WIP MR'
+ subject.title = '[Draft] MR'
end
it 'returns false' do
diff --git a/spec/models/metrics/dashboard/annotation_spec.rb b/spec/models/metrics/dashboard/annotation_spec.rb
index 3cba31ffdfe..bd4baeb8851 100644
--- a/spec/models/metrics/dashboard/annotation_spec.rb
+++ b/spec/models/metrics/dashboard/annotation_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::Annotation do
+RSpec.describe Metrics::Dashboard::Annotation do
using RSpec::Parameterized::TableSyntax
describe 'associations' do
diff --git a/spec/models/metrics/users_starred_dashboard_spec.rb b/spec/models/metrics/users_starred_dashboard_spec.rb
index 6cb14ae569e..c89344c0a1c 100644
--- a/spec/models/metrics/users_starred_dashboard_spec.rb
+++ b/spec/models/metrics/users_starred_dashboard_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::UsersStarredDashboard do
+RSpec.describe Metrics::UsersStarredDashboard do
describe 'associations' do
it { is_expected.to belong_to(:project).inverse_of(:metrics_users_starred_dashboards) }
it { is_expected.to belong_to(:user).inverse_of(:metrics_users_starred_dashboards) }
diff --git a/spec/models/milestone_note_spec.rb b/spec/models/milestone_note_spec.rb
index aad65cf0346..db1a7ca05f8 100644
--- a/spec/models/milestone_note_spec.rb
+++ b/spec/models/milestone_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MilestoneNote do
+RSpec.describe MilestoneNote do
describe '.from_event' do
let(:author) { create(:user) }
let(:project) { create(:project, :repository) }
@@ -11,9 +11,7 @@ describe MilestoneNote do
subject { described_class.from_event(event, resource: noteable, resource_parent: project) }
- it_behaves_like 'a system note', exclude_project: true do
- let(:action) { 'milestone' }
- end
+ it_behaves_like 'a synthetic note', 'milestone'
context 'with a remove milestone event' do
let(:milestone) { create(:milestone) }
diff --git a/spec/models/milestone_release_spec.rb b/spec/models/milestone_release_spec.rb
index 28cec7bbc17..3c781545d8a 100644
--- a/spec/models/milestone_release_spec.rb
+++ b/spec/models/milestone_release_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MilestoneRelease do
+RSpec.describe MilestoneRelease do
let(:project) { create(:project) }
let(:release) { create(:release, project: project) }
let(:milestone) { create(:milestone, project: project) }
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 33f84da27f6..b52b035e130 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Milestone do
+RSpec.describe Milestone do
it_behaves_like 'a timebox', :milestone
describe 'MilestoneStruct#serializable_hash' do
diff --git a/spec/models/namespace/root_storage_size_spec.rb b/spec/models/namespace/root_storage_size_spec.rb
deleted file mode 100644
index a8048b7f637..00000000000
--- a/spec/models/namespace/root_storage_size_spec.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Namespace::RootStorageSize, type: :model do
- let(:namespace) { create(:namespace) }
- let(:current_size) { 50.megabytes }
- let(:limit) { 100 }
- let(:model) { described_class.new(namespace) }
- let(:create_statistics) { create(:namespace_root_storage_statistics, namespace: namespace, storage_size: current_size)}
-
- before do
- create_statistics
-
- stub_application_setting(namespace_storage_size_limit: limit)
- end
-
- describe '#above_size_limit?' do
- subject { model.above_size_limit? }
-
- context 'when limit is 0' do
- let(:limit) { 0 }
-
- it { is_expected.to eq(false) }
- end
-
- context 'when below limit' do
- it { is_expected.to eq(false) }
- end
-
- context 'when above limit' do
- let(:current_size) { 101.megabytes }
-
- it { is_expected.to eq(true) }
- end
- end
-
- describe '#usage_ratio' do
- subject { model.usage_ratio }
-
- it { is_expected.to eq(0.5) }
-
- context 'when limit is 0' do
- let(:limit) { 0 }
-
- it { is_expected.to eq(0) }
- end
-
- context 'when there are no root_storage_statistics' do
- let(:create_statistics) { nil }
-
- it { is_expected.to eq(0) }
- end
- end
-
- describe '#current_size' do
- subject { model.current_size }
-
- it { is_expected.to eq(current_size) }
- end
-
- describe '#limit' do
- subject { model.limit }
-
- it { is_expected.to eq(limit.megabytes) }
- end
-end
diff --git a/spec/models/namespace/root_storage_statistics_spec.rb b/spec/models/namespace/root_storage_statistics_spec.rb
index 9e12831a704..ce6f875ee09 100644
--- a/spec/models/namespace/root_storage_statistics_spec.rb
+++ b/spec/models/namespace/root_storage_statistics_spec.rb
@@ -43,6 +43,7 @@ RSpec.describe Namespace::RootStorageStatistics, type: :model do
total_build_artifacts_size = stat1.build_artifacts_size + stat2.build_artifacts_size
total_packages_size = stat1.packages_size + stat2.packages_size
total_storage_size = stat1.storage_size + stat2.storage_size
+ total_snippets_size = stat1.snippets_size + stat2.snippets_size
expect(root_storage_statistics.repository_size).to eq(total_repository_size)
expect(root_storage_statistics.wiki_size).to eq(total_wiki_size)
@@ -50,6 +51,7 @@ RSpec.describe Namespace::RootStorageStatistics, type: :model do
expect(root_storage_statistics.build_artifacts_size).to eq(total_build_artifacts_size)
expect(root_storage_statistics.packages_size).to eq(total_packages_size)
expect(root_storage_statistics.storage_size).to eq(total_storage_size)
+ expect(root_storage_statistics.snippets_size).to eq(total_snippets_size)
end
it 'works when there are no projects' do
@@ -64,10 +66,20 @@ RSpec.describe Namespace::RootStorageStatistics, type: :model do
expect(root_storage_statistics.build_artifacts_size).to eq(0)
expect(root_storage_statistics.packages_size).to eq(0)
expect(root_storage_statistics.storage_size).to eq(0)
+ expect(root_storage_statistics.snippets_size).to eq(0)
+ end
+ end
+
+ shared_examples 'does not include personal snippets' do
+ specify do
+ expect(root_storage_statistics).not_to receive(:from_personal_snippets)
+
+ root_storage_statistics.recalculate!
end
end
it_behaves_like 'data refresh'
+ it_behaves_like 'does not include personal snippets'
context 'with subgroups' do
let(:subgroup1) { create(:group, parent: namespace)}
@@ -77,12 +89,45 @@ RSpec.describe Namespace::RootStorageStatistics, type: :model do
let(:project2) { create(:project, namespace: subgroup2) }
it_behaves_like 'data refresh'
+ it_behaves_like 'does not include personal snippets'
end
context 'with a personal namespace' do
- let(:namespace) { create(:user).namespace }
+ let_it_be(:user) { create(:user) }
+ let(:namespace) { user.namespace }
it_behaves_like 'data refresh'
+
+ context 'when user has personal snippets' do
+ let(:total_project_snippets_size) { stat1.snippets_size + stat2.snippets_size }
+
+ it 'aggregates personal and project snippets size' do
+ # This is just a a snippet authored by other user
+ # to ensure we only pick snippets from the namespace
+ # user
+ create(:personal_snippet, :repository).statistics.refresh!
+
+ snippets = create_list(:personal_snippet, 3, :repository, author: user)
+ snippets.each { |s| s.statistics.refresh! }
+
+ total_personal_snippets_size = snippets.map { |s| s.statistics.repository_size }.sum
+
+ root_storage_statistics.recalculate!
+
+ expect(root_storage_statistics.snippets_size).to eq(total_personal_snippets_size + total_project_snippets_size)
+ end
+
+ context 'when personal snippets do not have statistics' do
+ it 'does not raise any error' do
+ snippets = create_list(:personal_snippet, 2, :repository, author: user)
+ snippets.last.statistics.refresh!
+
+ root_storage_statistics.recalculate!
+
+ expect(root_storage_statistics.snippets_size).to eq(total_project_snippets_size + snippets.last.statistics.repository_size)
+ end
+ end
+ end
end
end
end
diff --git a/spec/models/namespace/traversal_hierarchy_spec.rb b/spec/models/namespace/traversal_hierarchy_spec.rb
new file mode 100644
index 00000000000..71b0e974106
--- /dev/null
+++ b/spec/models/namespace/traversal_hierarchy_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Namespace::TraversalHierarchy, type: :model do
+ let_it_be(:root, reload: true) { create(:namespace, :with_hierarchy) }
+
+ describe '.for_namespace' do
+ let(:hierarchy) { described_class.for_namespace(namespace) }
+
+ context 'with root group' do
+ let(:namespace) { root }
+
+ it { expect(hierarchy.root).to eq root }
+ end
+
+ context 'with child group' do
+ let(:namespace) { root.children.first.children.first }
+
+ it { expect(hierarchy.root).to eq root }
+ end
+
+ context 'with group outside of hierarchy' do
+ let(:namespace) { create(:namespace) }
+
+ it { expect(hierarchy.root).not_to eq root }
+ end
+ end
+
+ describe '.new' do
+ let(:hierarchy) { described_class.new(namespace) }
+
+ context 'with root group' do
+ let(:namespace) { root }
+
+ it { expect(hierarchy.root).to eq root }
+ end
+
+ context 'with child group' do
+ let(:namespace) { root.children.first }
+
+ it { expect { hierarchy }.to raise_error(StandardError, 'Must specify a root node') }
+ end
+ end
+
+ describe '#incorrect_traversal_ids' do
+ subject { described_class.new(root).incorrect_traversal_ids }
+
+ it { is_expected.to match_array Namespace.all }
+ end
+
+ describe '#sync_traversal_ids!' do
+ let(:hierarchy) { described_class.new(root) }
+
+ before do
+ hierarchy.sync_traversal_ids!
+ root.reload
+ end
+
+ it_behaves_like 'hierarchy with traversal_ids'
+ it { expect(hierarchy.incorrect_traversal_ids).to be_empty }
+ end
+end
diff --git a/spec/models/namespace_setting_spec.rb b/spec/models/namespace_setting_spec.rb
new file mode 100644
index 00000000000..257d78dfa2c
--- /dev/null
+++ b/spec/models/namespace_setting_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe NamespaceSetting, type: :model do
+ it { is_expected.to belong_to(:namespace) }
+end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index ed7ef8b2b8e..ad4c8448745 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Namespace do
+RSpec.describe Namespace do
include ProjectForksHelper
include GitHelpers
@@ -17,6 +17,8 @@ describe Namespace do
it { is_expected.to have_many :children }
it { is_expected.to have_one :root_storage_statistics }
it { is_expected.to have_one :aggregation_schedule }
+ it { is_expected.to have_one :namespace_settings }
+ it { is_expected.to have_many :custom_emoji }
end
describe 'validations' do
@@ -64,6 +66,36 @@ describe Namespace do
it { expect(group).to be_valid }
end
end
+
+ describe '1 char path length' do
+ it 'does not allow to create one' do
+ namespace = build(:namespace, path: 'j')
+
+ expect(namespace).not_to be_valid
+ expect(namespace.errors[:path].first).to eq('is too short (minimum is 2 characters)')
+ end
+
+ it 'does not allow to update one' do
+ namespace = create(:namespace)
+ namespace.update(path: 'j')
+
+ expect(namespace).not_to be_valid
+ expect(namespace.errors[:path].first).to eq('is too short (minimum is 2 characters)')
+ end
+
+ it 'allows updating other attributes for existing record' do
+ namespace = build(:namespace, path: 'j')
+ namespace.save(validate: false)
+ namespace.reload
+
+ expect(namespace.path).to eq('j')
+
+ namespace.update(name: 'something new')
+
+ expect(namespace).to be_valid
+ expect(namespace.name).to eq('something new')
+ end
+ end
end
describe 'delegate' do
@@ -153,7 +185,8 @@ describe Namespace do
wiki_size: 505,
lfs_objects_size: 202,
build_artifacts_size: 303,
- packages_size: 404))
+ packages_size: 404,
+ snippets_size: 605))
end
let(:project2) do
@@ -164,7 +197,8 @@ describe Namespace do
wiki_size: 50,
lfs_objects_size: 20,
build_artifacts_size: 30,
- packages_size: 40))
+ packages_size: 40,
+ snippets_size: 60))
end
it "sums all project storage counters in the namespace" do
@@ -172,12 +206,13 @@ describe Namespace do
project2
statistics = described_class.with_statistics.find(namespace.id)
- expect(statistics.storage_size).to eq 1665
+ expect(statistics.storage_size).to eq 2330
expect(statistics.repository_size).to eq 111
expect(statistics.wiki_size).to eq 555
expect(statistics.lfs_objects_size).to eq 222
expect(statistics.build_artifacts_size).to eq 333
expect(statistics.packages_size).to eq 444
+ expect(statistics.snippets_size).to eq 665
end
it "correctly handles namespaces without projects" do
@@ -189,6 +224,7 @@ describe Namespace do
expect(statistics.lfs_objects_size).to eq 0
expect(statistics.build_artifacts_size).to eq 0
expect(statistics.packages_size).to eq 0
+ expect(statistics.snippets_size).to eq 0
end
end
@@ -849,8 +885,13 @@ describe Namespace do
end
describe '#root_ancestor' do
+ let!(:root_group) { create(:group) }
+
+ it 'returns root_ancestor for root group without a query' do
+ expect { root_group.root_ancestor }.not_to exceed_query_limit(0)
+ end
+
it 'returns the top most ancestor' do
- root_group = create(:group)
nested_group = create(:group, parent: root_group)
deep_nested_group = create(:group, parent: nested_group)
very_deep_nested_group = create(:group, parent: deep_nested_group)
diff --git a/spec/models/network/graph_spec.rb b/spec/models/network/graph_spec.rb
index 232172fde76..a393aace39c 100644
--- a/spec/models/network/graph_spec.rb
+++ b/spec/models/network/graph_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Network::Graph do
+RSpec.describe Network::Graph do
let(:project) { create(:project, :repository) }
let!(:note_on_commit) { create(:note_on_commit, project: project) }
diff --git a/spec/models/note_diff_file_spec.rb b/spec/models/note_diff_file_spec.rb
index 11108016b8e..1ece1dfea59 100644
--- a/spec/models/note_diff_file_spec.rb
+++ b/spec/models/note_diff_file_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NoteDiffFile do
+RSpec.describe NoteDiffFile do
describe 'associations' do
it { is_expected.to belong_to(:diff_note) }
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index af3fdcfaa2e..e6e6a8c35cf 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Note do
+RSpec.describe Note do
include RepoHelpers
describe 'associations' do
diff --git a/spec/models/notification_recipient_spec.rb b/spec/models/notification_recipient_spec.rb
index 05aeafaa4d4..8429f577dc6 100644
--- a/spec/models/notification_recipient_spec.rb
+++ b/spec/models/notification_recipient_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NotificationRecipient do
+RSpec.describe NotificationRecipient do
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
let(:target) { create(:issue, project: project) }
diff --git a/spec/models/oauth_access_grant_spec.rb b/spec/models/oauth_access_grant_spec.rb
index 955dae906f3..ca67944752d 100644
--- a/spec/models/oauth_access_grant_spec.rb
+++ b/spec/models/oauth_access_grant_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe OauthAccessGrant do
+RSpec.describe OauthAccessGrant do
let(:user) { create(:user) }
let(:application) { create(:oauth_application, owner: user) }
diff --git a/spec/models/oauth_access_token_spec.rb b/spec/models/oauth_access_token_spec.rb
index 0a1c576a5e7..65a7f6410cf 100644
--- a/spec/models/oauth_access_token_spec.rb
+++ b/spec/models/oauth_access_token_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe OauthAccessToken do
+RSpec.describe OauthAccessToken do
let(:user) { create(:user) }
let(:app_one) { create(:oauth_application) }
let(:app_two) { create(:oauth_application) }
diff --git a/spec/models/packages/composer/metadatum_spec.rb b/spec/models/packages/composer/metadatum_spec.rb
new file mode 100644
index 00000000000..ae53532696b
--- /dev/null
+++ b/spec/models/packages/composer/metadatum_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Composer::Metadatum, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:package) }
+ it { is_expected.to validate_presence_of(:target_sha) }
+ it { is_expected.to validate_presence_of(:composer_json) }
+ end
+end
diff --git a/spec/models/packages/conan/file_metadatum_spec.rb b/spec/models/packages/conan/file_metadatum_spec.rb
new file mode 100644
index 00000000000..a66a2813196
--- /dev/null
+++ b/spec/models/packages/conan/file_metadatum_spec.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Conan::FileMetadatum, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package_file) }
+ end
+
+ describe 'validations' do
+ let(:package_file) { create(:conan_package_file, :conan_recipe_file) }
+
+ it { is_expected.to validate_presence_of(:package_file) }
+ it { is_expected.to validate_presence_of(:recipe_revision) }
+
+ describe '#recipe_revision' do
+ it { is_expected.to allow_value("0").for(:recipe_revision) }
+ it { is_expected.not_to allow_value(nil).for(:recipe_revision) }
+ end
+
+ describe '#package_revision_for_package_file' do
+ context 'recipe file' do
+ let(:conan_file_metadatum) { build(:conan_file_metadatum, :recipe_file, package_file: package_file) }
+
+ it 'is valid with empty value' do
+ conan_file_metadatum.package_revision = nil
+
+ expect(conan_file_metadatum).to be_valid
+ end
+
+ it 'is invalid with value' do
+ conan_file_metadatum.package_revision = '0'
+
+ expect(conan_file_metadatum).to be_invalid
+ end
+ end
+
+ context 'package file' do
+ let(:conan_file_metadatum) { build(:conan_file_metadatum, :package_file, package_file: package_file) }
+
+ it 'is valid with default value' do
+ conan_file_metadatum.package_revision = '0'
+
+ expect(conan_file_metadatum).to be_valid
+ end
+
+ it 'is invalid with non-default value' do
+ conan_file_metadatum.package_revision = 'foo'
+
+ expect(conan_file_metadatum).to be_invalid
+ end
+ end
+ end
+
+ describe '#conan_package_reference_for_package_file' do
+ context 'recipe file' do
+ let(:conan_file_metadatum) { build(:conan_file_metadatum, :recipe_file, package_file: package_file) }
+
+ it 'is valid with empty value' do
+ conan_file_metadatum.conan_package_reference = nil
+
+ expect(conan_file_metadatum).to be_valid
+ end
+
+ it 'is invalid with value' do
+ conan_file_metadatum.conan_package_reference = '123456789'
+
+ expect(conan_file_metadatum).to be_invalid
+ end
+ end
+
+ context 'package file' do
+ let(:conan_file_metadatum) { build(:conan_file_metadatum, :package_file, package_file: package_file) }
+
+ it 'is valid with acceptable value' do
+ conan_file_metadatum.conan_package_reference = '123456asdf'
+
+ expect(conan_file_metadatum).to be_valid
+ end
+
+ it 'is invalid with invalid value' do
+ conan_file_metadatum.conan_package_reference = 'foo@bar'
+
+ expect(conan_file_metadatum).to be_invalid
+ end
+
+ it 'is invalid when nil' do
+ conan_file_metadatum.conan_package_reference = nil
+
+ expect(conan_file_metadatum).to be_invalid
+ end
+ end
+ end
+
+ describe '#conan_package_type' do
+ it 'validates package of type conan' do
+ package = build('package')
+ package_file = build('package_file', package: package)
+ conan_file_metadatum = build('conan_file_metadatum', package_file: package_file)
+
+ expect(conan_file_metadatum).not_to be_valid
+ expect(conan_file_metadatum.errors.to_a).to contain_exactly('Package type must be Conan')
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/conan/metadatum_spec.rb b/spec/models/packages/conan/metadatum_spec.rb
new file mode 100644
index 00000000000..112f395818b
--- /dev/null
+++ b/spec/models/packages/conan/metadatum_spec.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Conan::Metadatum, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package) }
+ end
+
+ describe 'validations' do
+ let(:fifty_one_characters) { 'f_a' * 17}
+
+ it { is_expected.to validate_presence_of(:package) }
+ it { is_expected.to validate_presence_of(:package_username) }
+ it { is_expected.to validate_presence_of(:package_channel) }
+
+ describe '#package_username' do
+ it { is_expected.to allow_value("my-package+username").for(:package_username) }
+ it { is_expected.to allow_value("my_package.username").for(:package_username) }
+ it { is_expected.to allow_value("_my-package.username123").for(:package_username) }
+ it { is_expected.to allow_value("my").for(:package_username) }
+ it { is_expected.not_to allow_value('+my_package').for(:package_username) }
+ it { is_expected.not_to allow_value('.my_package').for(:package_username) }
+ it { is_expected.not_to allow_value('-my_package').for(:package_username) }
+ it { is_expected.not_to allow_value('m').for(:package_username) }
+ it { is_expected.not_to allow_value(fifty_one_characters).for(:package_username) }
+ it { is_expected.not_to allow_value("my/package").for(:package_username) }
+ it { is_expected.not_to allow_value("my(package)").for(:package_username) }
+ it { is_expected.not_to allow_value("my@package").for(:package_username) }
+ end
+
+ describe '#package_channel' do
+ it { is_expected.to allow_value("beta").for(:package_channel) }
+ it { is_expected.to allow_value("stable+1.0").for(:package_channel) }
+ it { is_expected.to allow_value("my").for(:package_channel) }
+ it { is_expected.to allow_value("my_channel.beta").for(:package_channel) }
+ it { is_expected.to allow_value("_my-channel.beta123").for(:package_channel) }
+ it { is_expected.not_to allow_value('+my_channel').for(:package_channel) }
+ it { is_expected.not_to allow_value('.my_channel').for(:package_channel) }
+ it { is_expected.not_to allow_value('-my_channel').for(:package_channel) }
+ it { is_expected.not_to allow_value('m').for(:package_channel) }
+ it { is_expected.not_to allow_value(fifty_one_characters).for(:package_channel) }
+ it { is_expected.not_to allow_value("my/channel").for(:package_channel) }
+ it { is_expected.not_to allow_value("my(channel)").for(:package_channel) }
+ it { is_expected.not_to allow_value("my@channel").for(:package_channel) }
+ end
+
+ describe '#conan_package_type' do
+ it 'will not allow a package with a different package_type' do
+ package = build('package')
+ conan_metadatum = build('conan_metadatum', package: package)
+
+ expect(conan_metadatum).not_to be_valid
+ expect(conan_metadatum.errors.to_a).to include('Package type must be Conan')
+ end
+ end
+ end
+
+ describe '#recipe' do
+ let(:package) { create(:conan_package) }
+
+ it 'returns the recipe' do
+ expect(package.conan_recipe).to eq("#{package.name}/#{package.version}@#{package.conan_metadatum.package_username}/#{package.conan_metadatum.package_channel}")
+ end
+ end
+
+ describe '#recipe_url' do
+ let(:package) { create(:conan_package) }
+
+ it 'returns the recipe url' do
+ expect(package.conan_recipe_path).to eq("#{package.name}/#{package.version}/#{package.conan_metadatum.package_username}/#{package.conan_metadatum.package_channel}")
+ end
+ end
+
+ describe '.package_username_from' do
+ let(:full_path) { 'foo/bar/baz-buz' }
+
+ it 'returns the username formatted package path' do
+ expect(described_class.package_username_from(full_path: full_path)).to eq('foo+bar+baz-buz')
+ end
+ end
+
+ describe '.full_path_from' do
+ let(:username) { 'foo+bar+baz-buz' }
+
+ it 'returns the username formatted package path' do
+ expect(described_class.full_path_from(package_username: username)).to eq('foo/bar/baz-buz')
+ end
+ end
+end
diff --git a/spec/models/packages/dependency_link_spec.rb b/spec/models/packages/dependency_link_spec.rb
new file mode 100644
index 00000000000..d8fde8f5eb3
--- /dev/null
+++ b/spec/models/packages/dependency_link_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::DependencyLink, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package).inverse_of(:dependency_links) }
+ it { is_expected.to belong_to(:dependency).inverse_of(:dependency_links) }
+ it { is_expected.to have_one(:nuget_metadatum).inverse_of(:dependency_link) }
+ end
+
+ describe 'validations' do
+ subject { create(:packages_dependency_link) }
+
+ it { is_expected.to validate_presence_of(:package) }
+ it { is_expected.to validate_presence_of(:dependency) }
+
+ context 'package_id and package_dependency_id uniqueness for dependency_type' do
+ it 'is not valid' do
+ exisiting_link = subject
+ link = build(
+ :packages_dependency_link,
+ package: exisiting_link.package,
+ dependency: exisiting_link.dependency,
+ dependency_type: exisiting_link.dependency_type
+ )
+
+ expect(link).not_to be_valid
+ expect(link.errors.to_a).to include("Dependency type has already been taken")
+ end
+ end
+ end
+
+ context 'with multiple links' do
+ let_it_be(:link1) { create(:packages_dependency_link) }
+ let_it_be(:link2) { create(:packages_dependency_link, dependency: link1.dependency, dependency_type: :devDependencies) }
+ let_it_be(:link3) { create(:packages_dependency_link, dependency: link1.dependency, dependency_type: :bundleDependencies) }
+
+ subject { described_class }
+
+ describe '.with_dependency_type' do
+ it 'returns links of the given type' do
+ expect(subject.with_dependency_type(:bundleDependencies)).to eq([link3])
+ end
+ end
+
+ describe '.for_package' do
+ let_it_be(:link1) { create(:packages_dependency_link) }
+ let_it_be(:link2) { create(:packages_dependency_link, dependency: link1.dependency, dependency_type: :devDependencies) }
+ let_it_be(:link3) { create(:packages_dependency_link, dependency: link1.dependency, dependency_type: :bundleDependencies) }
+
+ it 'returns the link for the given package' do
+ expect(subject.for_package(link1.package)).to eq([link1])
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/dependency_spec.rb b/spec/models/packages/dependency_spec.rb
new file mode 100644
index 00000000000..fa6b0fd1848
--- /dev/null
+++ b/spec/models/packages/dependency_spec.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Dependency, type: :model do
+ describe 'relationships' do
+ it { is_expected.to have_many(:dependency_links) }
+ end
+
+ describe 'validations' do
+ subject { create(:packages_dependency) }
+
+ it { is_expected.to validate_presence_of(:name) }
+ it { is_expected.to validate_presence_of(:version_pattern) }
+ it { is_expected.to validate_uniqueness_of(:name).scoped_to(:version_pattern) }
+ end
+
+ describe '.ids_for_package_names_and_version_patterns' do
+ let_it_be(:package_dependency1) { create(:packages_dependency, name: 'foo', version_pattern: '~1.0.0') }
+ let_it_be(:package_dependency2) { create(:packages_dependency, name: 'bar', version_pattern: '~2.5.0') }
+ let_it_be(:expected_ids) { [package_dependency1.id, package_dependency2.id] }
+ let(:names_and_version_patterns) { build_names_and_version_patterns(package_dependency1, package_dependency2) }
+ let(:chunk_size) { 50 }
+ let(:rows_limit) { 50 }
+
+ subject { Packages::Dependency.ids_for_package_names_and_version_patterns(names_and_version_patterns, chunk_size, rows_limit) }
+
+ it { is_expected.to match_array(expected_ids) }
+
+ context 'with unknown names' do
+ let(:names_and_version_patterns) { { unknown: '~1.0.0' } }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'with unknown version patterns' do
+ let(:names_and_version_patterns) { { 'foo' => '~1.0.0beta' } }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'with a name bigger than column size' do
+ let_it_be(:big_name) { 'a' * (Packages::Dependency::MAX_STRING_LENGTH + 1) }
+ let(:names_and_version_patterns) { build_names_and_version_patterns(package_dependency1, package_dependency2).merge(big_name => '~1.0.0') }
+
+ it { is_expected.to match_array(expected_ids) }
+ end
+
+ context 'with a version pattern bigger than column size' do
+ let_it_be(:big_version_pattern) { 'a' * (Packages::Dependency::MAX_STRING_LENGTH + 1) }
+ let(:names_and_version_patterns) { build_names_and_version_patterns(package_dependency1, package_dependency2).merge('test' => big_version_pattern) }
+
+ it { is_expected.to match_array(expected_ids) }
+ end
+
+ context 'with too big parameter' do
+ let(:size) { (Packages::Dependency::MAX_CHUNKED_QUERIES_COUNT * chunk_size) + 1 }
+ let(:names_and_version_patterns) { Hash[(1..size).map { |v| [v, v] }] }
+
+ it { expect { subject }.to raise_error(ArgumentError, 'Too many names_and_version_patterns') }
+ end
+
+ context 'with parameters size' do
+ let_it_be(:package_dependency3) { create(:packages_dependency, name: 'foo3', version_pattern: '~1.5.3') }
+ let_it_be(:package_dependency4) { create(:packages_dependency, name: 'foo4', version_pattern: '~1.5.4') }
+ let_it_be(:package_dependency5) { create(:packages_dependency, name: 'foo5', version_pattern: '~1.5.5') }
+ let_it_be(:package_dependency6) { create(:packages_dependency, name: 'foo6', version_pattern: '~1.5.6') }
+ let_it_be(:package_dependency7) { create(:packages_dependency, name: 'foo7', version_pattern: '~1.5.7') }
+ let(:expected_ids) { [package_dependency1.id, package_dependency2.id, package_dependency3.id, package_dependency4.id, package_dependency5.id, package_dependency6.id, package_dependency7.id] }
+ let(:names_and_version_patterns) { build_names_and_version_patterns(package_dependency1, package_dependency2, package_dependency3, package_dependency4, package_dependency5, package_dependency6, package_dependency7) }
+
+ context 'above the chunk size' do
+ let(:chunk_size) { 2 }
+
+ it { is_expected.to match_array(expected_ids) }
+ end
+
+ context 'selecting too many rows' do
+ let(:rows_limit) { 2 }
+
+ it { expect { subject }.to raise_error(ArgumentError, 'Too many Dependencies selected') }
+ end
+ end
+ end
+
+ describe '.for_package_names_and_version_patterns' do
+ let_it_be(:package_dependency1) { create(:packages_dependency, name: 'foo', version_pattern: '~1.0.0') }
+ let_it_be(:package_dependency2) { create(:packages_dependency, name: 'bar', version_pattern: '~2.5.0') }
+ let_it_be(:expected_array) { [package_dependency1, package_dependency2] }
+ let(:names_and_version_patterns) { build_names_and_version_patterns(package_dependency1, package_dependency2) }
+
+ subject { Packages::Dependency.for_package_names_and_version_patterns(names_and_version_patterns) }
+
+ it { is_expected.to match_array(expected_array) }
+
+ context 'with unknown names' do
+ let(:names_and_version_patterns) { { unknown: '~1.0.0' } }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'with unknown version patterns' do
+ let(:names_and_version_patterns) { { 'foo' => '~1.0.0beta' } }
+
+ it { is_expected.to be_empty }
+ end
+ end
+
+ def build_names_and_version_patterns(*package_dependencies)
+ result = Hash.new { |h, dependency| h[dependency.name] = dependency.version_pattern }
+ package_dependencies.each { |dependency| result[dependency] }
+ result
+ end
+end
diff --git a/spec/models/packages/go/module_spec.rb b/spec/models/packages/go/module_spec.rb
new file mode 100644
index 00000000000..03af4cf4b70
--- /dev/null
+++ b/spec/models/packages/go/module_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Go::Module, type: :model do
+ before do
+ stub_feature_flags(go_proxy_disable_gomod_validation: false)
+ end
+
+ describe '#path_valid?' do
+ context 'with root path' do
+ let_it_be(:package) { create(:go_module) }
+
+ context 'with major version 0' do
+ it('returns true') { expect(package.path_valid?(0)).to eq(true) }
+ end
+
+ context 'with major version 1' do
+ it('returns true') { expect(package.path_valid?(1)).to eq(true) }
+ end
+
+ context 'with major version 2' do
+ it('returns false') { expect(package.path_valid?(2)).to eq(false) }
+ end
+ end
+
+ context 'with path ./v2' do
+ let_it_be(:package) { create(:go_module, path: '/v2') }
+
+ context 'with major version 0' do
+ it('returns false') { expect(package.path_valid?(0)).to eq(false) }
+ end
+
+ context 'with major version 1' do
+ it('returns false') { expect(package.path_valid?(1)).to eq(false) }
+ end
+
+ context 'with major version 2' do
+ it('returns true') { expect(package.path_valid?(2)).to eq(true) }
+ end
+ end
+ end
+
+ describe '#gomod_valid?' do
+ let_it_be(:package) { create(:go_module) }
+
+ context 'with good gomod' do
+ it('returns true') { expect(package.gomod_valid?("module #{package.name}")).to eq(true) }
+ end
+
+ context 'with bad gomod' do
+ it('returns false') { expect(package.gomod_valid?("module #{package.name}/v2")).to eq(false) }
+ end
+
+ context 'with empty gomod' do
+ it('returns false') { expect(package.gomod_valid?("")).to eq(false) }
+ end
+ end
+end
diff --git a/spec/models/packages/go/module_version_spec.rb b/spec/models/packages/go/module_version_spec.rb
new file mode 100644
index 00000000000..c4c6a07d9e9
--- /dev/null
+++ b/spec/models/packages/go/module_version_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Go::ModuleVersion, type: :model do
+ let_it_be(:user) { create :user }
+ let_it_be(:project) { create :project_empty_repo, creator: user, path: 'my-go-lib' }
+ let_it_be(:mod) { create :go_module, project: project }
+
+ before :all do
+ create :go_module_commit, :files, project: project, tag: 'v1.0.0', files: { 'README.md' => 'Hi' }
+ create :go_module_commit, :module, project: project, tag: 'v1.0.1'
+ create :go_module_commit, :package, project: project, tag: 'v1.0.2', path: 'pkg'
+ create :go_module_commit, :module, project: project, tag: 'v1.0.3', name: 'mod'
+ create :go_module_commit, :files, project: project, files: { 'y.go' => "package a\n" }
+ create :go_module_commit, :module, project: project, name: 'v2'
+ create :go_module_commit, :files, project: project, tag: 'v2.0.0', files: { 'v2/x.go' => "package a\n" }
+ end
+
+ shared_examples '#files' do |desc, *entries|
+ it "returns #{desc}" do
+ actual = version.files.map { |x| x }.to_set
+ expect(actual).to eq(entries.to_set)
+ end
+ end
+
+ shared_examples '#archive' do |desc, *entries|
+ it "returns an archive of #{desc}" do
+ expected = entries.map { |e| "#{version.full_name}/#{e}" }.to_set
+
+ actual = Set[]
+ Zip::InputStream.open(StringIO.new(version.archive.string)) do |zip|
+ while (entry = zip.get_next_entry)
+ actual.add(entry.name)
+ end
+ end
+
+ expect(actual).to eq(expected)
+ end
+ end
+
+ describe '#name' do
+ context 'with ref and name specified' do
+ let_it_be(:version) { create :go_module_version, mod: mod, name: 'foobar', commit: project.repository.head_commit, ref: project.repository.find_tag('v1.0.0') }
+ it('returns that name') { expect(version.name).to eq('foobar') }
+ end
+
+ context 'with ref specified and name unspecified' do
+ let_it_be(:version) { create :go_module_version, mod: mod, commit: project.repository.head_commit, ref: project.repository.find_tag('v1.0.0') }
+ it('returns the name of the ref') { expect(version.name).to eq('v1.0.0') }
+ end
+
+ context 'with ref and name unspecified' do
+ let_it_be(:version) { create :go_module_version, mod: mod, commit: project.repository.head_commit }
+ it('returns nil') { expect(version.name).to eq(nil) }
+ end
+ end
+
+ describe '#gomod' do
+ context 'with go.mod missing' do
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.0' }
+ it('returns nil') { expect(version.gomod).to eq(nil) }
+ end
+
+ context 'with go.mod present' do
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.1' }
+ it('returns the contents of go.mod') { expect(version.gomod).to eq("module #{mod.name}\n") }
+ end
+ end
+
+ describe '#files' do
+ context 'with a root module' do
+ context 'with an empty module path' do
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.2' }
+ it_behaves_like '#files', 'all the files', 'README.md', 'go.mod', 'a.go', 'pkg/b.go'
+ end
+ end
+
+ context 'with a root module and a submodule' do
+ context 'with an empty module path' do
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.3' }
+ it_behaves_like '#files', 'files excluding the submodule', 'README.md', 'go.mod', 'a.go', 'pkg/b.go'
+ end
+
+ context 'with the submodule\'s path' do
+ let_it_be(:mod) { create :go_module, project: project, path: 'mod' }
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.3' }
+ it_behaves_like '#files', 'the submodule\'s files', 'mod/go.mod', 'mod/a.go'
+ end
+ end
+ end
+
+ describe '#archive' do
+ context 'with a root module' do
+ context 'with an empty module path' do
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.2' }
+ it_behaves_like '#archive', 'all the files', 'README.md', 'go.mod', 'a.go', 'pkg/b.go'
+ end
+ end
+
+ context 'with a root module and a submodule' do
+ context 'with an empty module path' do
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.3' }
+ it_behaves_like '#archive', 'files excluding the submodule', 'README.md', 'go.mod', 'a.go', 'pkg/b.go'
+ end
+
+ context 'with the submodule\'s path' do
+ let_it_be(:mod) { create :go_module, project: project, path: 'mod' }
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.3' }
+ it_behaves_like '#archive', 'the submodule\'s files', 'go.mod', 'a.go'
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/maven/metadatum_spec.rb b/spec/models/packages/maven/metadatum_spec.rb
new file mode 100644
index 00000000000..16f6929d710
--- /dev/null
+++ b/spec/models/packages/maven/metadatum_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Maven::Metadatum, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:package) }
+
+ describe '#app_name' do
+ it { is_expected.to allow_value("my-app").for(:app_name) }
+ it { is_expected.not_to allow_value("my/app").for(:app_name) }
+ it { is_expected.not_to allow_value("my(app)").for(:app_name) }
+ end
+
+ describe '#app_group' do
+ it { is_expected.to allow_value("my.domain.com").for(:app_group) }
+ it { is_expected.not_to allow_value("my/domain/com").for(:app_group) }
+ it { is_expected.not_to allow_value("my(domain)").for(:app_group) }
+ end
+
+ describe '#path' do
+ it { is_expected.to allow_value("my/domain/com/my-app").for(:path) }
+ it { is_expected.to allow_value("my/domain/com/my-app/1.0-SNAPSHOT").for(:path) }
+ it { is_expected.not_to allow_value("my(domain)com.my-app").for(:path) }
+ end
+
+ describe '#maven_package_type' do
+ it 'will not allow a package with a different package_type' do
+ package = build('conan_package')
+ maven_metadatum = build('maven_metadatum', package: package)
+
+ expect(maven_metadatum).not_to be_valid
+ expect(maven_metadatum.errors.to_a).to include('Package type must be Maven')
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/nuget/dependency_link_metadatum_spec.rb b/spec/models/packages/nuget/dependency_link_metadatum_spec.rb
new file mode 100644
index 00000000000..0c03c65028e
--- /dev/null
+++ b/spec/models/packages/nuget/dependency_link_metadatum_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::DependencyLinkMetadatum, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:dependency_link) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:dependency_link) }
+ it { is_expected.to validate_presence_of(:target_framework) }
+
+ describe '#ensure_nuget_package_type' do
+ it 'validates package of type nuget' do
+ package = build('conan_package')
+ dependency_link = build('packages_dependency_link', package: package)
+ nuget_metadatum = build('nuget_dependency_link_metadatum', dependency_link: dependency_link)
+
+ expect(nuget_metadatum).not_to be_valid
+ expect(nuget_metadatum.errors.to_a).to contain_exactly('Package type must be NuGet')
+ end
+
+ it 'validates package of type nuget with nil dependency_link' do
+ nuget_metadatum = build('nuget_dependency_link_metadatum', dependency_link: nil)
+
+ expect(nuget_metadatum).not_to be_valid
+ expect(nuget_metadatum.errors.to_a).to contain_exactly("Dependency link can't be blank", 'Package type must be NuGet')
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/nuget/metadatum_spec.rb b/spec/models/packages/nuget/metadatum_spec.rb
new file mode 100644
index 00000000000..c1bc5429500
--- /dev/null
+++ b/spec/models/packages/nuget/metadatum_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::Metadatum, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package).inverse_of(:nuget_metadatum) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:package) }
+
+ %i[license_url project_url icon_url].each do |url|
+ describe "##{url}" do
+ it { is_expected.to allow_value('http://sandbox.com').for(url) }
+ it { is_expected.to allow_value('https://sandbox.com').for(url) }
+ it { is_expected.not_to allow_value('123').for(url) }
+ it { is_expected.not_to allow_value('sandbox.com').for(url) }
+ end
+
+ describe '#ensure_at_least_one_field_supplied' do
+ subject { build(:nuget_metadatum) }
+
+ it 'rejects unfilled metadatum' do
+ subject.attributes = { license_url: nil, project_url: nil, icon_url: nil }
+
+ expect(subject).not_to be_valid
+ expect(subject.errors).to contain_exactly('Nuget metadatum must have at least license_url, project_url or icon_url set')
+ end
+ end
+
+ describe '#ensure_nuget_package_type' do
+ subject { build(:nuget_metadatum) }
+
+ it 'rejects if not linked to a nuget package' do
+ subject.package = build(:npm_package)
+
+ expect(subject).not_to be_valid
+ expect(subject.errors).to contain_exactly('Package type must be NuGet')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/package_file_spec.rb b/spec/models/packages/package_file_spec.rb
new file mode 100644
index 00000000000..7758ed4a500
--- /dev/null
+++ b/spec/models/packages/package_file_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::PackageFile, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package) }
+ it { is_expected.to have_one(:conan_file_metadatum) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:package) }
+ end
+
+ context 'with package filenames' do
+ let_it_be(:package_file1) { create(:package_file, :xml, file_name: 'FooBar') }
+ let_it_be(:package_file2) { create(:package_file, :xml, file_name: 'ThisIsATest') }
+
+ describe '.with_file_name' do
+ let(:filename) { 'FooBar' }
+
+ subject { described_class.with_file_name(filename) }
+
+ it { is_expected.to match_array([package_file1]) }
+ end
+
+ describe '.with_file_name_like' do
+ let(:filename) { 'foobar' }
+
+ subject { described_class.with_file_name_like(filename) }
+
+ it { is_expected.to match_array([package_file1]) }
+ end
+ end
+
+ it_behaves_like 'UpdateProjectStatistics' do
+ subject { build(:package_file, :jar, size: 42) }
+
+ before do
+ allow_any_instance_of(Packages::PackageFileUploader).to receive(:size).and_return(42)
+ end
+ end
+
+ describe '.with_conan_package_reference' do
+ let_it_be(:non_matching_package_file) { create(:package_file, :nuget) }
+ let_it_be(:metadatum) { create(:conan_file_metadatum, :package_file) }
+ let_it_be(:reference) { metadatum.conan_package_reference}
+
+ it 'returns matching packages' do
+ expect(described_class.with_conan_package_reference(reference))
+ .to eq([metadatum.package_file])
+ end
+ end
+
+ describe '#update_file_metadata callback' do
+ let_it_be(:package_file) { build(:package_file, :nuget, file_store: nil, size: nil) }
+
+ subject { package_file.save! }
+
+ it 'updates metadata columns' do
+ expect(package_file)
+ .to receive(:update_file_metadata)
+ .and_call_original
+
+ expect { subject }
+ .to change { package_file.file_store }.from(nil).to(::Packages::PackageFileUploader::Store::LOCAL)
+ .and change { package_file.size }.from(nil).to(3513)
+ end
+ end
+end
diff --git a/spec/models/packages/package_spec.rb b/spec/models/packages/package_spec.rb
new file mode 100644
index 00000000000..4170bf595f0
--- /dev/null
+++ b/spec/models/packages/package_spec.rb
@@ -0,0 +1,485 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Package, type: :model do
+ include SortingHelper
+
+ describe 'relationships' do
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to have_many(:package_files).dependent(:destroy) }
+ it { is_expected.to have_many(:dependency_links).inverse_of(:package) }
+ it { is_expected.to have_many(:tags).inverse_of(:package) }
+ it { is_expected.to have_one(:conan_metadatum).inverse_of(:package) }
+ it { is_expected.to have_one(:maven_metadatum).inverse_of(:package) }
+ it { is_expected.to have_one(:nuget_metadatum).inverse_of(:package) }
+ end
+
+ describe '.with_composer_target' do
+ let!(:package1) { create(:composer_package, :with_metadatum, sha: '123') }
+ let!(:package2) { create(:composer_package, :with_metadatum, sha: '123') }
+ let!(:package3) { create(:composer_package, :with_metadatum, sha: '234') }
+
+ subject { described_class.with_composer_target('123').to_a }
+
+ it 'selects packages with the specified sha' do
+ expect(subject).to include(package1)
+ expect(subject).to include(package2)
+ expect(subject).not_to include(package3)
+ end
+ end
+
+ describe '.sort_by_attribute' do
+ let_it_be(:group) { create(:group, :public) }
+ let_it_be(:project) { create(:project, :public, namespace: group, name: 'project A') }
+ let!(:package1) { create(:npm_package, project: project, version: '3.1.0', name: "@#{project.root_namespace.path}/foo1") }
+ let!(:package2) { create(:nuget_package, project: project, version: '2.0.4') }
+ let(:package3) { create(:maven_package, project: project, version: '1.1.1', name: 'zzz') }
+
+ before do
+ travel_to(1.day.ago) do
+ package3
+ end
+ end
+
+ RSpec.shared_examples 'package sorting by attribute' do |order_by|
+ subject { described_class.where(id: packages.map(&:id)).sort_by_attribute("#{order_by}_#{sort}").to_a }
+
+ context "sorting by #{order_by}" do
+ context 'ascending order' do
+ let(:sort) { 'asc' }
+
+ it { is_expected.to eq packages }
+ end
+
+ context 'descending order' do
+ let(:sort) { 'desc' }
+
+ it { is_expected.to eq packages.reverse }
+ end
+ end
+ end
+
+ it_behaves_like 'package sorting by attribute', 'name' do
+ let(:packages) { [package1, package2, package3] }
+ end
+
+ it_behaves_like 'package sorting by attribute', 'created_at' do
+ let(:packages) { [package3, package1, package2] }
+ end
+
+ it_behaves_like 'package sorting by attribute', 'version' do
+ let(:packages) { [package3, package2, package1] }
+ end
+
+ it_behaves_like 'package sorting by attribute', 'type' do
+ let(:packages) { [package3, package1, package2] }
+ end
+
+ it_behaves_like 'package sorting by attribute', 'project_path' do
+ let(:another_project) { create(:project, :public, namespace: group, name: 'project B') }
+ let!(:package4) { create(:npm_package, project: another_project, version: '3.1.0', name: "@#{project.root_namespace.path}/bar") }
+
+ let(:packages) { [package1, package2, package3, package4] }
+ end
+ end
+
+ describe 'validations' do
+ subject { create(:package) }
+
+ it { is_expected.to validate_presence_of(:project) }
+ it { is_expected.to validate_uniqueness_of(:name).scoped_to(:project_id, :version, :package_type) }
+
+ describe '#name' do
+ it { is_expected.to allow_value("my/domain/com/my-app").for(:name) }
+ it { is_expected.to allow_value("my.app-11.07.2018").for(:name) }
+ it { is_expected.not_to allow_value("my(dom$$$ain)com.my-app").for(:name) }
+
+ context 'conan package' do
+ subject { create(:conan_package) }
+
+ let(:fifty_one_characters) {'f_b' * 17}
+
+ it { is_expected.to allow_value('foo+bar').for(:name) }
+ it { is_expected.to allow_value('foo_bar').for(:name) }
+ it { is_expected.to allow_value('foo.bar').for(:name) }
+ it { is_expected.not_to allow_value(fifty_one_characters).for(:name) }
+ it { is_expected.not_to allow_value('+foobar').for(:name) }
+ it { is_expected.not_to allow_value('.foobar').for(:name) }
+ it { is_expected.not_to allow_value('%foo%bar').for(:name) }
+ end
+ end
+
+ describe '#version' do
+ RSpec.shared_examples 'validating version to be SemVer compliant for' do |factory_name|
+ context "for #{factory_name}" do
+ subject { create(factory_name) }
+
+ it { is_expected.to allow_value('1.2.3').for(:version) }
+ it { is_expected.to allow_value('1.2.3-beta').for(:version) }
+ it { is_expected.to allow_value('1.2.3-alpha.3').for(:version) }
+ it { is_expected.not_to allow_value('1').for(:version) }
+ it { is_expected.not_to allow_value('1.2').for(:version) }
+ it { is_expected.not_to allow_value('1./2.3').for(:version) }
+ it { is_expected.not_to allow_value('../../../../../1.2.3').for(:version) }
+ it { is_expected.not_to allow_value('%2e%2e%2f1.2.3').for(:version) }
+ end
+ end
+
+ context 'conan package' do
+ subject { create(:conan_package) }
+
+ let(:fifty_one_characters) {'1.2' * 17}
+
+ it { is_expected.to allow_value('1.2').for(:version) }
+ it { is_expected.to allow_value('1.2.3-beta').for(:version) }
+ it { is_expected.to allow_value('1.2.3-pre1+build2').for(:version) }
+ it { is_expected.not_to allow_value('1').for(:version) }
+ it { is_expected.not_to allow_value(fifty_one_characters).for(:version) }
+ it { is_expected.not_to allow_value('1./2.3').for(:version) }
+ it { is_expected.not_to allow_value('.1.2.3').for(:version) }
+ it { is_expected.not_to allow_value('+1.2.3').for(:version) }
+ it { is_expected.not_to allow_value('%2e%2e%2f1.2.3').for(:version) }
+ end
+
+ context 'maven package' do
+ subject { create(:maven_package) }
+
+ it { is_expected.to allow_value('0').for(:version) }
+ it { is_expected.to allow_value('1').for(:version) }
+ it { is_expected.to allow_value('10').for(:version) }
+ it { is_expected.to allow_value('1.0').for(:version) }
+ it { is_expected.to allow_value('1.3.350.v20200505-1744').for(:version) }
+ it { is_expected.to allow_value('1.1-beta-2').for(:version) }
+ it { is_expected.to allow_value('1.2-SNAPSHOT').for(:version) }
+ it { is_expected.to allow_value('12.1.2-2-1').for(:version) }
+ it { is_expected.to allow_value('1.2.3..beta').for(:version) }
+ it { is_expected.to allow_value('1.2.3-beta').for(:version) }
+ it { is_expected.to allow_value('10.2.3-beta').for(:version) }
+ it { is_expected.to allow_value('2.0.0.v200706041905-7C78EK9E_EkMNfNOd2d8qq').for(:version) }
+ it { is_expected.to allow_value('1.2-alpha-1-20050205.060708-1').for(:version) }
+ it { is_expected.to allow_value('703220b4e2cea9592caeb9f3013f6b1e5335c293').for(:version) }
+ it { is_expected.to allow_value('RELEASE').for(:version) }
+ it { is_expected.not_to allow_value('..1.2.3').for(:version) }
+ it { is_expected.not_to allow_value(' 1.2.3').for(:version) }
+ it { is_expected.not_to allow_value("1.2.3 \r\t").for(:version) }
+ it { is_expected.not_to allow_value("\r\t 1.2.3").for(:version) }
+ it { is_expected.not_to allow_value('1.2.3-4/../../').for(:version) }
+ it { is_expected.not_to allow_value('1.2.3-4%2e%2e%').for(:version) }
+ it { is_expected.not_to allow_value('../../../../../1.2.3').for(:version) }
+ it { is_expected.not_to allow_value('%2e%2e%2f1.2.3').for(:version) }
+ end
+
+ it_behaves_like 'validating version to be SemVer compliant for', :npm_package
+ it_behaves_like 'validating version to be SemVer compliant for', :nuget_package
+ end
+
+ describe '#package_already_taken' do
+ context 'npm package' do
+ let!(:package) { create(:npm_package) }
+
+ it 'will not allow a package of the same name' do
+ new_package = build(:npm_package, name: package.name)
+
+ expect(new_package).not_to be_valid
+ end
+ end
+
+ context 'maven package' do
+ let!(:package) { create(:maven_package) }
+
+ it 'will allow a package of the same name' do
+ new_package = build(:maven_package, name: package.name)
+
+ expect(new_package).to be_valid
+ end
+ end
+ end
+
+ context "recipe uniqueness for conan packages" do
+ let!(:package) { create('conan_package') }
+
+ it "will allow a conan package with same project, name, version and package_type" do
+ new_package = build('conan_package', project: package.project, name: package.name, version: package.version)
+ new_package.conan_metadatum.package_channel = 'beta'
+ expect(new_package).to be_valid
+ end
+
+ it "will not allow a conan package with same recipe (name, version, metadatum.package_channel, metadatum.package_username, and package_type)" do
+ new_package = build('conan_package', project: package.project, name: package.name, version: package.version)
+ expect(new_package).not_to be_valid
+ expect(new_package.errors.to_a).to include("Package recipe already exists")
+ end
+ end
+
+ Packages::Package.package_types.keys.without('conan').each do |pt|
+ context "project id, name, version and package type uniqueness for package type #{pt}" do
+ let(:package) { create("#{pt}_package") }
+
+ it "will not allow a #{pt} package with same project, name, version and package_type" do
+ new_package = build("#{pt}_package", project: package.project, name: package.name, version: package.version)
+ expect(new_package).not_to be_valid
+ expect(new_package.errors.to_a).to include("Name has already been taken")
+ end
+ end
+ end
+ end
+
+ describe '#destroy' do
+ let(:package) { create(:npm_package) }
+ let(:package_file) { package.package_files.first }
+ let(:project_statistics) { ProjectStatistics.for_project_ids(package.project.id).first }
+
+ it 'affects project statistics' do
+ expect { package.destroy! }
+ .to change { project_statistics.reload.packages_size }
+ .from(package_file.size).to(0)
+ end
+ end
+
+ describe '.by_name_and_file_name' do
+ let!(:package) { create(:npm_package) }
+ let!(:package_file) { package.package_files.first }
+
+ subject { described_class }
+
+ it 'finds a package with correct arguiments' do
+ expect(subject.by_name_and_file_name(package.name, package_file.file_name)).to eq(package)
+ end
+
+ it 'will raise error if not found' do
+ expect { subject.by_name_and_file_name('foo', 'foo-5.5.5.tgz') }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ context 'version scopes' do
+ let!(:package1) { create(:npm_package, version: '1.0.0') }
+ let!(:package2) { create(:npm_package, version: '1.0.1') }
+ let!(:package3) { create(:npm_package, version: '1.0.1') }
+
+ describe '.last_of_each_version' do
+ subject { described_class.last_of_each_version }
+
+ it 'includes only latest package per version' do
+ is_expected.to include(package1, package3)
+ is_expected.not_to include(package2)
+ end
+ end
+
+ describe '.has_version' do
+ subject { described_class.has_version }
+
+ before do
+ create(:maven_metadatum).package.update!(version: nil)
+ end
+
+ it 'includes only packages with version attribute' do
+ is_expected.to match_array([package1, package2, package3])
+ end
+ end
+
+ describe '.with_version' do
+ subject { described_class.with_version('1.0.1') }
+
+ it 'includes only packages with specified version' do
+ is_expected.to match_array([package2, package3])
+ end
+ end
+
+ describe '.without_version_like' do
+ let(:version_pattern) { '%.0.0%' }
+
+ subject { described_class.without_version_like(version_pattern) }
+
+ it 'includes packages without the version pattern' do
+ is_expected.to match_array([package2, package3])
+ end
+ end
+ end
+
+ context 'conan scopes' do
+ let!(:package) { create(:conan_package) }
+
+ describe '.with_conan_channel' do
+ subject { described_class.with_conan_channel('stable') }
+
+ it 'includes only packages with specified version' do
+ is_expected.to include(package)
+ end
+ end
+
+ describe '.with_conan_username' do
+ subject do
+ described_class.with_conan_username(
+ Packages::Conan::Metadatum.package_username_from(full_path: package.project.full_path)
+ )
+ end
+
+ it 'includes only packages with specified version' do
+ is_expected.to match_array([package])
+ end
+ end
+ end
+
+ describe '.without_nuget_temporary_name' do
+ let!(:package1) { create(:nuget_package) }
+ let!(:package2) { create(:nuget_package, name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) }
+
+ subject { described_class.without_nuget_temporary_name }
+
+ it 'does not include nuget temporary packages' do
+ expect(subject).to eq([package1])
+ end
+ end
+
+ describe '.processed' do
+ let!(:package1) { create(:nuget_package) }
+ let!(:package2) { create(:npm_package) }
+ let!(:package3) { create(:nuget_package) }
+
+ subject { described_class.processed }
+
+ it { is_expected.to match_array([package1, package2, package3]) }
+
+ context 'with temporary packages' do
+ let!(:package1) { create(:nuget_package, name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) }
+
+ it { is_expected.to match_array([package2, package3]) }
+ end
+ end
+
+ describe '.limit_recent' do
+ let!(:package1) { create(:nuget_package) }
+ let!(:package2) { create(:nuget_package) }
+ let!(:package3) { create(:nuget_package) }
+
+ subject { described_class.limit_recent(2) }
+
+ it { is_expected.to match_array([package3, package2]) }
+ end
+
+ context 'with several packages' do
+ let_it_be(:package1) { create(:nuget_package, name: 'FooBar') }
+ let_it_be(:package2) { create(:nuget_package, name: 'foobar') }
+ let_it_be(:package3) { create(:npm_package) }
+ let_it_be(:package4) { create(:npm_package) }
+
+ describe '.pluck_names' do
+ subject { described_class.pluck_names }
+
+ it { is_expected.to match_array([package1, package2, package3, package4].map(&:name)) }
+ end
+
+ describe '.pluck_versions' do
+ subject { described_class.pluck_versions }
+
+ it { is_expected.to match_array([package1, package2, package3, package4].map(&:version)) }
+ end
+
+ describe '.with_name_like' do
+ subject { described_class.with_name_like(name_term) }
+
+ context 'with downcase name' do
+ let(:name_term) { 'foobar' }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+
+ context 'with prefix wildcard' do
+ let(:name_term) { '%ar' }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+
+ context 'with suffix wildcard' do
+ let(:name_term) { 'foo%' }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+
+ context 'with surrounding wildcards' do
+ let(:name_term) { '%ooba%' }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+ end
+
+ describe '.search_by_name' do
+ let(:query) { 'oba' }
+
+ subject { described_class.search_by_name(query) }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+ end
+
+ describe '.select_distinct_name' do
+ let_it_be(:nuget_package) { create(:nuget_package) }
+ let_it_be(:nuget_packages) { create_list(:nuget_package, 3, name: nuget_package.name, project: nuget_package.project) }
+ let_it_be(:maven_package) { create(:maven_package) }
+ let_it_be(:maven_packages) { create_list(:maven_package, 3, name: maven_package.name, project: maven_package.project) }
+
+ subject { described_class.select_distinct_name }
+
+ it 'returns only distinct names' do
+ packages = subject
+
+ expect(packages.size).to eq(2)
+ expect(packages.pluck(:name)).to match_array([nuget_package.name, maven_package.name])
+ end
+ end
+
+ describe '#versions' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:package) { create(:maven_package, project: project) }
+ let_it_be(:package2) { create(:maven_package, project: project) }
+ let_it_be(:package3) { create(:maven_package, project: project, name: 'foo') }
+
+ it 'returns other package versions of the same package name belonging to the project' do
+ expect(package.versions).to contain_exactly(package2)
+ end
+
+ it 'does not return different packages' do
+ expect(package.versions).not_to include(package3)
+ end
+ end
+
+ describe '#pipeline' do
+ let_it_be(:package) { create(:maven_package) }
+
+ context 'package without pipeline' do
+ it 'returns nil if there is no pipeline' do
+ expect(package.pipeline).to be_nil
+ end
+ end
+
+ context 'package with pipeline' do
+ let_it_be(:pipeline) { create(:ci_pipeline) }
+
+ before do
+ package.create_build_info!(pipeline: pipeline)
+ end
+
+ it 'returns the pipeline' do
+ expect(package.pipeline).to eq(pipeline)
+ end
+ end
+ end
+
+ describe '#tag_names' do
+ let_it_be(:package) { create(:nuget_package) }
+
+ subject { package.tag_names }
+
+ it { is_expected.to eq([]) }
+
+ context 'with tags' do
+ let(:tags) { %w(tag1 tag2 tag3) }
+
+ before do
+ tags.each { |t| create(:packages_tag, name: t, package: package) }
+ end
+
+ it { is_expected.to contain_exactly(*tags) }
+ end
+ end
+end
diff --git a/spec/models/packages/pypi/metadatum_spec.rb b/spec/models/packages/pypi/metadatum_spec.rb
new file mode 100644
index 00000000000..2c9893ef8f3
--- /dev/null
+++ b/spec/models/packages/pypi/metadatum_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Pypi::Metadatum, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:package) }
+
+ describe '#pypi_package_type' do
+ it 'will not allow a package with a different package_type' do
+ package = build('package')
+ pypi_metadatum = build('pypi_metadatum', package: package)
+
+ expect(pypi_metadatum).not_to be_valid
+ expect(pypi_metadatum.errors.to_a).to include('Package type must be PyPi')
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/sem_ver_spec.rb b/spec/models/packages/sem_ver_spec.rb
new file mode 100644
index 00000000000..419653dca19
--- /dev/null
+++ b/spec/models/packages/sem_ver_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::SemVer, type: :model do
+ shared_examples '#parse with a valid semver' do |str, major, minor, patch, prerelease, build|
+ context "with #{str}" do
+ it "returns #{described_class.new(major, minor, patch, prerelease, build, prefixed: true)} with prefix" do
+ expected = described_class.new(major, minor, patch, prerelease, build, prefixed: true)
+ expect(described_class.parse('v' + str, prefixed: true)).to eq(expected)
+ end
+
+ it "returns #{described_class.new(major, minor, patch, prerelease, build)} without prefix" do
+ expected = described_class.new(major, minor, patch, prerelease, build)
+ expect(described_class.parse(str)).to eq(expected)
+ end
+ end
+ end
+
+ shared_examples '#parse with an invalid semver' do |str|
+ context "with #{str}" do
+ it 'returns nil with prefix' do
+ expect(described_class.parse('v' + str, prefixed: true)).to be_nil
+ end
+
+ it 'returns nil without prefix' do
+ expect(described_class.parse(str)).to be_nil
+ end
+ end
+ end
+
+ describe '#parse' do
+ it_behaves_like '#parse with a valid semver', '1.0.0', 1, 0, 0, nil, nil
+ it_behaves_like '#parse with a valid semver', '1.0.0-pre', 1, 0, 0, 'pre', nil
+ it_behaves_like '#parse with a valid semver', '1.0.0+build', 1, 0, 0, nil, 'build'
+ it_behaves_like '#parse with a valid semver', '1.0.0-pre+build', 1, 0, 0, 'pre', 'build'
+ it_behaves_like '#parse with an invalid semver', '01.0.0'
+ it_behaves_like '#parse with an invalid semver', '0.01.0'
+ it_behaves_like '#parse with an invalid semver', '0.0.01'
+ it_behaves_like '#parse with an invalid semver', '1.0.0asdf'
+ end
+end
diff --git a/spec/models/packages/tag_spec.rb b/spec/models/packages/tag_spec.rb
new file mode 100644
index 00000000000..18ec99c3d51
--- /dev/null
+++ b/spec/models/packages/tag_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Tag, type: :model do
+ let!(:project) { create(:project) }
+ let!(:package) { create(:npm_package, version: '1.0.2', project: project, updated_at: 3.days.ago) }
+
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package).inverse_of(:tags) }
+ end
+
+ describe 'validations' do
+ subject { create(:packages_tag) }
+
+ it { is_expected.to validate_presence_of(:package) }
+ it { is_expected.to validate_presence_of(:name) }
+ end
+
+ describe '.for_packages' do
+ let(:package2) { create(:package, project: project, updated_at: 2.days.ago) }
+ let(:package3) { create(:package, project: project, updated_at: 1.day.ago) }
+ let!(:tag1) { create(:packages_tag, package: package) }
+ let!(:tag2) { create(:packages_tag, package: package2) }
+ let!(:tag3) { create(:packages_tag, package: package3) }
+
+ subject { described_class.for_packages(project.packages) }
+
+ it { is_expected.to match_array([tag1, tag2, tag3]) }
+
+ context 'with too many tags' do
+ before do
+ stub_const('Packages::Tag::FOR_PACKAGES_TAGS_LIMIT', 2)
+ end
+
+ it { is_expected.to match_array([tag2, tag3]) }
+ end
+ end
+
+ describe '.with_name' do
+ let_it_be(:package) { create(:package) }
+ let_it_be(:tag1) { create(:packages_tag, package: package, name: 'tag1') }
+ let_it_be(:tag2) { create(:packages_tag, package: package, name: 'tag2') }
+ let_it_be(:tag3) { create(:packages_tag, package: package, name: 'tag3') }
+ let(:name) { 'tag1' }
+
+ subject { described_class.with_name(name) }
+
+ it { is_expected.to contain_exactly(tag1) }
+
+ context 'with nil name' do
+ let(:name) { nil }
+
+ it { is_expected.to eq([]) }
+ end
+
+ context 'with multiple names' do
+ let(:name) { %w(tag1 tag3) }
+
+ it { is_expected.to contain_exactly(tag1, tag3) }
+ end
+ end
+end
diff --git a/spec/models/pages/lookup_path_spec.rb b/spec/models/pages/lookup_path_spec.rb
index c05d4c82634..38bd9b39a56 100644
--- a/spec/models/pages/lookup_path_spec.rb
+++ b/spec/models/pages/lookup_path_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Pages::LookupPath do
+RSpec.describe Pages::LookupPath do
let(:project) do
instance_double(Project,
id: 12345,
diff --git a/spec/models/pages/virtual_domain_spec.rb b/spec/models/pages/virtual_domain_spec.rb
index a5310738482..38f5f4d2538 100644
--- a/spec/models/pages/virtual_domain_spec.rb
+++ b/spec/models/pages/virtual_domain_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Pages::VirtualDomain do
+RSpec.describe Pages::VirtualDomain do
describe '#certificate and #key pair' do
let(:domain) { nil }
let(:project) { instance_double(Project) }
diff --git a/spec/models/pages_domain_acme_order_spec.rb b/spec/models/pages_domain_acme_order_spec.rb
index 4ffb4fc7389..4a104203e39 100644
--- a/spec/models/pages_domain_acme_order_spec.rb
+++ b/spec/models/pages_domain_acme_order_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PagesDomainAcmeOrder do
+RSpec.describe PagesDomainAcmeOrder do
using RSpec::Parameterized::TableSyntax
describe '.expired' do
diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb
index fc7694530d0..d283389e29e 100644
--- a/spec/models/pages_domain_spec.rb
+++ b/spec/models/pages_domain_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PagesDomain do
+RSpec.describe PagesDomain do
using RSpec::Parameterized::TableSyntax
subject(:pages_domain) { described_class.new }
diff --git a/spec/models/performance_monitoring/prometheus_dashboard_spec.rb b/spec/models/performance_monitoring/prometheus_dashboard_spec.rb
index ef7298c3d8c..61174a7d0c5 100644
--- a/spec/models/performance_monitoring/prometheus_dashboard_spec.rb
+++ b/spec/models/performance_monitoring/prometheus_dashboard_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PerformanceMonitoring::PrometheusDashboard do
+RSpec.describe PerformanceMonitoring::PrometheusDashboard do
let(:json_content) do
{
"dashboard" => "Dashboard Title",
@@ -50,19 +50,19 @@ describe PerformanceMonitoring::PrometheusDashboard do
context 'dashboard content is missing' do
let(:json_content) { nil }
- it_behaves_like 'validation failed', panel_groups: ["can't be blank"], dashboard: ["can't be blank"]
+ it_behaves_like 'validation failed', panel_groups: ["should be an array of panel_groups objects"], dashboard: ["can't be blank"]
end
context 'dashboard content is NOT a hash' do
let(:json_content) { YAML.safe_load("'test'") }
- it_behaves_like 'validation failed', panel_groups: ["can't be blank"], dashboard: ["can't be blank"]
+ it_behaves_like 'validation failed', panel_groups: ["should be an array of panel_groups objects"], dashboard: ["can't be blank"]
end
context 'content is an array' do
let(:json_content) { [{ "dashboard" => "Dashboard Title" }] }
- it_behaves_like 'validation failed', panel_groups: ["can't be blank"], dashboard: ["can't be blank"]
+ it_behaves_like 'validation failed', panel_groups: ["should be an array of panel_groups objects"], dashboard: ["can't be blank"]
end
context 'dashboard definition is missing panels_groups and dashboard keys' do
@@ -72,7 +72,7 @@ describe PerformanceMonitoring::PrometheusDashboard do
}
end
- it_behaves_like 'validation failed', panel_groups: ["can't be blank"], dashboard: ["can't be blank"]
+ it_behaves_like 'validation failed', panel_groups: ["should be an array of panel_groups objects"], dashboard: ["can't be blank"]
end
context 'group definition is missing panels and group keys' do
@@ -88,7 +88,7 @@ describe PerformanceMonitoring::PrometheusDashboard do
}
end
- it_behaves_like 'validation failed', panels: ["can't be blank"], group: ["can't be blank"]
+ it_behaves_like 'validation failed', panels: ["should be an array of panels objects"], group: ["can't be blank"]
end
context 'panel definition is missing metrics and title keys' do
@@ -110,7 +110,7 @@ describe PerformanceMonitoring::PrometheusDashboard do
}
end
- it_behaves_like 'validation failed', metrics: ["can't be blank"], title: ["can't be blank"]
+ it_behaves_like 'validation failed', metrics: ["should be an array of metrics objects"], title: ["can't be blank"]
end
context 'metrics definition is missing unit, query and query_range keys' do
@@ -180,7 +180,7 @@ describe PerformanceMonitoring::PrometheusDashboard do
describe '.find_for' do
let(:project) { build_stubbed(:project) }
let(:user) { build_stubbed(:user) }
- let(:environment) { build_stubbed(:environment) }
+ let(:environment) { build_stubbed(:environment, project: project) }
let(:path) { ::Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH }
context 'dashboard has been found' do
diff --git a/spec/models/performance_monitoring/prometheus_metric_spec.rb b/spec/models/performance_monitoring/prometheus_metric_spec.rb
index 83f687aa90e..b5b9cd58aa8 100644
--- a/spec/models/performance_monitoring/prometheus_metric_spec.rb
+++ b/spec/models/performance_monitoring/prometheus_metric_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PerformanceMonitoring::PrometheusMetric do
+RSpec.describe PerformanceMonitoring::PrometheusMetric do
let(:json_content) do
{
"id" => "metric_of_ages",
diff --git a/spec/models/performance_monitoring/prometheus_panel_group_spec.rb b/spec/models/performance_monitoring/prometheus_panel_group_spec.rb
index ecf7e13a9a3..9e92cb27954 100644
--- a/spec/models/performance_monitoring/prometheus_panel_group_spec.rb
+++ b/spec/models/performance_monitoring/prometheus_panel_group_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PerformanceMonitoring::PrometheusPanelGroup do
+RSpec.describe PerformanceMonitoring::PrometheusPanelGroup do
let(:json_content) do
{
"group" => "Group Title",
diff --git a/spec/models/performance_monitoring/prometheus_panel_spec.rb b/spec/models/performance_monitoring/prometheus_panel_spec.rb
index 127b9e8183a..c5c6b1fdafd 100644
--- a/spec/models/performance_monitoring/prometheus_panel_spec.rb
+++ b/spec/models/performance_monitoring/prometheus_panel_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PerformanceMonitoring::PrometheusPanel do
+RSpec.describe PerformanceMonitoring::PrometheusPanel do
let(:json_content) do
{
"max_value" => 1,
diff --git a/spec/models/personal_access_token_spec.rb b/spec/models/personal_access_token_spec.rb
index a3f5eb38511..a39a37b605f 100644
--- a/spec/models/personal_access_token_spec.rb
+++ b/spec/models/personal_access_token_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PersonalAccessToken do
+RSpec.describe PersonalAccessToken do
subject { described_class }
describe '.build' do
@@ -165,6 +165,7 @@ describe PersonalAccessToken do
let_it_be(:revoked_token) { create(:personal_access_token, revoked: true) }
let_it_be(:valid_token_and_notified) { create(:personal_access_token, expires_at: 2.days.from_now, expire_notification_delivered: true) }
let_it_be(:valid_token) { create(:personal_access_token, expires_at: 2.days.from_now) }
+ let_it_be(:long_expiry_token) { create(:personal_access_token, expires_at: '999999-12-31'.to_date) }
context 'in one day' do
it "doesn't have any tokens" do
@@ -187,10 +188,24 @@ describe PersonalAccessToken do
expect(described_class.without_impersonation).to contain_exactly(personal_access_token)
end
end
+
+ describe 'revoke scopes' do
+ let_it_be(:revoked_token) { create(:personal_access_token, :revoked) }
+ let_it_be(:non_revoked_token) { create(:personal_access_token, revoked: false) }
+ let_it_be(:non_revoked_token2) { create(:personal_access_token, revoked: nil) }
+
+ describe '.revoked' do
+ it { expect(described_class.revoked).to contain_exactly(revoked_token) }
+ end
+
+ describe '.not_revoked' do
+ it { expect(described_class.not_revoked).to contain_exactly(non_revoked_token, non_revoked_token2) }
+ end
+ end
end
describe '.simple_sorts' do
- it 'includes overriden keys' do
+ it 'includes overridden keys' do
expect(described_class.simple_sorts.keys).to include(*%w(expires_at_asc expires_at_desc))
end
end
diff --git a/spec/models/personal_snippet_spec.rb b/spec/models/personal_snippet_spec.rb
index fb96d6e8bc3..10d70fed1ee 100644
--- a/spec/models/personal_snippet_spec.rb
+++ b/spec/models/personal_snippet_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PersonalSnippet do
+RSpec.describe PersonalSnippet do
describe '#embeddable?' do
[
{ snippet: :public, embeddable: true },
diff --git a/spec/models/plan_limits_spec.rb b/spec/models/plan_limits_spec.rb
index 1366f088623..831fd0dcbc3 100644
--- a/spec/models/plan_limits_spec.rb
+++ b/spec/models/plan_limits_spec.rb
@@ -2,57 +2,217 @@
require 'spec_helper'
-describe PlanLimits do
- let(:plan_limits) { create(:plan_limits) }
- let(:model) { ProjectHook }
- let(:count) { model.count }
+RSpec.describe PlanLimits do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:plan_limits) { create(:plan_limits) }
+ let(:project_hooks_count) { 2 }
before do
- create(:project_hook)
+ create_list(:project_hook, project_hooks_count, project: project)
end
- context 'without plan limits configured' do
- describe '#exceeded?' do
- it 'does not exceed any relation offset' do
- expect(plan_limits.exceeded?(:project_hooks, model)).to be false
- expect(plan_limits.exceeded?(:project_hooks, count)).to be false
+ describe '#exceeded?' do
+ let(:alternate_limit) { double('an alternate limit value') }
+
+ subject(:exceeded_limit) { plan_limits.exceeded?(:project_hooks, limit_subject, alternate_limit: alternate_limit) }
+
+ before do
+ allow(plan_limits).to receive(:limit_for).with(:project_hooks, alternate_limit: alternate_limit).and_return(limit)
+ end
+
+ shared_examples_for 'comparing limits' do
+ context 'when limit for given name results to a disabled value' do
+ let(:limit) { nil }
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when limit for given name results to a non-disabled value' do
+ context 'and given count is smaller than limit' do
+ let(:limit) { project_hooks_count + 1 }
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'and given count is equal to the limit' do
+ let(:limit) { project_hooks_count }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'and given count is greater than the limit' do
+ let(:limit) { project_hooks_count - 1 }
+
+ it { is_expected.to eq(true) }
+ end
+ end
+ end
+
+ context 'when given limit subject is an integer' do
+ let(:limit_subject) { project.hooks.count }
+
+ it_behaves_like 'comparing limits'
+ end
+
+ context 'when given limit subject is an ActiveRecord::Relation' do
+ let(:limit_subject) { project.hooks }
+
+ it_behaves_like 'comparing limits'
+ end
+
+ context 'when given limit subject is something else' do
+ let(:limit_subject) { ProjectHook }
+ let(:limit) { 100 }
+
+ it 'raises an error' do
+ expect { exceeded_limit }.to raise_error(ArgumentError)
end
end
end
- context 'with plan limits configured' do
- before do
- plan_limits.update!(project_hooks: 2)
+ describe '#limit_for' do
+ let(:alternate_limit) { nil }
+
+ subject(:limit) { plan_limits.limit_for(:project_hooks, alternate_limit: alternate_limit) }
+
+ context 'when given limit name does not exist' do
+ it 'raises an error' do
+ expect { plan_limits.limit_for(:project_foo) }.to raise_error(described_class::LimitUndefinedError)
+ end
end
- describe '#exceeded?' do
- it 'does not exceed the relation offset' do
- expect(plan_limits.exceeded?(:project_hooks, model)).to be false
- expect(plan_limits.exceeded?(:project_hooks, count)).to be false
+ context 'when given limit name is disabled' do
+ before do
+ plan_limits.update!(project_hooks: 0)
+ end
+
+ it { is_expected.to eq(nil) }
+
+ context 'and alternate_limit is a non-zero integer' do
+ let(:alternate_limit) { 1 }
+
+ it { is_expected.to eq(1) }
+ end
+
+ context 'and alternate_limit is zero' do
+ let(:alternate_limit) { 0 }
+
+ it { is_expected.to eq(nil) }
+ end
+
+ context 'and alternate_limit is a proc that returns non-zero integer' do
+ let(:alternate_limit) { -> { 1 } }
+
+ it { is_expected.to eq(1) }
+ end
+
+ context 'and alternate_limit is a proc that returns zero' do
+ let(:alternate_limit) { -> { 0 } }
+
+ it { is_expected.to eq(nil) }
+ end
+
+ context 'and alternate_limit is a proc that returns nil' do
+ let(:alternate_limit) { -> { nil } }
+
+ it { is_expected.to eq(nil) }
end
end
- context 'with boundary values' do
+ context 'when given limit name is enabled' do
+ let(:plan_limit_value) { 2 }
+
before do
- create(:project_hook)
+ plan_limits.update!(project_hooks: plan_limit_value)
end
- describe '#exceeded?' do
- it 'does exceed the relation offset' do
- expect(plan_limits.exceeded?(:project_hooks, model)).to be true
- expect(plan_limits.exceeded?(:project_hooks, count)).to be true
- end
+ context 'and alternate_limit is a non-zero integer that is bigger than the plan limit' do
+ let(:alternate_limit) { plan_limit_value + 1 }
+
+ it { is_expected.to eq(plan_limit_value) }
+ end
+
+ context 'and alternate_limit is a non-zero integer that is smaller than the plan limit' do
+ let(:alternate_limit) { plan_limit_value - 1 }
+
+ it { is_expected.to eq(alternate_limit) }
+ end
+
+ context 'and alternate_limit is zero' do
+ let(:alternate_limit) { 0 }
+
+ it { is_expected.to eq(plan_limit_value) }
+ end
+
+ context 'and alternate_limit is a proc that returns non-zero integer that is bigger than the plan limit' do
+ let(:alternate_limit) { -> { plan_limit_value + 1 } }
+
+ it { is_expected.to eq(plan_limit_value) }
+ end
+
+ context 'and alternate_limit is a proc that returns non-zero integer that is smaller than the plan limit' do
+ let(:alternate_limit) { -> { plan_limit_value - 1 } }
+
+ it { is_expected.to eq(alternate_limit.call) }
+ end
+
+ context 'and alternate_limit is a proc that returns zero' do
+ let(:alternate_limit) { -> { 0 } }
+
+ it { is_expected.to eq(plan_limit_value) }
+ end
+
+ context 'and alternate_limit is a proc that returns nil' do
+ let(:alternate_limit) { -> { nil } }
+
+ it { is_expected.to eq(plan_limit_value) }
end
end
end
context 'validates default values' do
+ # TODO: For now, these columns have default values set to 0.
+ # Each artifact type listed here have their own matching issues to determine
+ # the actual limit value. In each of those issues, the default value should also be updated to
+ # a non-zero value. Also update existing values of zero to whatever the default value will be.
+ # For a list of the issues, see: https://gitlab.com/gitlab-org/gitlab/-/issues/211378#note_355619970
+ let(:disabled_max_artifact_size_columns) do
+ %w[
+ ci_max_artifact_size_archive
+ ci_max_artifact_size_metadata
+ ci_max_artifact_size_trace
+ ci_max_artifact_size_junit
+ ci_max_artifact_size_sast
+ ci_max_artifact_size_dependency_scanning
+ ci_max_artifact_size_container_scanning
+ ci_max_artifact_size_dast
+ ci_max_artifact_size_codequality
+ ci_max_artifact_size_license_management
+ ci_max_artifact_size_license_scanning
+ ci_max_artifact_size_performance
+ ci_max_artifact_size_browser_performance
+ ci_max_artifact_size_load_performance
+ ci_max_artifact_size_metrics
+ ci_max_artifact_size_metrics_referee
+ ci_max_artifact_size_network_referee
+ ci_max_artifact_size_dotenv
+ ci_max_artifact_size_cobertura
+ ci_max_artifact_size_terraform
+ ci_max_artifact_size_accessibility
+ ci_max_artifact_size_cluster_applications
+ ci_max_artifact_size_secret_detection
+ ci_max_artifact_size_requirements
+ ci_max_artifact_size_coverage_fuzzing
+ ]
+ end
+
let(:columns_with_zero) do
%w[
ci_active_pipelines
ci_pipeline_size
ci_active_jobs
- ]
+ storage_size_limit
+ ] + disabled_max_artifact_size_columns
end
it "has positive values for enabled limits" do
diff --git a/spec/models/plan_spec.rb b/spec/models/plan_spec.rb
index 3f3b8046232..490c6b1bbf7 100644
--- a/spec/models/plan_spec.rb
+++ b/spec/models/plan_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Plan do
+RSpec.describe Plan do
describe '#default?' do
subject { plan.default? }
@@ -14,4 +14,16 @@ describe Plan do
end
end
end
+
+ context 'when updating plan limits' do
+ let(:plan) { described_class.default }
+
+ it { expect(plan).to be_persisted }
+
+ it { expect(plan.actual_limits).not_to be_persisted }
+
+ it 'successfully updates the limits' do
+ expect(plan.actual_limits.update!(ci_instance_level_variables: 100)).to be_truthy
+ end
+ end
end
diff --git a/spec/models/pool_repository_spec.rb b/spec/models/pool_repository_spec.rb
index ae00f9df89e..92b3e41cd18 100644
--- a/spec/models/pool_repository_spec.rb
+++ b/spec/models/pool_repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PoolRepository do
+RSpec.describe PoolRepository do
describe 'associations' do
it { is_expected.to belong_to(:shard) }
it { is_expected.to belong_to(:source_project) }
diff --git a/spec/models/postgresql/replication_slot_spec.rb b/spec/models/postgresql/replication_slot_spec.rb
index d435fccc09a..02a4d783b84 100644
--- a/spec/models/postgresql/replication_slot_spec.rb
+++ b/spec/models/postgresql/replication_slot_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Postgresql::ReplicationSlot do
+RSpec.describe Postgresql::ReplicationSlot do
describe '.in_use?' do
it 'returns true when replication slots are present' do
expect(described_class).to receive(:exists?).and_return(true)
diff --git a/spec/models/product_analytics_event_spec.rb b/spec/models/product_analytics_event_spec.rb
new file mode 100644
index 00000000000..6058df9fa13
--- /dev/null
+++ b/spec/models/product_analytics_event_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe ProductAnalyticsEvent, type: :model do
+ it { is_expected.to belong_to(:project) }
+ it { expect(described_class).to respond_to(:order_by_time) }
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:project_id) }
+ it { is_expected.to validate_presence_of(:event_id) }
+ it { is_expected.to validate_presence_of(:v_collector) }
+ it { is_expected.to validate_presence_of(:v_etl) }
+ end
+
+ describe '.timerange' do
+ let_it_be(:event_1) { create(:product_analytics_event, collector_tstamp: Time.zone.now - 1.day) }
+ let_it_be(:event_2) { create(:product_analytics_event, collector_tstamp: Time.zone.now - 5.days) }
+ let_it_be(:event_3) { create(:product_analytics_event, collector_tstamp: Time.zone.now - 15.days) }
+
+ it { expect(described_class.timerange(3.days)).to match_array([event_1]) }
+ it { expect(described_class.timerange(7.days)).to match_array([event_1, event_2]) }
+ it { expect(described_class.timerange(30.days)).to match_array([event_1, event_2, event_3]) }
+ end
+end
diff --git a/spec/models/programming_language_spec.rb b/spec/models/programming_language_spec.rb
index b327d360461..f2201eabd1c 100644
--- a/spec/models/programming_language_spec.rb
+++ b/spec/models/programming_language_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProgrammingLanguage do
+RSpec.describe ProgrammingLanguage do
it { is_expected.to respond_to(:name) }
it { is_expected.to respond_to(:color) }
diff --git a/spec/models/project_authorization_spec.rb b/spec/models/project_authorization_spec.rb
index 6f06fe4e55a..c517fc8be55 100644
--- a/spec/models/project_authorization_spec.rb
+++ b/spec/models/project_authorization_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectAuthorization do
+RSpec.describe ProjectAuthorization do
let(:user) { create(:user) }
let(:project1) { create(:project) }
let(:project2) { create(:project) }
diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb
index 5af25ac1437..8313879114f 100644
--- a/spec/models/project_auto_devops_spec.rb
+++ b/spec/models/project_auto_devops_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectAutoDevops do
+RSpec.describe ProjectAutoDevops do
let_it_be(:project) { build(:project) }
it_behaves_like 'having unique enum values'
diff --git a/spec/models/project_ci_cd_setting_spec.rb b/spec/models/project_ci_cd_setting_spec.rb
index ecca371ce4e..698465e854a 100644
--- a/spec/models/project_ci_cd_setting_spec.rb
+++ b/spec/models/project_ci_cd_setting_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectCiCdSetting do
+RSpec.describe ProjectCiCdSetting do
describe 'validations' do
it 'validates default_git_depth is between 0 and 1000 or nil' do
expect(subject).to validate_numericality_of(:default_git_depth)
diff --git a/spec/models/project_custom_attribute_spec.rb b/spec/models/project_custom_attribute_spec.rb
index 80638676b49..25ee1e60819 100644
--- a/spec/models/project_custom_attribute_spec.rb
+++ b/spec/models/project_custom_attribute_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectCustomAttribute do
+RSpec.describe ProjectCustomAttribute do
describe 'assocations' do
it { is_expected.to belong_to(:project) }
end
diff --git a/spec/models/project_daily_statistic_spec.rb b/spec/models/project_daily_statistic_spec.rb
index 86210af15d8..8dbabdb3829 100644
--- a/spec/models/project_daily_statistic_spec.rb
+++ b/spec/models/project_daily_statistic_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe ProjectDailyStatistic do
+RSpec.describe ProjectDailyStatistic do
it { is_expected.to belong_to(:project) }
end
diff --git a/spec/models/project_export_job_spec.rb b/spec/models/project_export_job_spec.rb
index dc39d0e401d..5a2b1443f8b 100644
--- a/spec/models/project_export_job_spec.rb
+++ b/spec/models/project_export_job_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectExportJob, type: :model do
+RSpec.describe ProjectExportJob, type: :model do
let(:project) { create(:project) }
let!(:job1) { create(:project_export_job, project: project, status: 0) }
let!(:job2) { create(:project_export_job, project: project, status: 2) }
diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb
index e33ea75bc5d..c927c8fd1f9 100644
--- a/spec/models/project_feature_spec.rb
+++ b/spec/models/project_feature_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectFeature do
+RSpec.describe ProjectFeature do
using RSpec::Parameterized::TableSyntax
let(:project) { create(:project) }
diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb
index 8ef29e8a876..c925d87170c 100644
--- a/spec/models/project_group_link_spec.rb
+++ b/spec/models/project_group_link_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectGroupLink do
+RSpec.describe ProjectGroupLink do
describe "Associations" do
it { is_expected.to belong_to(:group) }
it { is_expected.to belong_to(:project) }
diff --git a/spec/models/project_import_data_spec.rb b/spec/models/project_import_data_spec.rb
index fe47811f074..50a2ee42084 100644
--- a/spec/models/project_import_data_spec.rb
+++ b/spec/models/project_import_data_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectImportData do
+RSpec.describe ProjectImportData do
describe '#merge_data' do
it 'writes the Hash to the attribute if it is nil' do
row = described_class.new
diff --git a/spec/models/project_import_state_spec.rb b/spec/models/project_import_state_spec.rb
index f3b83c036b5..6a0402d43a8 100644
--- a/spec/models/project_import_state_spec.rb
+++ b/spec/models/project_import_state_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectImportState, type: :model do
+RSpec.describe ProjectImportState, type: :model do
let_it_be(:correlation_id) { 'cid' }
let_it_be(:import_state, refind: true) { create(:import_state, correlation_id_value: correlation_id) }
diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb
index 330aab9f856..f451c2905e6 100644
--- a/spec/models/project_label_spec.rb
+++ b/spec/models/project_label_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectLabel do
+RSpec.describe ProjectLabel do
describe 'relationships' do
it { is_expected.to belong_to(:project) }
end
diff --git a/spec/models/project_metrics_setting_spec.rb b/spec/models/project_metrics_setting_spec.rb
index adfbbbc3a45..6639f9cb208 100644
--- a/spec/models/project_metrics_setting_spec.rb
+++ b/spec/models/project_metrics_setting_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectMetricsSetting do
+RSpec.describe ProjectMetricsSetting do
describe 'Associations' do
it { is_expected.to belong_to(:project) }
end
diff --git a/spec/models/project_repository_spec.rb b/spec/models/project_repository_spec.rb
index c966447fedc..6852ca0097d 100644
--- a/spec/models/project_repository_spec.rb
+++ b/spec/models/project_repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectRepository do
+RSpec.describe ProjectRepository do
describe 'associations' do
it { is_expected.to belong_to(:shard) }
it { is_expected.to belong_to(:project) }
diff --git a/spec/models/project_services/alerts_service_spec.rb b/spec/models/project_services/alerts_service_spec.rb
index 4e63ece26d8..db25885c76a 100644
--- a/spec/models/project_services/alerts_service_spec.rb
+++ b/spec/models/project_services/alerts_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AlertsService do
+RSpec.describe AlertsService do
let_it_be(:project) { create(:project) }
let(:service_params) { { project: project, active: active } }
let(:active) { true }
diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb
index 8b6f2888c0a..7a6fe4b1537 100644
--- a/spec/models/project_services/asana_service_spec.rb
+++ b/spec/models/project_services/asana_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AsanaService do
+RSpec.describe AsanaService do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
diff --git a/spec/models/project_services/assembla_service_spec.rb b/spec/models/project_services/assembla_service_spec.rb
index 2c86c0ec7be..207add6f090 100644
--- a/spec/models/project_services/assembla_service_spec.rb
+++ b/spec/models/project_services/assembla_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AssemblaService do
+RSpec.describe AssemblaService do
include StubRequests
describe "Associations" do
diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb
index c1efa3a4348..4d2474cc56a 100644
--- a/spec/models/project_services/bamboo_service_spec.rb
+++ b/spec/models/project_services/bamboo_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BambooService, :use_clean_rails_memory_store_caching do
+RSpec.describe BambooService, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
include StubRequests
diff --git a/spec/models/project_services/bugzilla_service_spec.rb b/spec/models/project_services/bugzilla_service_spec.rb
index ab939e0d2f8..560c7c3ee83 100644
--- a/spec/models/project_services/bugzilla_service_spec.rb
+++ b/spec/models/project_services/bugzilla_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BugzillaService do
+RSpec.describe BugzillaService do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -32,49 +32,4 @@ describe BugzillaService do
it { is_expected.not_to validate_presence_of(:new_issue_url) }
end
end
-
- context 'overriding properties' do
- let(:url) { 'http://bugzilla.example.com' }
- let(:access_params) do
- { project_url: url, issues_url: url, new_issue_url: url }
- end
-
- # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
- context 'when data are stored in properties' do
- let(:properties) { access_params.merge(title: title, description: description) }
- let(:service) do
- create(:bugzilla_service, :without_properties_callback, properties: properties)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when data are stored in separated fields' do
- let(:service) do
- create(:bugzilla_service, title: title, description: description, properties: access_params)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when data are stored in both properties and separated fields' do
- let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') }
- let(:service) do
- create(:bugzilla_service, :without_properties_callback, title: title, description: description, properties: properties)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when no title & description are set' do
- let(:service) do
- create(:bugzilla_service, properties: access_params)
- end
-
- it 'returns default values' do
- expect(service.title).to eq('Bugzilla')
- expect(service.description).to eq('Bugzilla issue tracker')
- end
- end
- end
end
diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb
index 8b6fa36eaa5..ff717a59e7b 100644
--- a/spec/models/project_services/buildkite_service_spec.rb
+++ b/spec/models/project_services/buildkite_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BuildkiteService, :use_clean_rails_memory_store_caching do
+RSpec.describe BuildkiteService, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
include StubRequests
diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb
index 0d3dd89e93b..ea3990b339b 100644
--- a/spec/models/project_services/campfire_service_spec.rb
+++ b/spec/models/project_services/campfire_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CampfireService do
+RSpec.describe CampfireService do
include StubRequests
describe 'Associations' do
diff --git a/spec/models/project_services/chat_message/alert_message_spec.rb b/spec/models/project_services/chat_message/alert_message_spec.rb
index a1dd332c005..927c5dffe77 100644
--- a/spec/models/project_services/chat_message/alert_message_spec.rb
+++ b/spec/models/project_services/chat_message/alert_message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatMessage::AlertMessage do
+RSpec.describe ChatMessage::AlertMessage do
subject { described_class.new(args) }
let_it_be(:start_time) { Time.current }
diff --git a/spec/models/project_services/chat_message/base_message_spec.rb b/spec/models/project_services/chat_message/base_message_spec.rb
index 8f80cf0b074..a7ddf230758 100644
--- a/spec/models/project_services/chat_message/base_message_spec.rb
+++ b/spec/models/project_services/chat_message/base_message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatMessage::BaseMessage do
+RSpec.describe ChatMessage::BaseMessage do
let(:base_message) { described_class.new(args) }
let(:args) { { project_url: 'https://gitlab-domain.com' } }
diff --git a/spec/models/project_services/chat_message/deployment_message_spec.rb b/spec/models/project_services/chat_message/deployment_message_spec.rb
index 42c1689db3d..9c361f90ae0 100644
--- a/spec/models/project_services/chat_message/deployment_message_spec.rb
+++ b/spec/models/project_services/chat_message/deployment_message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatMessage::DeploymentMessage do
+RSpec.describe ChatMessage::DeploymentMessage do
describe '#pretext' do
it 'returns a message with the data returned by the deployment data builder' do
environment = create(:environment, name: "myenvironment")
diff --git a/spec/models/project_services/chat_message/issue_message_spec.rb b/spec/models/project_services/chat_message/issue_message_spec.rb
index c4d10be8331..051f4780ba4 100644
--- a/spec/models/project_services/chat_message/issue_message_spec.rb
+++ b/spec/models/project_services/chat_message/issue_message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatMessage::IssueMessage do
+RSpec.describe ChatMessage::IssueMessage do
subject { described_class.new(args) }
let(:args) do
diff --git a/spec/models/project_services/chat_message/merge_message_spec.rb b/spec/models/project_services/chat_message/merge_message_spec.rb
index 6063ef4ecb3..45be5212508 100644
--- a/spec/models/project_services/chat_message/merge_message_spec.rb
+++ b/spec/models/project_services/chat_message/merge_message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatMessage::MergeMessage do
+RSpec.describe ChatMessage::MergeMessage do
subject { described_class.new(args) }
let(:args) do
diff --git a/spec/models/project_services/chat_message/note_message_spec.rb b/spec/models/project_services/chat_message/note_message_spec.rb
index 5e7987dc0f6..6a741365d55 100644
--- a/spec/models/project_services/chat_message/note_message_spec.rb
+++ b/spec/models/project_services/chat_message/note_message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatMessage::NoteMessage do
+RSpec.describe ChatMessage::NoteMessage do
subject { described_class.new(args) }
let(:color) { '#345' }
diff --git a/spec/models/project_services/chat_message/pipeline_message_spec.rb b/spec/models/project_services/chat_message/pipeline_message_spec.rb
index a7171577063..4eb2f57315b 100644
--- a/spec/models/project_services/chat_message/pipeline_message_spec.rb
+++ b/spec/models/project_services/chat_message/pipeline_message_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe ChatMessage::PipelineMessage do
+RSpec.describe ChatMessage::PipelineMessage do
subject { described_class.new(args) }
let(:args) do
diff --git a/spec/models/project_services/chat_message/push_message_spec.rb b/spec/models/project_services/chat_message/push_message_spec.rb
index 9d990508ab2..e3ba4c2aefe 100644
--- a/spec/models/project_services/chat_message/push_message_spec.rb
+++ b/spec/models/project_services/chat_message/push_message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatMessage::PushMessage do
+RSpec.describe ChatMessage::PushMessage do
subject { described_class.new(args) }
let(:args) do
diff --git a/spec/models/project_services/chat_message/wiki_page_message_spec.rb b/spec/models/project_services/chat_message/wiki_page_message_spec.rb
index 1346a43335e..04c9e5934be 100644
--- a/spec/models/project_services/chat_message/wiki_page_message_spec.rb
+++ b/spec/models/project_services/chat_message/wiki_page_message_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatMessage::WikiPageMessage do
+RSpec.describe ChatMessage::WikiPageMessage do
subject { described_class.new(args) }
let(:args) do
diff --git a/spec/models/project_services/chat_notification_service_spec.rb b/spec/models/project_services/chat_notification_service_spec.rb
index 1caec5c6eb7..77a1377c138 100644
--- a/spec/models/project_services/chat_notification_service_spec.rb
+++ b/spec/models/project_services/chat_notification_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatNotificationService do
+RSpec.describe ChatNotificationService do
describe 'Associations' do
before do
allow(subject).to receive(:activated?).and_return(true)
diff --git a/spec/models/project_services/confluence_service_spec.rb b/spec/models/project_services/confluence_service_spec.rb
new file mode 100644
index 00000000000..5d153b17070
--- /dev/null
+++ b/spec/models/project_services/confluence_service_spec.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ConfluenceService do
+ describe 'Associations' do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ before do
+ subject.active = active
+ end
+
+ context 'when service is active' do
+ let(:active) { true }
+
+ it { is_expected.not_to allow_value('https://example.com').for(:confluence_url) }
+ it { is_expected.not_to allow_value('example.com').for(:confluence_url) }
+ it { is_expected.not_to allow_value('foo').for(:confluence_url) }
+ it { is_expected.not_to allow_value('ftp://example.atlassian.net/wiki').for(:confluence_url) }
+ it { is_expected.not_to allow_value('https://example.atlassian.net').for(:confluence_url) }
+ it { is_expected.not_to allow_value('https://.atlassian.net/wiki').for(:confluence_url) }
+ it { is_expected.not_to allow_value('https://example.atlassian.net/wikifoo').for(:confluence_url) }
+ it { is_expected.not_to allow_value('').for(:confluence_url) }
+ it { is_expected.not_to allow_value(nil).for(:confluence_url) }
+ it { is_expected.not_to allow_value('😊').for(:confluence_url) }
+ it { is_expected.to allow_value('https://example.atlassian.net/wiki').for(:confluence_url) }
+ it { is_expected.to allow_value('http://example.atlassian.net/wiki').for(:confluence_url) }
+ it { is_expected.to allow_value('https://example.atlassian.net/wiki/').for(:confluence_url) }
+ it { is_expected.to allow_value('http://example.atlassian.net/wiki/').for(:confluence_url) }
+ it { is_expected.to allow_value('https://example.atlassian.net/wiki/foo').for(:confluence_url) }
+
+ it { is_expected.to validate_presence_of(:confluence_url) }
+ end
+
+ context 'when service is inactive' do
+ let(:active) { false }
+
+ it { is_expected.not_to validate_presence_of(:confluence_url) }
+ it { is_expected.to allow_value('foo').for(:confluence_url) }
+ end
+ end
+
+ describe '#detailed_description' do
+ it 'can correctly return a link to the project wiki when active' do
+ project = create(:project)
+ subject.project = project
+ subject.active = true
+
+ expect(subject.detailed_description).to include(Gitlab::Routing.url_helpers.project_wikis_url(project))
+ end
+
+ context 'when the project wiki is not enabled' do
+ it 'returns nil when both active or inactive', :aggregate_failures do
+ project = create(:project, :wiki_disabled)
+ subject.project = project
+
+ [true, false].each do |active|
+ subject.active = active
+
+ expect(subject.detailed_description).to be_nil
+ end
+ end
+ end
+ end
+
+ describe 'Caching has_confluence on project_settings' do
+ let(:project) { create(:project) }
+
+ subject { project.project_setting.has_confluence? }
+
+ it 'sets the property to true when service is active' do
+ create(:confluence_service, project: project, active: true)
+
+ is_expected.to be(true)
+ end
+
+ it 'sets the property to false when service is not active' do
+ create(:confluence_service, project: project, active: false)
+
+ is_expected.to be(false)
+ end
+
+ it 'creates a project_setting record if one was not already created' do
+ expect { create(:confluence_service) }.to change { ProjectSetting.count }.by(1)
+ end
+ end
+end
diff --git a/spec/models/project_services/custom_issue_tracker_service_spec.rb b/spec/models/project_services/custom_issue_tracker_service_spec.rb
index e749ea6eacc..881ae60a680 100644
--- a/spec/models/project_services/custom_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/custom_issue_tracker_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CustomIssueTrackerService do
+RSpec.describe CustomIssueTrackerService do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -31,66 +31,5 @@ describe CustomIssueTrackerService do
it { is_expected.not_to validate_presence_of(:issues_url) }
it { is_expected.not_to validate_presence_of(:new_issue_url) }
end
-
- context 'title' do
- let(:issue_tracker) { described_class.new(properties: {}) }
-
- it 'sets a default title' do
- issue_tracker.title = nil
-
- expect(issue_tracker.title).to eq('Custom Issue Tracker')
- end
-
- it 'sets the custom title' do
- issue_tracker.title = 'test title'
-
- expect(issue_tracker.title).to eq('test title')
- end
- end
- end
-
- context 'overriding properties' do
- let(:url) { 'http://custom.example.com' }
- let(:access_params) do
- { project_url: url, issues_url: url, new_issue_url: url }
- end
-
- # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
- context 'when data are stored in properties' do
- let(:properties) { access_params.merge(title: title, description: description) }
- let(:service) do
- create(:custom_issue_tracker_service, :without_properties_callback, properties: properties)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when data are stored in separated fields' do
- let(:service) do
- create(:custom_issue_tracker_service, title: title, description: description, properties: access_params)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when data are stored in both properties and separated fields' do
- let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') }
- let(:service) do
- create(:custom_issue_tracker_service, :without_properties_callback, title: title, description: description, properties: properties)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when no title & description are set' do
- let(:service) do
- create(:custom_issue_tracker_service, properties: access_params)
- end
-
- it 'returns default values' do
- expect(service.title).to eq('Custom Issue Tracker')
- expect(service.description).to eq('Custom issue tracker')
- end
- end
end
end
diff --git a/spec/models/project_services/data_fields_spec.rb b/spec/models/project_services/data_fields_spec.rb
index 6b388a7222b..9a3042f9f8d 100644
--- a/spec/models/project_services/data_fields_spec.rb
+++ b/spec/models/project_services/data_fields_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DataFields do
+RSpec.describe DataFields do
let(:url) { 'http://url.com' }
let(:username) { 'username_one' }
let(:properties) do
diff --git a/spec/models/project_services/discord_service_spec.rb b/spec/models/project_services/discord_service_spec.rb
index b5a54676dd7..d4bd08ddeb6 100644
--- a/spec/models/project_services/discord_service_spec.rb
+++ b/spec/models/project_services/discord_service_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe DiscordService do
+RSpec.describe DiscordService do
it_behaves_like "chat service", "Discord notifications" do
let(:client) { Discordrb::Webhooks::Client }
let(:client_arguments) { { url: webhook_url } }
diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb
index 1ee9c5f90c6..9aaf4f7a644 100644
--- a/spec/models/project_services/drone_ci_service_spec.rb
+++ b/spec/models/project_services/drone_ci_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DroneCiService, :use_clean_rails_memory_store_caching do
+RSpec.describe DroneCiService, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
describe 'associations' do
diff --git a/spec/models/project_services/emails_on_push_service_spec.rb b/spec/models/project_services/emails_on_push_service_spec.rb
index 44db95afc57..6954a72f9c1 100644
--- a/spec/models/project_services/emails_on_push_service_spec.rb
+++ b/spec/models/project_services/emails_on_push_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EmailsOnPushService do
+RSpec.describe EmailsOnPushService do
describe 'Validations' do
context 'when service is active' do
before do
diff --git a/spec/models/project_services/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb
index f8d88a944a5..c6891401a0f 100644
--- a/spec/models/project_services/external_wiki_service_spec.rb
+++ b/spec/models/project_services/external_wiki_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExternalWikiService do
+RSpec.describe ExternalWikiService do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb
index c1ebe69ee66..94a49fb3080 100644
--- a/spec/models/project_services/flowdock_service_spec.rb
+++ b/spec/models/project_services/flowdock_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe FlowdockService do
+RSpec.describe FlowdockService do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
index 7f1c6224b7d..a6b7cb05836 100644
--- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabIssueTrackerService do
+RSpec.describe GitlabIssueTrackerService do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -51,49 +51,4 @@ describe GitlabIssueTrackerService do
end
end
end
-
- context 'overriding properties' do
- let(:url) { 'http://gitlab.example.com' }
- let(:access_params) do
- { project_url: url, issues_url: url, new_issue_url: url }
- end
-
- # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
- context 'when data are stored in properties' do
- let(:properties) { access_params.merge(title: title, description: description) }
- let(:service) do
- create(:gitlab_issue_tracker_service, :without_properties_callback, properties: properties)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when data are stored in separated fields' do
- let(:service) do
- create(:gitlab_issue_tracker_service, title: title, description: description, properties: access_params)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when data are stored in both properties and separated fields' do
- let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') }
- let(:service) do
- create(:gitlab_issue_tracker_service, :without_properties_callback, title: title, description: description, properties: properties)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when no title & description are set' do
- let(:service) do
- create(:gitlab_issue_tracker_service, properties: access_params)
- end
-
- it 'returns default values' do
- expect(service.title).to eq('GitLab')
- expect(service.description).to eq('GitLab issue tracker')
- end
- end
- end
end
diff --git a/spec/models/project_services/hangouts_chat_service_spec.rb b/spec/models/project_services/hangouts_chat_service_spec.rb
index 0505ac9b49c..042e32439d1 100644
--- a/spec/models/project_services/hangouts_chat_service_spec.rb
+++ b/spec/models/project_services/hangouts_chat_service_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe HangoutsChatService do
+RSpec.describe HangoutsChatService do
it_behaves_like "chat service", "Hangouts Chat" do
let(:client) { HangoutsChat::Sender }
let(:client_arguments) { webhook_url }
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index c25edf81352..667e6cc85ab 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe HipchatService do
+RSpec.describe HipchatService do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb
index 88a93eef214..07963947de8 100644
--- a/spec/models/project_services/irker_service_spec.rb
+++ b/spec/models/project_services/irker_service_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require 'socket'
require 'json'
-describe IrkerService do
+RSpec.describe IrkerService do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
diff --git a/spec/models/project_services/issue_tracker_data_spec.rb b/spec/models/project_services/issue_tracker_data_spec.rb
index db617cf0abb..3ddb7d9250f 100644
--- a/spec/models/project_services/issue_tracker_data_spec.rb
+++ b/spec/models/project_services/issue_tracker_data_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssueTrackerData do
+RSpec.describe IssueTrackerData do
let(:service) { create(:custom_issue_tracker_service, active: false, properties: {}) }
describe 'Associations' do
diff --git a/spec/models/project_services/issue_tracker_service_spec.rb b/spec/models/project_services/issue_tracker_service_spec.rb
index f1cdee5c4a3..5b12c7330b8 100644
--- a/spec/models/project_services/issue_tracker_service_spec.rb
+++ b/spec/models/project_services/issue_tracker_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssueTrackerService do
+RSpec.describe IssueTrackerService do
describe 'Validations' do
let(:project) { create :project }
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 20e85f0fd4b..cfc2c920cd2 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -2,11 +2,9 @@
require 'spec_helper'
-describe JiraService do
+RSpec.describe JiraService do
include AssetsHelpers
- let(:title) { 'custom title' }
- let(:description) { 'custom description' }
let(:url) { 'http://jira.example.com' }
let(:api_url) { 'http://api-jira.example.com' }
let(:username) { 'jira-username' }
@@ -93,7 +91,6 @@ describe JiraService do
let(:params) do
{
project: create(:project),
- title: 'custom title', description: 'custom description',
url: url, api_url: api_url,
username: username, password: password,
jira_issue_transition_id: transition_id
@@ -106,19 +103,6 @@ describe JiraService do
expect(subject.properties).to be_nil
end
- it 'sets title correctly' do
- service = subject
-
- expect(service.title).to eq('custom title')
- end
-
- it 'sets service data correctly' do
- service = subject
-
- expect(service.title).to eq('custom title')
- expect(service.description).to eq('custom description')
- end
-
it 'stores data in data_fields correcty' do
service = subject
@@ -209,7 +193,6 @@ describe JiraService do
end
it 'does not reset password if url "changed" to the same url as before' do
- service.title = 'aaaaaa'
service.url = 'http://jira.example.com'
service.save
@@ -318,46 +301,32 @@ describe JiraService do
# this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
context 'when data are stored in properties' do
- let(:properties) { data_params.merge(title: title, description: description) }
+ let(:properties) { data_params }
let!(:service) do
create(:jira_service, :without_properties_callback, properties: properties.merge(additional: 'something'))
end
- it_behaves_like 'issue tracker fields'
it_behaves_like 'handles jira fields'
end
context 'when data are stored in separated fields' do
let(:service) do
- create(:jira_service, data_params.merge(properties: {}, title: title, description: description))
+ create(:jira_service, data_params.merge(properties: {}))
end
- it_behaves_like 'issue tracker fields'
it_behaves_like 'handles jira fields'
end
context 'when data are stored in both properties and separated fields' do
- let(:properties) { data_params.merge(title: title, description: description) }
+ let(:properties) { data_params }
let(:service) do
create(:jira_service, :without_properties_callback, active: false, properties: properties).tap do |service|
create(:jira_tracker_data, data_params.merge(service: service))
end
end
- it_behaves_like 'issue tracker fields'
it_behaves_like 'handles jira fields'
end
-
- context 'when no title & description are set' do
- let(:service) do
- create(:jira_service, properties: access_params)
- end
-
- it 'returns default values' do
- expect(service.title).to eq('Jira')
- expect(service.description).to eq(s_('JiraService|Jira issue tracker'))
- end
- end
end
describe '#close_issue' do
@@ -704,59 +673,6 @@ describe JiraService do
end
end
- describe 'description and title' do
- let(:title) { 'Jira One' }
- let(:description) { 'Jira One issue tracker' }
- let(:properties) do
- {
- url: 'http://jira.example.com/web',
- username: 'mic',
- password: 'password',
- title: title,
- description: description
- }
- end
-
- context 'when it is not set' do
- it 'default values are returned' do
- service = create(:jira_service)
-
- expect(service.title).to eq('Jira')
- expect(service.description).to eq(s_('JiraService|Jira issue tracker'))
- end
- end
-
- context 'when it is set in properties' do
- it 'values from properties are returned' do
- service = create(:jira_service, :without_properties_callback, properties: properties)
-
- expect(service.title).to eq(title)
- expect(service.description).to eq(description)
- end
- end
-
- context 'when it is in title & description fields' do
- it 'values from title and description fields are returned' do
- service = create(:jira_service, title: title, description: description)
-
- expect(service.title).to eq(title)
- expect(service.description).to eq(description)
- end
- end
-
- context 'when it is in both properites & title & description fields' do
- it 'values from title and description fields are returned' do
- title2 = 'Jira 2'
- description2 = 'Jira description 2'
-
- service = create(:jira_service, title: title2, description: description2, properties: properties)
-
- expect(service.title).to eq(title2)
- expect(service.description).to eq(description2)
- end
- end
- end
-
describe 'project and issue urls' do
context 'when gitlab.yml was initialized' do
it 'is prepopulated with the settings' do
@@ -808,7 +724,7 @@ describe JiraService do
describe '#new_issue_url' do
it 'handles trailing slashes' do
- expect(service.new_issue_url).to eq('http://jira.test.com/path/secure/CreateIssue.jspa')
+ expect(service.new_issue_url).to eq('http://jira.test.com/path/secure/CreateIssue!default.jspa')
end
end
end
diff --git a/spec/models/project_services/jira_tracker_data_spec.rb b/spec/models/project_services/jira_tracker_data_spec.rb
index 12f6b99e8a7..9e38bced46c 100644
--- a/spec/models/project_services/jira_tracker_data_spec.rb
+++ b/spec/models/project_services/jira_tracker_data_spec.rb
@@ -2,8 +2,8 @@
require 'spec_helper'
-describe JiraTrackerData do
- let(:service) { create(:jira_service, active: false) }
+RSpec.describe JiraTrackerData do
+ let(:service) { build(:jira_service) }
describe 'Associations' do
it { is_expected.to belong_to(:service) }
diff --git a/spec/models/project_services/mattermost_service_spec.rb b/spec/models/project_services/mattermost_service_spec.rb
index 5b974985706..af1944ea77d 100644
--- a/spec/models/project_services/mattermost_service_spec.rb
+++ b/spec/models/project_services/mattermost_service_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe MattermostService do
+RSpec.describe MattermostService do
it_behaves_like "slack or mattermost notifications", "Mattermost"
end
diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
index 836181929e3..4fff3bc56cc 100644
--- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb
+++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MattermostSlashCommandsService do
+RSpec.describe MattermostSlashCommandsService do
it_behaves_like "chat slash commands service"
context 'Mattermost API' do
diff --git a/spec/models/project_services/microsoft_teams_service_spec.rb b/spec/models/project_services/microsoft_teams_service_spec.rb
index 425599c73d4..610feb52827 100644
--- a/spec/models/project_services/microsoft_teams_service_spec.rb
+++ b/spec/models/project_services/microsoft_teams_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MicrosoftTeamsService do
+RSpec.describe MicrosoftTeamsService do
let(:chat_service) { described_class.new }
let(:webhook_url) { 'https://example.gitlab.com/' }
diff --git a/spec/models/project_services/open_project_service_spec.rb b/spec/models/project_services/open_project_service_spec.rb
index 8e373a31e62..1abaab0ceff 100644
--- a/spec/models/project_services/open_project_service_spec.rb
+++ b/spec/models/project_services/open_project_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe OpenProjectService do
+RSpec.describe OpenProjectService do
describe 'Validations' do
context 'when service is active' do
before do
diff --git a/spec/models/project_services/open_project_tracker_data_spec.rb b/spec/models/project_services/open_project_tracker_data_spec.rb
index 0d387bbf69b..e6a3963ba87 100644
--- a/spec/models/project_services/open_project_tracker_data_spec.rb
+++ b/spec/models/project_services/open_project_tracker_data_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe OpenProjectTrackerData do
+RSpec.describe OpenProjectTrackerData do
describe 'Associations' do
it { is_expected.to belong_to(:service) }
end
diff --git a/spec/models/project_services/packagist_service_spec.rb b/spec/models/project_services/packagist_service_spec.rb
index 53f18a1bdd9..f710385b6e2 100644
--- a/spec/models/project_services/packagist_service_spec.rb
+++ b/spec/models/project_services/packagist_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PackagistService do
+RSpec.describe PackagistService do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
diff --git a/spec/models/project_services/pipelines_email_service_spec.rb b/spec/models/project_services/pipelines_email_service_spec.rb
index de1edf2099a..9a8386c619e 100644
--- a/spec/models/project_services/pipelines_email_service_spec.rb
+++ b/spec/models/project_services/pipelines_email_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelinesEmailService, :mailer do
+RSpec.describe PipelinesEmailService, :mailer do
let(:pipeline) do
create(:ci_pipeline, :failed,
project: project,
diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb
index dde46c82df6..8de85cc7fa5 100644
--- a/spec/models/project_services/pivotaltracker_service_spec.rb
+++ b/spec/models/project_services/pivotaltracker_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PivotaltrackerService do
+RSpec.describe PivotaltrackerService do
include StubRequests
describe 'Associations' do
diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb
index db3cbe23ad3..16837e2b93a 100644
--- a/spec/models/project_services/prometheus_service_spec.rb
+++ b/spec/models/project_services/prometheus_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PrometheusService, :use_clean_rails_memory_store_caching do
+RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching do
include PrometheusHelpers
include ReactiveCachingHelpers
@@ -23,7 +23,7 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
# result = { success: false, result: error }
expect(result[:success]).to be_falsy
- expect(result[:result]).to be_instance_of(Gitlab::PrometheusClient::Error)
+ expect(result[:result]).to be_instance_of(Gitlab::PrometheusClient::UnexpectedResponseError)
expect(redirect_req_stub).to have_been_requested
expect(redirected_req_stub).not_to have_been_requested
@@ -262,8 +262,6 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
service.google_iap_audience_client_id = "IAP_CLIENT_ID.apps.googleusercontent.com"
stub_request(:post, "https://oauth2.googleapis.com/token").to_return(status: 200, body: '{"id_token": "FOO"}', headers: { 'Content-Type': 'application/json; charset=UTF-8' })
-
- stub_feature_flags(prometheus_service_iap_auth: true)
end
it 'includes the authorization header' do
@@ -474,11 +472,7 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
title: 'API URL',
placeholder: s_('PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/'),
required: true
- }
- ]
- end
- let(:feature_flagged_fields) do
- [
+ },
{
type: 'text',
name: 'google_iap_audience_client_id',
@@ -498,13 +492,7 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
end
it 'returns fields' do
- stub_feature_flags(prometheus_service_iap_auth: false)
expect(service.fields).to eq(expected_fields)
end
-
- it 'returns fields with feature flag on' do
- stub_feature_flags(prometheus_service_iap_auth: true)
- expect(service.fields).to eq(expected_fields + feature_flagged_fields)
- end
end
end
diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb
index 380f02739bc..b7d3b8987b8 100644
--- a/spec/models/project_services/pushover_service_spec.rb
+++ b/spec/models/project_services/pushover_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PushoverService do
+RSpec.describe PushoverService do
include StubRequests
describe 'Associations' do
diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb
index 6220d7b1fac..b9be3940d34 100644
--- a/spec/models/project_services/redmine_service_spec.rb
+++ b/spec/models/project_services/redmine_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RedmineService do
+RSpec.describe RedmineService do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -50,49 +50,4 @@ describe RedmineService do
expect(described_class.reference_pattern.match('#123')[:issue]).to eq('123')
end
end
-
- context 'overriding properties' do
- let(:url) { 'http://redmine.example.com' }
- let(:access_params) do
- { project_url: url, issues_url: url, new_issue_url: url }
- end
-
- # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
- context 'when data are stored in properties' do
- let(:properties) { access_params.merge(title: title, description: description) }
- let(:service) do
- create(:redmine_service, :without_properties_callback, properties: properties)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when data are stored in separated fields' do
- let(:service) do
- create(:redmine_service, title: title, description: description, properties: access_params)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when data are stored in both properties and separated fields' do
- let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') }
- let(:service) do
- create(:redmine_service, :without_properties_callback, title: title, description: description, properties: properties)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when no title & description are set' do
- let(:service) do
- create(:redmine_service, properties: access_params)
- end
-
- it 'returns default values' do
- expect(service.title).to eq('Redmine')
- expect(service.description).to eq('Redmine issue tracker')
- end
- end
- end
end
diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb
index 93036ac7ec4..0b35b9e7b30 100644
--- a/spec/models/project_services/slack_service_spec.rb
+++ b/spec/models/project_services/slack_service_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe SlackService do
+RSpec.describe SlackService do
it_behaves_like "slack or mattermost notifications", 'Slack'
end
diff --git a/spec/models/project_services/slack_slash_commands_service_spec.rb b/spec/models/project_services/slack_slash_commands_service_spec.rb
index 8c57907d064..95c87ef01bc 100644
--- a/spec/models/project_services/slack_slash_commands_service_spec.rb
+++ b/spec/models/project_services/slack_slash_commands_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SlackSlashCommandsService do
+RSpec.describe SlackSlashCommandsService do
it_behaves_like "chat slash commands service"
describe '#trigger' do
diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb
index 0dd77b68588..a3fda33664a 100644
--- a/spec/models/project_services/teamcity_service_spec.rb
+++ b/spec/models/project_services/teamcity_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TeamcityService, :use_clean_rails_memory_store_caching do
+RSpec.describe TeamcityService, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
include StubRequests
diff --git a/spec/models/project_services/unify_circuit_service_spec.rb b/spec/models/project_services/unify_circuit_service_spec.rb
index 51079ea5395..73702aa8471 100644
--- a/spec/models/project_services/unify_circuit_service_spec.rb
+++ b/spec/models/project_services/unify_circuit_service_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe UnifyCircuitService do
+RSpec.describe UnifyCircuitService do
it_behaves_like "chat service", "Unify Circuit" do
let(:client_arguments) { webhook_url }
let(:content_key) { :subject }
diff --git a/spec/models/project_services/webex_teams_service_spec.rb b/spec/models/project_services/webex_teams_service_spec.rb
index 38977ef3b7d..bd73d0c93b8 100644
--- a/spec/models/project_services/webex_teams_service_spec.rb
+++ b/spec/models/project_services/webex_teams_service_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe WebexTeamsService do
+RSpec.describe WebexTeamsService do
it_behaves_like "chat service", "Webex Teams" do
let(:client_arguments) { webhook_url }
let(:content_key) { :markdown }
diff --git a/spec/models/project_services/youtrack_service_spec.rb b/spec/models/project_services/youtrack_service_spec.rb
index b8fff635e99..4339b44e1de 100644
--- a/spec/models/project_services/youtrack_service_spec.rb
+++ b/spec/models/project_services/youtrack_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe YoutrackService do
+RSpec.describe YoutrackService do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -42,49 +42,4 @@ describe YoutrackService do
expect(described_class.reference_pattern.match('yt-123')[:issue]).to eq('yt-123')
end
end
-
- context 'overriding properties' do
- let(:url) { 'http://youtrack.example.com' }
- let(:access_params) do
- { project_url: url, issues_url: url, new_issue_url: url }
- end
-
- # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
- context 'when data are stored in properties' do
- let(:properties) { access_params.merge(title: title, description: description) }
- let(:service) do
- create(:youtrack_service, :without_properties_callback, properties: properties)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when data are stored in separated fields' do
- let(:service) do
- create(:youtrack_service, title: title, description: description, properties: access_params)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when data are stored in both properties and separated fields' do
- let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') }
- let(:service) do
- create(:youtrack_service, :without_properties_callback, title: title, description: description, properties: properties)
- end
-
- it_behaves_like 'issue tracker fields'
- end
-
- context 'when no title & description are set' do
- let(:service) do
- create(:youtrack_service, properties: access_params)
- end
-
- it 'returns default values' do
- expect(service.title).to eq('YouTrack')
- expect(service.description).to eq(s_('IssueTracker|YouTrack issue tracker'))
- end
- end
- end
end
diff --git a/spec/models/project_setting_spec.rb b/spec/models/project_setting_spec.rb
index 5cfb932eb2a..5572304d666 100644
--- a/spec/models/project_setting_spec.rb
+++ b/spec/models/project_setting_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe ProjectSetting, type: :model do
+RSpec.describe ProjectSetting, type: :model do
it { is_expected.to belong_to(:project) }
end
diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb
index c17a24dc7cf..464b9b1da84 100644
--- a/spec/models/project_snippet_spec.rb
+++ b/spec/models/project_snippet_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectSnippet do
+RSpec.describe ProjectSnippet do
describe "Associations" do
it { is_expected.to belong_to(:project) }
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 9ec306d297e..8fdda241719 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Project do
+RSpec.describe Project do
include ProjectForksHelper
include GitHelpers
include ExternalAuthorizationServiceHelpers
@@ -63,6 +63,7 @@ describe Project do
it { is_expected.to have_one(:bugzilla_service) }
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(:confluence_service) }
it { is_expected.to have_one(:project_feature) }
it { is_expected.to have_one(:project_repository) }
it { is_expected.to have_one(:container_expiration_policy) }
@@ -119,6 +120,8 @@ describe Project do
it { is_expected.to have_many(:metrics_users_starred_dashboards).inverse_of(:project) }
it { is_expected.to have_many(:repository_storage_moves) }
it { is_expected.to have_many(:reviews).inverse_of(:project) }
+ it { is_expected.to have_many(:packages).class_name('Packages::Package') }
+ it { is_expected.to have_many(:package_files).class_name('Packages::PackageFile') }
it_behaves_like 'model with repository' do
let_it_be(:container) { create(:project, :repository, path: 'somewhere') }
@@ -1378,6 +1381,62 @@ describe Project do
end
end
+ describe '.service_desk_enabled' do
+ it 'returns the correct project' do
+ project_with_service_desk_enabled = create(:project)
+ project_with_service_desk_disabled = create(:project, :service_desk_disabled)
+
+ expect(described_class.service_desk_enabled).to include(project_with_service_desk_enabled)
+ expect(described_class.service_desk_enabled).not_to include(project_with_service_desk_disabled)
+ end
+ end
+
+ describe '#service_desk_enabled?' do
+ let_it_be(:namespace) { create(:namespace) }
+
+ subject(:project) { build(:project, :private, namespace: namespace, service_desk_enabled: true) }
+
+ before do
+ allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(true)
+ allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(true)
+ end
+
+ it 'is enabled' do
+ expect(project.service_desk_enabled?).to be_truthy
+ expect(project.service_desk_enabled).to be_truthy
+ end
+ end
+
+ describe '#service_desk_address' do
+ let_it_be(:project) { create(:project, service_desk_enabled: true) }
+
+ before do
+ allow(Gitlab::ServiceDesk).to receive(:enabled?).and_return(true)
+ allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true)
+ allow(Gitlab.config.incoming_email).to receive(:address).and_return("test+%{key}@mail.com")
+ end
+
+ it 'uses project full path as service desk address key' do
+ expect(project.service_desk_address).to eq("test+#{project.full_path_slug}-#{project.project_id}-issue-@mail.com")
+ end
+ end
+
+ describe '.find_by_service_desk_project_key' do
+ it 'returns the correct project' do
+ project1 = create(:project)
+ project2 = create(:project)
+ create(:service_desk_setting, project: project1, project_key: 'key1')
+ create(:service_desk_setting, project: project2, project_key: 'key2')
+
+ expect(Project.find_by_service_desk_project_key('key1')).to eq(project1)
+ expect(Project.find_by_service_desk_project_key('key2')).to eq(project2)
+ end
+
+ it 'returns nil if there is no project with the key' do
+ expect(Project.find_by_service_desk_project_key('some_key')).to be_nil
+ end
+ end
+
context 'repository storage by default' do
let(:project) { build(:project) }
@@ -1651,6 +1710,14 @@ describe Project do
let(:project_name) { 'group.example.com' }
it { is_expected.to eq("http://group.example.com") }
+
+ context 'mixed case path' do
+ before do
+ project.update!(path: 'Group.example.com')
+ end
+
+ it { is_expected.to eq("http://group.example.com") }
+ end
end
context 'project page' do
@@ -1658,6 +1725,14 @@ describe Project do
let(:project_name) { 'Project' }
it { is_expected.to eq("http://group.example.com/project") }
+
+ context 'mixed case path' do
+ before do
+ project.update!(path: 'Project')
+ end
+
+ it { is_expected.to eq("http://group.example.com/Project") }
+ end
end
end
@@ -2897,28 +2972,73 @@ describe Project do
subject { project.deployment_variables(environment: environment, kubernetes_namespace: namespace) }
- before do
- expect(project).to receive(:deployment_platform).with(environment: environment)
- .and_return(deployment_platform)
- end
+ context 'when the deployment platform is stubbed' do
+ before do
+ expect(project).to receive(:deployment_platform).with(environment: environment)
+ .and_return(deployment_platform)
+ end
+
+ context 'when project has a deployment platform' do
+ let(:platform_variables) { %w(platform variables) }
+ let(:deployment_platform) { double }
+
+ before do
+ expect(deployment_platform).to receive(:predefined_variables)
+ .with(project: project, environment_name: environment, kubernetes_namespace: namespace)
+ .and_return(platform_variables)
+ end
+
+ it { is_expected.to eq platform_variables }
+ end
- context 'when project has no deployment platform' do
- let(:deployment_platform) { nil }
+ context 'when project has no deployment platform' do
+ let(:deployment_platform) { nil }
- it { is_expected.to eq [] }
+ it { is_expected.to eq [] }
+ end
end
- context 'when project has a deployment platform' do
- let(:platform_variables) { %w(platform variables) }
- let(:deployment_platform) { double }
+ context 'when project has a deployment platforms' do
+ let(:project) { create(:project) }
+
+ let!(:default_cluster) do
+ create(:cluster,
+ :not_managed,
+ platform_type: :kubernetes,
+ projects: [project],
+ environment_scope: '*',
+ platform_kubernetes: default_cluster_kubernetes)
+ end
- before do
- expect(deployment_platform).to receive(:predefined_variables)
- .with(project: project, environment_name: environment, kubernetes_namespace: namespace)
- .and_return(platform_variables)
+ let!(:review_env_cluster) do
+ create(:cluster,
+ :not_managed,
+ platform_type: :kubernetes,
+ projects: [project],
+ environment_scope: 'review/*',
+ platform_kubernetes: review_env_cluster_kubernetes)
end
- it { is_expected.to eq platform_variables }
+ let(:default_cluster_kubernetes) { create(:cluster_platform_kubernetes, token: 'default-AAA') }
+ let(:review_env_cluster_kubernetes) { create(:cluster_platform_kubernetes, token: 'review-AAA') }
+
+ context 'when environment name is review/name' do
+ let!(:environment) { create(:environment, project: project, name: 'review/name') }
+
+ it 'returns variables from this service' do
+ expect(project.deployment_variables(environment: 'review/name'))
+ .to include(key: 'KUBE_TOKEN', value: 'review-AAA', public: false, masked: true)
+ end
+ end
+
+ context 'when environment name is other' do
+ let!(:environment) { create(:environment, project: project, name: 'staging/name') }
+
+ it 'returns variables from this service' do
+ expect(project.deployment_variables(environment: 'staging/name'))
+ .to include(key: 'KUBE_TOKEN', value: 'default-AAA', public: false, masked: true)
+ end
+ end
end
end
@@ -3999,7 +4119,7 @@ describe Project do
it 'returns the number of forks' do
project = build(:project)
- expect_any_instance_of(Projects::ForksCountService).to receive(:count).and_return(1)
+ expect_any_instance_of(::Projects::BatchForksCountService).to receive(:refresh_cache_and_retrieve_data).and_return({ project => 1 })
expect(project.forks_count).to eq(1)
end
@@ -4655,6 +4775,7 @@ describe Project do
expect(project).to receive(:refresh_markdown_cache!)
expect(InternalId).to receive(:flush_records!).with(project: project)
expect(DetectRepositoryLanguagesWorker).to receive(:perform_async).with(project.id)
+ expect(project).to receive(:write_repository_config)
project.after_import
end
@@ -4743,6 +4864,36 @@ describe Project do
end
end
+ describe "#default_branch" do
+ context "with an empty repository" do
+ let_it_be(:project) { create(:project_empty_repo) }
+
+ context "Gitlab::CurrentSettings.default_branch_name is unavailable" do
+ before do
+ expect(Gitlab::CurrentSettings)
+ .to receive(:default_branch_name)
+ .and_return(nil)
+ end
+
+ it "returns that value" do
+ expect(project.default_branch).to be_nil
+ end
+ end
+
+ context "Gitlab::CurrentSettings.default_branch_name is available" do
+ before do
+ expect(Gitlab::CurrentSettings)
+ .to receive(:default_branch_name)
+ .and_return('example_branch')
+ end
+
+ it "returns that value" do
+ expect(project.default_branch).to eq("example_branch")
+ end
+ end
+ end
+ end
+
describe '#to_ability_name' do
it 'returns project' do
project = build(:project_empty_repo)
@@ -5886,6 +6037,30 @@ describe Project do
end
end
+ describe '#prometheus_service_active?' do
+ let(:project) { create(:project) }
+
+ subject { project.prometheus_service_active? }
+
+ before do
+ create(:prometheus_service, project: project, manual_configuration: manual_configuration)
+ end
+
+ context 'when project has an activated prometheus service' do
+ let(:manual_configuration) { true }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when project has an inactive prometheus service' do
+ let(:manual_configuration) { false }
+
+ it 'the service is marked as inactive' do
+ expect(subject).to be_falsey
+ end
+ end
+ end
+
describe '#self_monitoring?' do
let_it_be(:project) { create(:project) }
@@ -6025,6 +6200,39 @@ describe Project do
end
end
+ describe '#packages_enabled' do
+ subject { create(:project).packages_enabled }
+
+ it { is_expected.to be true }
+ end
+
+ describe '#package_already_taken?' do
+ let(:namespace) { create(:namespace) }
+ let(:project) { create(:project, :public, namespace: namespace) }
+ let!(:package) { create(:npm_package, project: project, name: "@#{namespace.path}/foo") }
+
+ context 'no package exists with the same name' do
+ it 'returns false' do
+ result = project.package_already_taken?("@#{namespace.path}/bar")
+ expect(result).to be false
+ end
+
+ it 'returns false if it is the project that the package belongs to' do
+ result = project.package_already_taken?("@#{namespace.path}/foo")
+ expect(result).to be false
+ end
+ end
+
+ context 'a package already exists with the same name' do
+ let(:alt_project) { create(:project, :public, namespace: namespace) }
+
+ it 'returns true' do
+ result = alt_project.package_already_taken?("@#{namespace.path}/foo")
+ expect(result).to be true
+ end
+ end
+ end
+
describe '#design_management_enabled?' do
let(:project) { build(:project) }
diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb
index 4bc6130387a..3659e6b973e 100644
--- a/spec/models/project_statistics_spec.rb
+++ b/spec/models/project_statistics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectStatistics do
+RSpec.describe ProjectStatistics do
let(:project) { create :project }
let(:statistics) { project.statistics }
@@ -32,7 +32,8 @@ describe ProjectStatistics do
repository_size: 2.exabytes,
wiki_size: 1.exabytes,
lfs_objects_size: 2.exabytes,
- build_artifacts_size: 3.exabytes - 1
+ build_artifacts_size: 2.exabytes - 1,
+ snippets_size: 1.exabyte
)
statistics.reload
@@ -41,8 +42,9 @@ describe ProjectStatistics do
expect(statistics.repository_size).to eq(2.exabytes)
expect(statistics.wiki_size).to eq(1.exabytes)
expect(statistics.lfs_objects_size).to eq(2.exabytes)
- expect(statistics.build_artifacts_size).to eq(3.exabytes - 1)
+ expect(statistics.build_artifacts_size).to eq(2.exabytes - 1)
expect(statistics.storage_size).to eq(8.exabytes - 1)
+ expect(statistics.snippets_size).to eq(1.exabyte)
end
end
@@ -52,23 +54,47 @@ describe ProjectStatistics do
statistics.wiki_size = 6
statistics.lfs_objects_size = 3
statistics.build_artifacts_size = 4
+ statistics.snippets_size = 5
expect(statistics.total_repository_size).to eq 5
end
end
describe '#wiki_size' do
- it "is initialized with not null value" do
+ it 'is initialized with not null value' do
+ expect(statistics.attributes['wiki_size']).to be_zero
+ expect(statistics.wiki_size).to be_zero
+ end
+
+ it 'coerces any nil value to 0' do
+ statistics.update!(wiki_size: nil)
+
+ expect(statistics.attributes['wiki_size']).to be_nil
expect(statistics.wiki_size).to eq 0
end
end
+ describe '#snippets_size' do
+ it 'is initialized with not null value' do
+ expect(statistics.attributes['snippets_size']).to be_zero
+ expect(statistics.snippets_size).to be_zero
+ end
+
+ it 'coerces any nil value to 0' do
+ statistics.update!(snippets_size: nil)
+
+ expect(statistics.attributes['snippets_size']).to be_nil
+ expect(statistics.snippets_size).to eq 0
+ end
+ end
+
describe '#refresh!' do
before do
allow(statistics).to receive(:update_commit_count)
allow(statistics).to receive(:update_repository_size)
allow(statistics).to receive(:update_wiki_size)
allow(statistics).to receive(:update_lfs_objects_size)
+ allow(statistics).to receive(:update_snippets_size)
allow(statistics).to receive(:update_storage_size)
end
@@ -82,6 +108,7 @@ describe ProjectStatistics do
expect(statistics).to have_received(:update_repository_size)
expect(statistics).to have_received(:update_wiki_size)
expect(statistics).to have_received(:update_lfs_objects_size)
+ expect(statistics).to have_received(:update_snippets_size)
end
end
@@ -95,6 +122,7 @@ describe ProjectStatistics do
expect(statistics).not_to have_received(:update_commit_count)
expect(statistics).not_to have_received(:update_repository_size)
expect(statistics).not_to have_received(:update_wiki_size)
+ expect(statistics).not_to have_received(:update_snippets_size)
end
end
@@ -108,9 +136,11 @@ describe ProjectStatistics do
expect(statistics).to have_received(:update_commit_count)
expect(statistics).to have_received(:update_repository_size)
expect(statistics).to have_received(:update_wiki_size)
+ expect(statistics).to have_received(:update_snippets_size)
expect(statistics.repository_size).to eq(0)
expect(statistics.commit_count).to eq(0)
expect(statistics.wiki_size).to eq(0)
+ expect(statistics.snippets_size).to eq(0)
end
end
@@ -130,9 +160,11 @@ describe ProjectStatistics do
expect(statistics).to have_received(:update_commit_count)
expect(statistics).to have_received(:update_repository_size)
expect(statistics).to have_received(:update_wiki_size)
+ expect(statistics).to have_received(:update_snippets_size)
expect(statistics.repository_size).to eq(0)
expect(statistics.commit_count).to eq(0)
expect(statistics.wiki_size).to eq(0)
+ expect(statistics.snippets_size).to eq(0)
end
end
@@ -202,6 +234,33 @@ describe ProjectStatistics do
end
end
+ describe '#update_snippets_size' do
+ before do
+ create_list(:project_snippet, 2, project: project)
+ SnippetStatistics.update_all(repository_size: 10)
+ end
+
+ it 'stores the size of snippets' do
+ # Snippet not associated with the project
+ snippet = create(:project_snippet)
+ snippet.statistics.update!(repository_size: 40)
+
+ statistics.update_snippets_size
+
+ expect(statistics.update_snippets_size).to eq 20
+ end
+
+ context 'when not all snippets has statistics' do
+ it 'stores the size of snippets with statistics' do
+ SnippetStatistics.last.delete
+
+ statistics.update_snippets_size
+
+ expect(statistics.update_snippets_size).to eq 10
+ end
+ end
+ end
+
describe '#update_lfs_objects_size' do
let!(:lfs_object1) { create(:lfs_object, size: 23.megabytes) }
let!(:lfs_object2) { create(:lfs_object, size: 34.megabytes) }
@@ -222,12 +281,13 @@ describe ProjectStatistics do
statistics.update!(
repository_size: 2,
wiki_size: 4,
- lfs_objects_size: 3
+ lfs_objects_size: 3,
+ snippets_size: 2
)
statistics.reload
- expect(statistics.storage_size).to eq 9
+ expect(statistics.storage_size).to eq 11
end
it 'works during wiki_size backfill' do
@@ -241,6 +301,21 @@ describe ProjectStatistics do
expect(statistics.storage_size).to eq 5
end
+
+ context 'when nullable columns are nil' do
+ it 'does not raise any error' do
+ expect do
+ statistics.update!(
+ repository_size: 2,
+ wiki_size: nil,
+ lfs_objects_size: 3,
+ snippets_size: nil
+ )
+ end.not_to raise_error
+
+ expect(statistics.storage_size).to eq 5
+ end
+ end
end
describe '.increment_statistic' do
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index 24652a1d706..34ec856459c 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe ProjectTeam do
+RSpec.describe ProjectTeam do
let(:maintainer) { create(:user) }
let(:reporter) { create(:user) }
let(:guest) { create(:user) }
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index aff2b248642..d9c5fed542e 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectWiki do
+RSpec.describe ProjectWiki do
it_behaves_like 'wiki model' do
let(:wiki_container) { create(:project, :wiki_repo, namespace: user.namespace) }
let(:wiki_container_without_repo) { create(:project, namespace: user.namespace) }
diff --git a/spec/models/prometheus_alert_event_spec.rb b/spec/models/prometheus_alert_event_spec.rb
index 85e57cb08c3..913ca7db0be 100644
--- a/spec/models/prometheus_alert_event_spec.rb
+++ b/spec/models/prometheus_alert_event_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PrometheusAlertEvent do
+RSpec.describe PrometheusAlertEvent do
subject { build(:prometheus_alert_event) }
let(:alert) { subject.prometheus_alert }
diff --git a/spec/models/prometheus_alert_spec.rb b/spec/models/prometheus_alert_spec.rb
index 1409cf65fee..7169a34d96f 100644
--- a/spec/models/prometheus_alert_spec.rb
+++ b/spec/models/prometheus_alert_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PrometheusAlert do
+RSpec.describe PrometheusAlert do
let_it_be(:project) { build(:project) }
let(:metric) { build(:prometheus_metric) }
@@ -33,6 +33,10 @@ describe PrometheusAlert do
describe 'associations' do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:environment) }
+ it { is_expected.to belong_to(:prometheus_metric) }
+ it { is_expected.to have_many(:prometheus_alert_events) }
+ it { is_expected.to have_many(:related_issues) }
+ it { is_expected.to have_many(:alert_management_alerts) }
end
describe 'project validations' do
diff --git a/spec/models/prometheus_metric_spec.rb b/spec/models/prometheus_metric_spec.rb
index 93abef063cb..f284102b4a9 100644
--- a/spec/models/prometheus_metric_spec.rb
+++ b/spec/models/prometheus_metric_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PrometheusMetric do
+RSpec.describe PrometheusMetric do
subject { build(:prometheus_metric) }
it_behaves_like 'having unique enum values'
@@ -11,6 +11,7 @@ describe PrometheusMetric do
it { is_expected.to validate_presence_of(:title) }
it { is_expected.to validate_presence_of(:query) }
it { is_expected.to validate_presence_of(:group) }
+ it { is_expected.to validate_uniqueness_of(:identifier).scoped_to(:project_id).allow_nil }
describe 'common metrics' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/models/protectable_dropdown_spec.rb b/spec/models/protectable_dropdown_spec.rb
index aca3df9fdde..c51197234ca 100644
--- a/spec/models/protectable_dropdown_spec.rb
+++ b/spec/models/protectable_dropdown_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProtectableDropdown do
+RSpec.describe ProtectableDropdown do
let(:project) { create(:project, :repository) }
let(:subject) { described_class.new(project, :branches) }
diff --git a/spec/models/protected_branch/merge_access_level_spec.rb b/spec/models/protected_branch/merge_access_level_spec.rb
index 39dd586b157..b6c2d527d1b 100644
--- a/spec/models/protected_branch/merge_access_level_spec.rb
+++ b/spec/models/protected_branch/merge_access_level_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe ProtectedBranch::MergeAccessLevel do
+RSpec.describe ProtectedBranch::MergeAccessLevel do
it { is_expected.to validate_inclusion_of(:access_level).in_array([Gitlab::Access::MAINTAINER, Gitlab::Access::DEVELOPER, Gitlab::Access::NO_ACCESS]) }
end
diff --git a/spec/models/protected_branch/push_access_level_spec.rb b/spec/models/protected_branch/push_access_level_spec.rb
index 628c8d29ecd..77fe9814c86 100644
--- a/spec/models/protected_branch/push_access_level_spec.rb
+++ b/spec/models/protected_branch/push_access_level_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe ProtectedBranch::PushAccessLevel do
+RSpec.describe ProtectedBranch::PushAccessLevel do
it { is_expected.to validate_inclusion_of(:access_level).in_array([Gitlab::Access::MAINTAINER, Gitlab::Access::DEVELOPER, Gitlab::Access::NO_ACCESS]) }
end
diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb
index 30fce1cd5c4..a89f8778780 100644
--- a/spec/models/protected_branch_spec.rb
+++ b/spec/models/protected_branch_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProtectedBranch do
+RSpec.describe ProtectedBranch do
subject { build_stubbed(:protected_branch) }
describe 'Associations' do
diff --git a/spec/models/protected_tag_spec.rb b/spec/models/protected_tag_spec.rb
index 79120d17d39..7bc62b1d0e7 100644
--- a/spec/models/protected_tag_spec.rb
+++ b/spec/models/protected_tag_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProtectedTag do
+RSpec.describe ProtectedTag do
describe 'Associations' do
it { is_expected.to belong_to(:project) }
end
diff --git a/spec/models/push_event_payload_spec.rb b/spec/models/push_event_payload_spec.rb
index 6b59ee5ee57..32415ef4719 100644
--- a/spec/models/push_event_payload_spec.rb
+++ b/spec/models/push_event_payload_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PushEventPayload do
+RSpec.describe PushEventPayload do
it_behaves_like 'having unique enum values'
describe 'saving payloads' do
diff --git a/spec/models/push_event_spec.rb b/spec/models/push_event_spec.rb
index 5c1802669c1..61e31e7c4e3 100644
--- a/spec/models/push_event_spec.rb
+++ b/spec/models/push_event_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PushEvent do
+RSpec.describe PushEvent do
let(:payload) { PushEventPayload.new }
let(:event) do
diff --git a/spec/models/readme_blob_spec.rb b/spec/models/readme_blob_spec.rb
index 34182fa413f..95622d55254 100644
--- a/spec/models/readme_blob_spec.rb
+++ b/spec/models/readme_blob_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ReadmeBlob do
+RSpec.describe ReadmeBlob do
include FakeBlobHelpers
describe 'policy' do
diff --git a/spec/models/redirect_route_spec.rb b/spec/models/redirect_route_spec.rb
index b9b2873f8f2..c6e35923b89 100644
--- a/spec/models/redirect_route_spec.rb
+++ b/spec/models/redirect_route_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RedirectRoute do
+RSpec.describe RedirectRoute do
let(:group) { create(:group) }
let!(:redirect_route) { group.redirect_routes.create(path: 'gitlabb') }
diff --git a/spec/models/releases/evidence_spec.rb b/spec/models/releases/evidence_spec.rb
index 927e2e9bbe6..ca5d4b67b59 100644
--- a/spec/models/releases/evidence_spec.rb
+++ b/spec/models/releases/evidence_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Releases::Evidence do
+RSpec.describe Releases::Evidence do
let_it_be(:project) { create(:project) }
let(:release) { create(:release, project: project) }
diff --git a/spec/models/releases/link_spec.rb b/spec/models/releases/link_spec.rb
index 7533d1e6e5c..4dc1e53d59e 100644
--- a/spec/models/releases/link_spec.rb
+++ b/spec/models/releases/link_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Releases::Link do
+RSpec.describe Releases::Link do
let(:release) { create(:release, project: project) }
let(:project) { create(:project) }
diff --git a/spec/models/releases/source_spec.rb b/spec/models/releases/source_spec.rb
index d7af6fd90a6..d10b2140550 100644
--- a/spec/models/releases/source_spec.rb
+++ b/spec/models/releases/source_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Releases::Source do
+RSpec.describe Releases::Source do
let_it_be(:project) { create(:project, :repository, name: 'finance-cal') }
let(:tag_name) { 'v1.0' }
diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb
index 6d163a16e63..ebc9760ab14 100644
--- a/spec/models/remote_mirror_spec.rb
+++ b/spec/models/remote_mirror_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RemoteMirror, :mailer do
+RSpec.describe RemoteMirror, :mailer do
include GitHelpers
describe 'URL validation' do
diff --git a/spec/models/repository_language_spec.rb b/spec/models/repository_language_spec.rb
index 13a4cd1e7cf..8cf5e17086d 100644
--- a/spec/models/repository_language_spec.rb
+++ b/spec/models/repository_language_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RepositoryLanguage do
+RSpec.describe RepositoryLanguage do
let(:repository_language) { build(:repository_language) }
describe 'associations' do
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index c698b40a4c0..964cc5a13ca 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Repository do
+RSpec.describe Repository do
include RepoHelpers
include GitHelpers
@@ -252,6 +252,21 @@ describe Repository do
end
end
end
+
+ context 'with filename with pathspec characters' do
+ let(:filename) { ':wq' }
+ let(:newrev) { project.repository.commit('master').sha }
+
+ before do
+ create_file_in_repo(project, 'master', 'master', filename, 'Test file')
+ end
+
+ subject { repository.last_commit_for_path('master', filename, literal_pathspec: true).id }
+
+ it 'returns a commit SHA' do
+ expect(subject).to eq(newrev)
+ end
+ end
end
describe '#last_commit_id_for_path' do
@@ -276,6 +291,21 @@ describe Repository do
end
end
end
+
+ context 'with filename with pathspec characters' do
+ let(:filename) { ':wq' }
+ let(:newrev) { project.repository.commit('master').sha }
+
+ before do
+ create_file_in_repo(project, 'master', 'master', filename, 'Test file')
+ end
+
+ subject { repository.last_commit_id_for_path('master', filename, literal_pathspec: true) }
+
+ it 'returns a commit SHA' do
+ expect(subject).to eq(newrev)
+ end
+ end
end
describe '#commits' do
@@ -2865,6 +2895,29 @@ describe Repository do
end
end
+ describe '#project' do
+ it 'returns the project for a project snippet' do
+ snippet = create(:project_snippet)
+
+ expect(snippet.repository.project).to be(snippet.project)
+ end
+
+ it 'returns nil for a personal snippet' do
+ snippet = create(:personal_snippet)
+
+ expect(snippet.repository.project).to be_nil
+ end
+
+ it 'returns the container if it is a project' do
+ expect(repository.project).to be(project)
+ end
+
+ it 'returns nil if the container is not a project' do
+ expect(repository).to receive(:container).and_return(Group.new)
+ expect(repository.project).to be_nil
+ end
+ end
+
describe '#submodule_links' do
it 'returns an instance of Gitlab::SubmoduleLinks' do
expect(repository.submodule_links).to be_a(Gitlab::SubmoduleLinks)
diff --git a/spec/models/resource_milestone_event_spec.rb b/spec/models/resource_milestone_event_spec.rb
index 66686ec77d0..76ffb358d80 100644
--- a/spec/models/resource_milestone_event_spec.rb
+++ b/spec/models/resource_milestone_event_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResourceMilestoneEvent, type: :model do
+RSpec.describe ResourceMilestoneEvent, type: :model do
it_behaves_like 'a resource event'
it_behaves_like 'a resource event for issues'
it_behaves_like 'a resource event for merge requests'
diff --git a/spec/models/resource_state_event_spec.rb b/spec/models/resource_state_event_spec.rb
index 986a13cbd0d..1381b45cf9e 100644
--- a/spec/models/resource_state_event_spec.rb
+++ b/spec/models/resource_state_event_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResourceStateEvent, type: :model do
+RSpec.describe ResourceStateEvent, type: :model do
subject { build(:resource_state_event, issue: issue) }
let(:issue) { create(:issue) }
diff --git a/spec/models/review_spec.rb b/spec/models/review_spec.rb
index 9dd8b90feee..2683dc93a4b 100644
--- a/spec/models/review_spec.rb
+++ b/spec/models/review_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Review do
+RSpec.describe Review do
describe 'associations' do
it { is_expected.to belong_to(:author).class_name('User').with_foreign_key(:author_id).inverse_of(:reviews) }
it { is_expected.to belong_to(:merge_request).inverse_of(:reviews).touch(false) }
diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb
index 20289afbeb5..0f1637016d6 100644
--- a/spec/models/route_spec.rb
+++ b/spec/models/route_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Route do
+RSpec.describe Route do
let(:group) { create(:group, path: 'git_lab', name: 'git_lab') }
let(:route) { group.route }
diff --git a/spec/models/sent_notification_spec.rb b/spec/models/sent_notification_spec.rb
index 087bc957373..aeafb49f8b5 100644
--- a/spec/models/sent_notification_spec.rb
+++ b/spec/models/sent_notification_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SentNotification do
+RSpec.describe SentNotification do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
diff --git a/spec/models/sentry_issue_spec.rb b/spec/models/sentry_issue_spec.rb
index b4c1cf57761..33654bf5e1a 100644
--- a/spec/models/sentry_issue_spec.rb
+++ b/spec/models/sentry_issue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SentryIssue do
+RSpec.describe SentryIssue do
describe 'associations' do
it { is_expected.to belong_to(:issue) }
end
diff --git a/spec/models/serverless/domain_cluster_spec.rb b/spec/models/serverless/domain_cluster_spec.rb
index f5e1eb304a1..fdae0483c19 100644
--- a/spec/models/serverless/domain_cluster_spec.rb
+++ b/spec/models/serverless/domain_cluster_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Serverless::DomainCluster do
+RSpec.describe ::Serverless::DomainCluster do
subject { create(:serverless_domain_cluster) }
describe 'validations' do
diff --git a/spec/models/serverless/domain_spec.rb b/spec/models/serverless/domain_spec.rb
index ba54e05b4e3..f997b28b149 100644
--- a/spec/models/serverless/domain_spec.rb
+++ b/spec/models/serverless/domain_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Serverless::Domain do
+RSpec.describe ::Serverless::Domain do
let(:function_name) { 'test-function' }
let(:pages_domain_name) { 'serverless.gitlab.io' }
let(:pages_domain) { create(:pages_domain, :instance_serverless, domain: pages_domain_name) }
diff --git a/spec/models/serverless/function_spec.rb b/spec/models/serverless/function_spec.rb
index 810d4409a34..632f5eba5c3 100644
--- a/spec/models/serverless/function_spec.rb
+++ b/spec/models/serverless/function_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Serverless::Function do
+RSpec.describe ::Serverless::Function do
let(:project) { create(:project) }
let(:func) { described_class.new(project, 'test', 'test-ns') }
diff --git a/spec/models/service_desk_setting_spec.rb b/spec/models/service_desk_setting_spec.rb
new file mode 100644
index 00000000000..ca57a5d4087
--- /dev/null
+++ b/spec/models/service_desk_setting_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ServiceDeskSetting do
+ describe 'validations' do
+ subject(:service_desk_setting) { create(:service_desk_setting) }
+
+ it { is_expected.to validate_presence_of(:project_id) }
+ it { is_expected.to validate_length_of(:outgoing_name).is_at_most(255) }
+ it { is_expected.to validate_length_of(:project_key).is_at_most(255) }
+ it { is_expected.to allow_value('abc123_').for(:project_key) }
+ it { is_expected.not_to allow_value('abc 12').for(:project_key) }
+ it { is_expected.not_to allow_value('Big val').for(:project_key) }
+
+ describe '.valid_issue_template' do
+ let_it_be(:project) { create(:project, :custom_repo, files: { '.gitlab/issue_templates/service_desk.md' => 'template' }) }
+
+ it 'is not valid if template does not exist' do
+ settings = build(:service_desk_setting, project: project, issue_template_key: 'invalid key')
+
+ expect(settings).not_to be_valid
+ expect(settings.errors[:issue_template_key].first).to eq('is empty or does not exist')
+ end
+
+ it 'is valid if template exists' do
+ settings = build(:service_desk_setting, project: project, issue_template_key: 'service_desk')
+
+ expect(settings).to be_valid
+ end
+ end
+ end
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:project) }
+ end
+end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index 8698a6cf3d3..75bbb074526 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Service do
+RSpec.describe Service do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -304,8 +304,6 @@ describe Service do
end
describe 'build issue tracker from an integration' do
- let(:title) { 'custom title' }
- let(:description) { 'custom description' }
let(:url) { 'http://jira.example.com' }
let(:api_url) { 'http://api-jira.example.com' }
let(:username) { 'jira-username' }
@@ -322,8 +320,6 @@ describe Service do
service = described_class.build_from_integration(project.id, integration)
expect(service).to be_active
- expect(service.title).to eq(title)
- expect(service.description).to eq(description)
expect(service.url).to eq(url)
expect(service.api_url).to eq(api_url)
expect(service.username).to eq(username)
@@ -335,7 +331,7 @@ describe Service do
# this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
context 'when data are stored in properties' do
- let(:properties) { data_params.merge(title: title, description: description) }
+ let(:properties) { data_params }
let!(:integration) do
create(:jira_service, :without_properties_callback, template: true, properties: properties.merge(additional: 'something'))
end
@@ -345,14 +341,14 @@ describe Service do
context 'when data are stored in separated fields' do
let(:integration) do
- create(:jira_service, :template, data_params.merge(properties: {}, title: title, description: description))
+ create(:jira_service, :template, data_params.merge(properties: {}))
end
it_behaves_like 'service creation from an integration'
end
context 'when data are stored in both properties and separated fields' do
- let(:properties) { data_params.merge(title: title, description: description) }
+ let(:properties) { data_params }
let(:integration) do
create(:jira_service, :without_properties_callback, active: true, template: true, properties: properties).tap do |service|
create(:jira_tracker_data, data_params.merge(service: service))
@@ -390,6 +386,33 @@ describe Service do
end
end
+ describe 'instance' do
+ describe '.instance_for' do
+ let_it_be(:jira_service) { create(:jira_service, :instance) }
+ let_it_be(:slack_service) { create(:slack_service, :instance) }
+
+ subject { described_class.instance_for(type) }
+
+ context 'Hipchat serivce' do
+ let(:type) { 'HipchatService' }
+
+ it { is_expected.to eq(nil) }
+ end
+
+ context 'Jira serivce' do
+ let(:type) { 'JiraService' }
+
+ it { is_expected.to eq(jira_service) }
+ end
+
+ context 'Slack serivce' do
+ let(:type) { 'SlackService' }
+
+ it { is_expected.to eq(slack_service) }
+ end
+ end
+ end
+
describe "{property}_changed?" do
let(:service) do
BambooService.create(
@@ -514,7 +537,6 @@ describe Service do
let(:service) do
GitlabIssueTrackerService.create(
project: create(:project),
- title: 'random title',
project_url: 'http://gitlab.example.com'
)
end
@@ -523,10 +545,6 @@ describe Service do
expect { service }.not_to raise_error
end
- it 'sets title correctly' do
- expect(service.title).to eq('random title')
- end
-
it 'sets data correctly' do
expect(service.data_fields.project_url).to eq('http://gitlab.example.com')
end
diff --git a/spec/models/shard_spec.rb b/spec/models/shard_spec.rb
index 4da86858b54..a9d11f4290c 100644
--- a/spec/models/shard_spec.rb
+++ b/spec/models/shard_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Shard do
+RSpec.describe Shard do
describe '.populate!' do
it 'creates shards based on the config file' do
expect(described_class.all).to be_empty
diff --git a/spec/models/snippet_blob_spec.rb b/spec/models/snippet_blob_spec.rb
index 88441e39d45..19b985f66ee 100644
--- a/spec/models/snippet_blob_spec.rb
+++ b/spec/models/snippet_blob_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SnippetBlob do
+RSpec.describe SnippetBlob do
let(:snippet) { create(:snippet) }
subject { described_class.new(snippet) }
diff --git a/spec/models/snippet_input_action_collection_spec.rb b/spec/models/snippet_input_action_collection_spec.rb
index ef18ab5a810..3ec206bd031 100644
--- a/spec/models/snippet_input_action_collection_spec.rb
+++ b/spec/models/snippet_input_action_collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SnippetInputActionCollection do
+RSpec.describe SnippetInputActionCollection do
let(:action_name) { 'create' }
let(:action) { { action: action_name, file_path: 'foo', content: 'bar', previous_path: 'foobar' } }
let(:data) { [action, action] }
diff --git a/spec/models/snippet_input_action_spec.rb b/spec/models/snippet_input_action_spec.rb
index 5e379a48171..ca61b80df4c 100644
--- a/spec/models/snippet_input_action_spec.rb
+++ b/spec/models/snippet_input_action_spec.rb
@@ -2,18 +2,18 @@
require 'spec_helper'
-describe SnippetInputAction do
+RSpec.describe SnippetInputAction do
describe 'validations' do
using RSpec::Parameterized::TableSyntax
where(:action, :file_path, :content, :previous_path, :allowed_actions, :is_valid, :invalid_field) do
:create | 'foobar' | 'foobar' | 'foobar' | nil | true | nil
- :move | 'foobar' | 'foobar' | 'foobar' | nil | true | nil
+ :move | 'foobar' | 'foobar' | 'foo1' | nil | true | nil
:delete | 'foobar' | 'foobar' | 'foobar' | nil | true | nil
:update | 'foobar' | 'foobar' | 'foobar' | nil | true | nil
:foo | 'foobar' | 'foobar' | 'foobar' | nil | false | :action
'create' | 'foobar' | 'foobar' | 'foobar' | nil | true | nil
- 'move' | 'foobar' | 'foobar' | 'foobar' | nil | true | nil
+ 'move' | 'foobar' | 'foobar' | 'foo1' | nil | true | nil
'delete' | 'foobar' | 'foobar' | 'foobar' | nil | true | nil
'update' | 'foobar' | 'foobar' | 'foobar' | nil | true | nil
'foo' | 'foobar' | 'foobar' | 'foobar' | nil | false | :action
@@ -21,10 +21,16 @@ describe SnippetInputAction do
'' | 'foobar' | 'foobar' | 'foobar' | nil | false | :action
:move | 'foobar' | 'foobar' | nil | nil | false | :previous_path
:move | 'foobar' | 'foobar' | '' | nil | false | :previous_path
+ :move | 'foobar' | 'foobar' | 'foobar' | nil | false | :file_path
+ :move | nil | 'foobar' | 'foobar' | nil | false | :file_path
+ :move | '' | 'foobar' | 'foobar' | nil | false | :file_path
+ :move | nil | 'foobar' | 'foo1' | nil | false | :file_path
+ :move | 'foobar' | nil | 'foo1' | nil | true | nil
+ :move | 'foobar' | '' | 'foo1' | nil | true | nil
:create | 'foobar' | nil | 'foobar' | nil | false | :content
:create | 'foobar' | '' | 'foobar' | nil | false | :content
- :create | nil | 'foobar' | 'foobar' | nil | false | :file_path
- :create | '' | 'foobar' | 'foobar' | nil | false | :file_path
+ :create | nil | 'foobar' | 'foobar' | nil | true | nil
+ :create | '' | 'foobar' | 'foobar' | nil | true | nil
:update | 'foobar' | nil | 'foobar' | nil | false | :content
:update | 'foobar' | '' | 'foobar' | nil | false | :content
:update | 'other' | 'foobar' | 'foobar' | nil | false | :file_path
diff --git a/spec/models/snippet_repository_spec.rb b/spec/models/snippet_repository_spec.rb
index b86a6f82f07..8c25d713c0a 100644
--- a/spec/models/snippet_repository_spec.rb
+++ b/spec/models/snippet_repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SnippetRepository do
+RSpec.describe SnippetRepository do
let_it_be(:user) { create(:user) }
let(:snippet) { create(:personal_snippet, :repository, author: user) }
let(:snippet_repository) { snippet.snippet_repository }
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index 4d6586c1df4..3f9c6981de1 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Snippet do
+RSpec.describe Snippet do
describe 'modules' do
subject { described_class }
@@ -20,6 +20,7 @@ describe Snippet do
it { is_expected.to have_many(:award_emoji).dependent(:destroy) }
it { is_expected.to have_many(:user_mentions).class_name("SnippetUserMention") }
it { is_expected.to have_one(:snippet_repository) }
+ it { is_expected.to have_one(:statistics).class_name('SnippetStatistics').dependent(:destroy) }
end
describe 'validation' do
@@ -91,6 +92,17 @@ describe Snippet do
end
end
+ describe 'callbacks' do
+ it 'creates snippet statistics when the snippet is created' do
+ snippet = build(:snippet)
+ expect(snippet.statistics).to be_nil
+
+ snippet.save
+
+ expect(snippet.statistics).to be_persisted
+ end
+ end
+
describe '#to_reference' do
context 'when snippet belongs to a project' do
let(:project) { build(:project, name: 'sample-project') }
@@ -750,4 +762,29 @@ describe Snippet do
end
end
end
+
+ describe '#list_files' do
+ let_it_be(:snippet) { create(:snippet, :repository) }
+ let(:ref) { 'test-ref' }
+
+ subject { snippet.list_files(ref) }
+
+ context 'when snippet has a repository' do
+ it 'lists files from the repository with the ref' do
+ expect(snippet.repository).to receive(:ls_files).with(ref)
+
+ subject
+ end
+ end
+
+ context 'when snippet does not have a repository' do
+ before do
+ allow(snippet.repository).to receive(:empty?).and_return(true)
+ end
+
+ it 'returns an empty array' do
+ expect(subject).to eq []
+ end
+ end
+ end
end
diff --git a/spec/models/snippet_statistics_spec.rb b/spec/models/snippet_statistics_spec.rb
new file mode 100644
index 00000000000..ad25bd7b3be
--- /dev/null
+++ b/spec/models/snippet_statistics_spec.rb
@@ -0,0 +1,149 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe SnippetStatistics do
+ let_it_be(:snippet_without_repo) { create(:snippet) }
+ let_it_be(:snippet_with_repo) { create(:snippet, :repository) }
+
+ let(:statistics) { snippet_with_repo.statistics }
+
+ it { is_expected.to belong_to(:snippet) }
+ it { is_expected.to validate_presence_of(:snippet) }
+
+ describe '#update_commit_count' do
+ subject { statistics.update_commit_count }
+
+ it 'updates the count of commits' do
+ commit_count = snippet_with_repo.repository.commit_count
+
+ subject
+
+ expect(statistics.commit_count).to eq commit_count
+ end
+
+ context 'when the snippet does not have a repository' do
+ let(:statistics) { snippet_without_repo.statistics }
+
+ it 'returns 0' do
+ expect(subject).to eq 0
+ expect(statistics.commit_count).to eq 0
+ end
+ end
+ end
+
+ describe '#update_file_count' do
+ subject { statistics.update_file_count }
+
+ it 'updates the count of files' do
+ file_count = snippet_with_repo.repository.ls_files(nil).count
+
+ subject
+
+ expect(statistics.file_count).to eq file_count
+ end
+
+ context 'when the snippet does not have a repository' do
+ let(:statistics) { snippet_without_repo.statistics }
+
+ it 'returns 0' do
+ expect(subject).to eq 0
+ expect(statistics.file_count).to eq 0
+ end
+ end
+ end
+
+ describe '#update_repository_size' do
+ subject { statistics.update_repository_size }
+
+ it 'updates the repository_size' do
+ repository_size = snippet_with_repo.repository.size.megabytes.to_i
+
+ subject
+
+ expect(statistics.repository_size).to eq repository_size
+ end
+
+ context 'when the snippet does not have a repository' do
+ let(:statistics) { snippet_without_repo.statistics }
+
+ it 'returns 0' do
+ expect(subject).to eq 0
+ expect(statistics.repository_size).to eq 0
+ end
+ end
+ end
+
+ describe '#refresh!' do
+ subject { statistics.refresh! }
+
+ it 'retrieves and saves statistic data from repository' do
+ expect(statistics).to receive(:update_commit_count)
+ expect(statistics).to receive(:update_file_count)
+ expect(statistics).to receive(:update_repository_size)
+ expect(statistics).to receive(:save!)
+
+ subject
+ end
+ end
+
+ context 'with a PersonalSnippet' do
+ let!(:snippet) { create(:personal_snippet, :repository) }
+
+ shared_examples 'personal snippet statistics updates' do
+ it 'schedules a namespace statistics worker' do
+ expect(Namespaces::ScheduleAggregationWorker)
+ .to receive(:perform_async).once
+
+ statistics.save!
+ end
+
+ it 'does not try to update project stats' do
+ expect(statistics).not_to receive(:schedule_update_project_statistic)
+
+ statistics.save!
+ end
+ end
+
+ context 'when creating' do
+ let(:statistics) { build(:snippet_statistics, snippet_id: snippet.id, with_data: true) }
+
+ before do
+ snippet.statistics.delete
+ end
+
+ it_behaves_like 'personal snippet statistics updates'
+ end
+
+ context 'when updating' do
+ let(:statistics) { snippet.statistics }
+
+ before do
+ snippet.statistics.repository_size = 123
+ end
+
+ it_behaves_like 'personal snippet statistics updates'
+ end
+ end
+
+ context 'with a ProjectSnippet' do
+ let!(:snippet) { create(:project_snippet) }
+
+ it_behaves_like 'UpdateProjectStatistics' do
+ subject { build(:snippet_statistics, snippet: snippet, id: snippet.id, with_data: true) }
+
+ before do
+ # The shared examples requires the snippet statistics not to be present
+ snippet.statistics.delete
+ snippet.reload
+ end
+ end
+
+ it 'does not call personal snippet callbacks' do
+ expect(snippet.statistics).not_to receive(:update_author_root_storage_statistics)
+ expect(snippet.statistics).to receive(:schedule_update_project_statistic)
+
+ snippet.statistics.update!(repository_size: 123)
+ end
+ end
+end
diff --git a/spec/models/spam_log_spec.rb b/spec/models/spam_log_spec.rb
index 8d0f247b5d6..97a0dc27f17 100644
--- a/spec/models/spam_log_spec.rb
+++ b/spec/models/spam_log_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SpamLog do
+RSpec.describe SpamLog do
let_it_be(:admin) { create(:admin) }
describe 'associations' do
diff --git a/spec/models/ssh_host_key_spec.rb b/spec/models/ssh_host_key_spec.rb
index a17cd8ba345..4d729d5585f 100644
--- a/spec/models/ssh_host_key_spec.rb
+++ b/spec/models/ssh_host_key_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SshHostKey do
+RSpec.describe SshHostKey do
using RSpec::Parameterized::TableSyntax
include ReactiveCachingHelpers
diff --git a/spec/models/state_note_spec.rb b/spec/models/state_note_spec.rb
index d3409315e41..bd07af7ceca 100644
--- a/spec/models/state_note_spec.rb
+++ b/spec/models/state_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe StateNote do
+RSpec.describe StateNote do
describe '.from_event' do
let_it_be(:author) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
@@ -10,18 +10,62 @@ describe StateNote do
ResourceStateEvent.states.each do |state, _value|
context "with event state #{state}" do
- let_it_be(:event) { create(:resource_state_event, issue: noteable, state: state, created_at: '2020-02-05') }
+ let(:event) { create(:resource_state_event, issue: noteable, state: state, created_at: '2020-02-05') }
subject { described_class.from_event(event, resource: noteable, resource_parent: project) }
- it_behaves_like 'a system note', exclude_project: true do
- let(:action) { state.to_s }
+ it_behaves_like 'a synthetic note', state == 'reopened' ? 'opened' : state
+
+ it 'contains the expected values' do
+ expect(subject.author).to eq(author)
+ expect(subject.created_at).to eq(event.created_at)
+ expect(subject.note).to eq(state)
+ end
+ end
+ end
+
+ context 'with a mentionable source' do
+ subject { described_class.from_event(event, resource: noteable, resource_parent: project) }
+
+ context 'with a commit' do
+ let(:commit) { create(:commit, project: project) }
+ let(:event) { create(:resource_state_event, issue: noteable, state: :closed, created_at: '2020-02-05', source_commit: commit.id) }
+
+ it 'contains the expected values' do
+ expect(subject.author).to eq(author)
+ expect(subject.created_at).to eq(subject.created_at)
+ expect(subject.note).to eq("closed via commit #{commit.id}")
+ end
+ end
+
+ context 'with a merge request' do
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ let(:event) { create(:resource_state_event, issue: noteable, state: :closed, created_at: '2020-02-05', source_merge_request: merge_request) }
+
+ it 'contains the expected values' do
+ expect(subject.author).to eq(author)
+ expect(subject.created_at).to eq(event.created_at)
+ expect(subject.note).to eq("closed via merge request !#{merge_request.iid}")
+ end
+ end
+
+ context 'when closed by error tracking' do
+ let(:event) { create(:resource_state_event, issue: noteable, state: :closed, created_at: '2020-02-05', close_after_error_tracking_resolve: true) }
+
+ it 'contains the expected values' do
+ expect(subject.author).to eq(author)
+ expect(subject.created_at).to eq(event.created_at)
+ expect(subject.note).to eq('resolved the corresponding error and closed the issue.')
end
+ end
+
+ context 'when closed by promotheus alert' do
+ let(:event) { create(:resource_state_event, issue: noteable, state: :closed, created_at: '2020-02-05', close_auto_resolve_prometheus_alert: true) }
it 'contains the expected values' do
expect(subject.author).to eq(author)
expect(subject.created_at).to eq(event.created_at)
- expect(subject.note_html).to eq("<p dir=\"auto\">#{state}</p>")
+ expect(subject.note).to eq('automatically closed this issue because the alert resolved.')
end
end
end
diff --git a/spec/models/subscription_spec.rb b/spec/models/subscription_spec.rb
index 41bd48810b2..be85e6e10f4 100644
--- a/spec/models/subscription_spec.rb
+++ b/spec/models/subscription_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Subscription do
+RSpec.describe Subscription do
describe 'relationships' do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:subscribable) }
diff --git a/spec/models/suggestion_spec.rb b/spec/models/suggestion_spec.rb
index 2ac3ae0a5ad..6c30bc39c1d 100644
--- a/spec/models/suggestion_spec.rb
+++ b/spec/models/suggestion_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Suggestion do
+RSpec.describe Suggestion do
let(:suggestion) { create(:suggestion) }
describe 'associations' do
@@ -38,26 +38,106 @@ describe Suggestion do
end
describe '#appliable?' do
- context 'when patch is already applied' do
- let(:suggestion) { create(:suggestion, :applied) }
+ let(:suggestion) { build(:suggestion) }
- it 'returns false' do
- expect(suggestion).not_to be_appliable
+ subject(:appliable) { suggestion.appliable? }
+
+ before do
+ allow(suggestion).to receive(:inapplicable_reason).and_return(inapplicable_reason)
+ end
+
+ context 'when inapplicable_reason is nil' do
+ let(:inapplicable_reason) { nil }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when inapplicable_reason is not nil' do
+ let(:inapplicable_reason) { :applied }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#inapplicable_reason' do
+ let(:merge_request) { create(:merge_request) }
+
+ let!(:note) do
+ create(
+ :diff_note_on_merge_request,
+ project: merge_request.project,
+ noteable: merge_request
+ )
+ end
+
+ let(:suggestion) { build(:suggestion, note: note) }
+
+ subject(:inapplicable_reason) { suggestion.inapplicable_reason }
+
+ context 'when suggestion is already applied' do
+ let(:suggestion) { build(:suggestion, :applied, note: note) }
+
+ it { is_expected.to eq(:applied) }
+ end
+
+ context 'when merge request was merged' do
+ before do
+ merge_request.mark_as_merged!
+ end
+
+ it { is_expected.to eq(:merge_request_merged) }
+ end
+
+ context 'when merge request is closed' do
+ before do
+ merge_request.close!
end
+
+ it { is_expected.to eq(:merge_request_closed) }
end
- context 'when merge request is not opened' do
- let(:merge_request) { create(:merge_request, :merged) }
- let(:note) do
- create(:diff_note_on_merge_request, project: merge_request.project,
- noteable: merge_request)
+ context 'when source branch is deleted' do
+ before do
+ merge_request.project.repository.rm_branch(merge_request.author, merge_request.source_branch)
end
- let(:suggestion) { create(:suggestion, note: note) }
+ it { is_expected.to eq(:source_branch_deleted) }
+ end
- it 'returns false' do
- expect(suggestion).not_to be_appliable
+ context 'when content is outdated' do
+ before do
+ allow(suggestion).to receive(:outdated?).and_return(true)
+ end
+
+ it { is_expected.to eq(:outdated) }
+ end
+
+ context 'when note is outdated' do
+ before do
+ allow(note).to receive(:active?).and_return(false)
end
+
+ it { is_expected.to eq(:outdated) }
+ end
+
+ context 'when applicable' do
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '#single_line?' do
+ subject(:single_line) { suggestion.single_line? }
+
+ context 'when suggestion is for a single line' do
+ let(:suggestion) { build(:suggestion, lines_above: 0, lines_below: 0) }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when suggestion is for multiple lines' do
+ let(:suggestion) { build(:suggestion, lines_above: 2, lines_below: 0) }
+
+ it { is_expected.to eq(false) }
end
end
end
diff --git a/spec/models/system_note_metadata_spec.rb b/spec/models/system_note_metadata_spec.rb
index 801f139355b..9a6b57afb97 100644
--- a/spec/models/system_note_metadata_spec.rb
+++ b/spec/models/system_note_metadata_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SystemNoteMetadata do
+RSpec.describe SystemNoteMetadata do
describe 'associations' do
it { is_expected.to belong_to(:note) }
it { is_expected.to belong_to(:description_version) }
diff --git a/spec/models/term_agreement_spec.rb b/spec/models/term_agreement_spec.rb
index 42a48048b67..98c7a2daadd 100644
--- a/spec/models/term_agreement_spec.rb
+++ b/spec/models/term_agreement_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TermAgreement do
+RSpec.describe TermAgreement do
describe 'validations' do
it { is_expected.to validate_presence_of(:term) }
it { is_expected.to validate_presence_of(:user) }
diff --git a/spec/models/terraform/state_spec.rb b/spec/models/terraform/state_spec.rb
index 3cd15e23ee2..00e67ad70db 100644
--- a/spec/models/terraform/state_spec.rb
+++ b/spec/models/terraform/state_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Terraform::State do
+RSpec.describe Terraform::State do
subject { create(:terraform_state, :with_file) }
let(:terraform_state_file) { fixture_file('terraform/terraform.tfstate') }
diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
index bda89fc01f3..44e81455a67 100644
--- a/spec/models/todo_spec.rb
+++ b/spec/models/todo_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Todo do
+RSpec.describe Todo do
let(:issue) { create(:issue) }
describe 'relationships' do
diff --git a/spec/models/tree_spec.rb b/spec/models/tree_spec.rb
index 7dde8459f9a..1522d836f76 100644
--- a/spec/models/tree_spec.rb
+++ b/spec/models/tree_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Tree do
+RSpec.describe Tree do
let(:repository) { create(:project, :repository).repository }
let(:sha) { repository.root_ref }
diff --git a/spec/models/trending_project_spec.rb b/spec/models/trending_project_spec.rb
index 39f5d686eb4..802f8befbcd 100644
--- a/spec/models/trending_project_spec.rb
+++ b/spec/models/trending_project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TrendingProject do
+RSpec.describe TrendingProject do
let(:user) { create(:user) }
let(:public_project1) { create(:project, :public, :repository) }
let(:public_project2) { create(:project, :public, :repository) }
diff --git a/spec/models/upload_spec.rb b/spec/models/upload_spec.rb
index 8a64948d570..18388b4cd83 100644
--- a/spec/models/upload_spec.rb
+++ b/spec/models/upload_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Upload do
+RSpec.describe Upload do
describe 'associations' do
it { is_expected.to belong_to(:model) }
end
diff --git a/spec/models/uploads/fog_spec.rb b/spec/models/uploads/fog_spec.rb
index 72a169280af..899e6f2064c 100644
--- a/spec/models/uploads/fog_spec.rb
+++ b/spec/models/uploads/fog_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Uploads::Fog do
+RSpec.describe Uploads::Fog do
let(:data_store) { described_class.new }
before do
diff --git a/spec/models/uploads/local_spec.rb b/spec/models/uploads/local_spec.rb
index 374c3019edc..d354b252b39 100644
--- a/spec/models/uploads/local_spec.rb
+++ b/spec/models/uploads/local_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Uploads::Local do
+RSpec.describe Uploads::Local do
let(:data_store) { described_class.new }
before do
diff --git a/spec/models/user_agent_detail_spec.rb b/spec/models/user_agent_detail_spec.rb
index 5c28511b446..e3f3d9c342b 100644
--- a/spec/models/user_agent_detail_spec.rb
+++ b/spec/models/user_agent_detail_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserAgentDetail do
+RSpec.describe UserAgentDetail do
describe '.submittable?' do
it 'is submittable when not already submitted' do
detail = build(:user_agent_detail)
diff --git a/spec/models/user_callout_spec.rb b/spec/models/user_callout_spec.rb
index a084b1ac662..cdf70dd5190 100644
--- a/spec/models/user_callout_spec.rb
+++ b/spec/models/user_callout_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserCallout do
+RSpec.describe UserCallout do
let!(:callout) { create(:user_callout) }
it_behaves_like 'having unique enum values'
diff --git a/spec/models/user_canonical_email_spec.rb b/spec/models/user_canonical_email_spec.rb
index 54a4e968033..8e26f68c09b 100644
--- a/spec/models/user_canonical_email_spec.rb
+++ b/spec/models/user_canonical_email_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserCanonicalEmail do
+RSpec.describe UserCanonicalEmail do
it { is_expected.to belong_to(:user) }
describe 'validations' do
diff --git a/spec/models/user_custom_attribute_spec.rb b/spec/models/user_custom_attribute_spec.rb
index d0981b2d771..1a51ad662b0 100644
--- a/spec/models/user_custom_attribute_spec.rb
+++ b/spec/models/user_custom_attribute_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserCustomAttribute do
+RSpec.describe UserCustomAttribute do
describe 'assocations' do
it { is_expected.to belong_to(:user) }
end
diff --git a/spec/models/user_detail_spec.rb b/spec/models/user_detail_spec.rb
index 2b2bfff7be2..041af5b9c31 100644
--- a/spec/models/user_detail_spec.rb
+++ b/spec/models/user_detail_spec.rb
@@ -2,13 +2,42 @@
require 'spec_helper'
-describe UserDetail do
+RSpec.describe UserDetail do
it { is_expected.to belong_to(:user) }
describe 'validations' do
- describe 'job_title' do
+ describe '#job_title' do
it { is_expected.not_to validate_presence_of(:job_title) }
it { is_expected.to validate_length_of(:job_title).is_at_most(200) }
end
+
+ describe '#bio' do
+ it { is_expected.to validate_length_of(:bio).is_at_most(255) }
+ end
+ end
+
+ describe '#bio_html' do
+ let(:user) { create(:user, bio: 'some **bio**') }
+
+ subject { user.user_detail.bio_html }
+
+ it 'falls back to #bio when the html representation is missing' do
+ user.user_detail.update!(bio_html: nil)
+
+ expect(subject).to eq(user.user_detail.bio)
+ end
+
+ it 'stores rendered html' do
+ expect(subject).to include('some <strong>bio</strong>')
+ end
+
+ it 'does not try to set the value when the column is not there' do
+ without_bio_html_column = UserDetail.column_names - ['bio_html']
+
+ expect(described_class).to receive(:column_names).at_least(:once).and_return(without_bio_html_column)
+ expect(user.user_detail).not_to receive(:bio_html=)
+
+ subject
+ end
end
end
diff --git a/spec/models/user_highest_role_spec.rb b/spec/models/user_highest_role_spec.rb
index b3c795f6623..3ae672cf7f7 100644
--- a/spec/models/user_highest_role_spec.rb
+++ b/spec/models/user_highest_role_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserHighestRole do
+RSpec.describe UserHighestRole do
describe 'associations' do
it { is_expected.to belong_to(:user).required }
end
diff --git a/spec/models/user_interacted_project_spec.rb b/spec/models/user_interacted_project_spec.rb
index 83c66bf1969..2fec8be76e8 100644
--- a/spec/models/user_interacted_project_spec.rb
+++ b/spec/models/user_interacted_project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserInteractedProject do
+RSpec.describe UserInteractedProject do
describe '.track' do
subject { described_class.track(event) }
diff --git a/spec/models/user_mentions/commit_user_mention_spec.rb b/spec/models/user_mentions/commit_user_mention_spec.rb
index ebad3902d6b..91d28241650 100644
--- a/spec/models/user_mentions/commit_user_mention_spec.rb
+++ b/spec/models/user_mentions/commit_user_mention_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CommitUserMention do
+RSpec.describe CommitUserMention do
describe 'associations' do
it { is_expected.to belong_to(:note) }
end
diff --git a/spec/models/user_mentions/issue_user_mention_spec.rb b/spec/models/user_mentions/issue_user_mention_spec.rb
index ac29f3084b4..6faf598ee36 100644
--- a/spec/models/user_mentions/issue_user_mention_spec.rb
+++ b/spec/models/user_mentions/issue_user_mention_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssueUserMention do
+RSpec.describe IssueUserMention do
describe 'associations' do
it { is_expected.to belong_to(:issue) }
it { is_expected.to belong_to(:note) }
diff --git a/spec/models/user_mentions/merge_request_user_mention_spec.rb b/spec/models/user_mentions/merge_request_user_mention_spec.rb
index c5c7cebfaa5..10fcb126965 100644
--- a/spec/models/user_mentions/merge_request_user_mention_spec.rb
+++ b/spec/models/user_mentions/merge_request_user_mention_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestUserMention do
+RSpec.describe MergeRequestUserMention do
describe 'associations' do
it { is_expected.to belong_to(:merge_request) }
it { is_expected.to belong_to(:note) }
diff --git a/spec/models/user_mentions/snippet_user_mention_spec.rb b/spec/models/user_mentions/snippet_user_mention_spec.rb
index 0e34a2dd5a1..0762e731a53 100644
--- a/spec/models/user_mentions/snippet_user_mention_spec.rb
+++ b/spec/models/user_mentions/snippet_user_mention_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SnippetUserMention do
+RSpec.describe SnippetUserMention do
describe 'associations' do
it { is_expected.to belong_to(:snippet) }
it { is_expected.to belong_to(:note) }
diff --git a/spec/models/user_preference_spec.rb b/spec/models/user_preference_spec.rb
index cf32d4eeca7..27ddaea763d 100644
--- a/spec/models/user_preference_spec.rb
+++ b/spec/models/user_preference_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserPreference do
+RSpec.describe UserPreference do
let(:user_preference) { create(:user_preference) }
describe 'notes filters global keys' do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index dd4b174a38f..fa2e4b63648 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe User do
+RSpec.describe User do
include ProjectForksHelper
include TermsHelper
include ExclusiveLeaseHelpers
@@ -58,6 +58,10 @@ describe User do
it { is_expected.to delegate_method(:job_title).to(:user_detail).allow_nil }
it { is_expected.to delegate_method(:job_title=).to(:user_detail).with_arguments(:args).allow_nil }
+
+ it { is_expected.to delegate_method(:bio).to(:user_detail).allow_nil }
+ it { is_expected.to delegate_method(:bio=).to(:user_detail).with_arguments(:args).allow_nil }
+ it { is_expected.to delegate_method(:bio_html).to(:user_detail).allow_nil }
end
describe 'associations' do
@@ -91,64 +95,28 @@ describe User do
it { is_expected.to have_many(:metrics_users_starred_dashboards).inverse_of(:user) }
it { is_expected.to have_many(:reviews).inverse_of(:author) }
- describe "#bio" do
- it 'syncs bio with `user_details.bio` on create' do
- user = create(:user, bio: 'my bio')
-
- expect(user.bio).to eq(user.user_detail.bio)
+ describe "#user_detail" do
+ it 'does not persist `user_detail` by default' do
+ expect(create(:user).user_detail).not_to be_persisted
end
- context 'when `migrate_bio_to_user_details` feature flag is off' do
- before do
- stub_feature_flags(migrate_bio_to_user_details: false)
- end
-
- it 'does not sync bio with `user_details.bio`' do
- user = create(:user, bio: 'my bio')
+ it 'creates `user_detail` when `bio` is given' do
+ user = create(:user, bio: 'my bio')
- expect(user.bio).to eq('my bio')
- expect(user.user_detail.bio).to eq('')
- end
+ expect(user.user_detail).to be_persisted
+ expect(user.user_detail.bio).to eq('my bio')
end
- it 'syncs bio with `user_details.bio` on update' do
- user = create(:user)
-
- user.update!(bio: 'my bio')
+ it 'delegates `bio` to `user_detail`' do
+ user = create(:user, bio: 'my bio')
expect(user.bio).to eq(user.user_detail.bio)
end
- context 'when `user_details` association already exists' do
- let(:user) { create(:user) }
-
- before do
- create(:user_detail, user: user)
- end
-
- it 'syncs bio with `user_details.bio`' do
- user.update!(bio: 'my bio')
-
- expect(user.bio).to eq(user.user_detail.bio)
- end
-
- it 'falls back to "" when nil is given' do
- user.update!(bio: nil)
-
- expect(user.bio).to eq(nil)
- expect(user.user_detail.bio).to eq('')
- end
-
- # very unlikely scenario
- it 'truncates long bio when syncing to user_details' do
- invalid_bio = 'a' * 256
- truncated_bio = 'a' * 255
-
- user.bio = invalid_bio
- user.save(validate: false)
+ it 'creates `user_detail` when `bio` is first updated' do
+ user = create(:user)
- expect(user.user_detail.bio).to eq(truncated_bio)
- end
+ expect { user.update(bio: 'my bio') }.to change { user.user_detail.persisted? }.from(false).to(true)
end
end
@@ -214,7 +182,7 @@ describe User do
describe 'validations' do
describe 'password' do
- let!(:user) { create(:user) }
+ let!(:user) { build_stubbed(:user) }
before do
allow(Devise).to receive(:password_length).and_return(8..128)
@@ -337,8 +305,6 @@ describe User do
it { is_expected.not_to allow_value(-1).for(:projects_limit) }
it { is_expected.not_to allow_value(Gitlab::Database::MAX_INT_VALUE + 1).for(:projects_limit) }
- it { is_expected.to validate_length_of(:bio).is_at_most(255) }
-
it_behaves_like 'an object with email-formated attributes', :email do
subject { build(:user) }
end
@@ -3745,6 +3711,12 @@ describe User do
expect(user.namespace).not_to be_nil
end
+
+ it 'creates the namespace setting' do
+ user.save!
+
+ expect(user.namespace.namespace_settings).to be_persisted
+ end
end
context 'for an existing user' do
@@ -4634,7 +4606,8 @@ describe User do
[
{ state: 'blocked' },
{ user_type: :ghost },
- { user_type: :alert_bot }
+ { user_type: :alert_bot },
+ { user_type: :support_bot }
]
end
@@ -4688,6 +4661,7 @@ describe User do
where(:user_type, :expected_result) do
'human' | true
'alert_bot' | false
+ 'support_bot' | false
end
with_them do
@@ -4756,19 +4730,44 @@ describe User do
end
end
- describe '#migration_bot' do
- it 'creates the user if it does not exist' do
- expect do
- described_class.migration_bot
- end.to change { User.where(user_type: :migration_bot).count }.by(1)
+ context 'bot users' do
+ shared_examples 'bot users' do |bot_type|
+ it 'creates the user if it does not exist' do
+ expect do
+ described_class.public_send(bot_type)
+ end.to change { User.where(user_type: bot_type).count }.by(1)
+ end
+
+ it 'creates a route for the namespace of the created user' do
+ bot_user = described_class.public_send(bot_type)
+
+ expect(bot_user.namespace.route).to be_present
+ end
+
+ it 'does not create a new user if it already exists' do
+ described_class.public_send(bot_type)
+
+ expect do
+ described_class.public_send(bot_type)
+ end.not_to change { User.count }
+ end
end
- it 'does not create a new user if it already exists' do
- described_class.migration_bot
+ shared_examples 'bot user avatars' do |bot_type, avatar_filename|
+ it 'sets the custom avatar for the created bot' do
+ bot_user = described_class.public_send(bot_type)
- expect do
- described_class.migration_bot
- end.not_to change { User.count }
+ expect(bot_user.avatar.url).to be_present
+ expect(bot_user.avatar.filename).to eq(avatar_filename)
+ end
end
+
+ it_behaves_like 'bot users', :alert_bot
+ it_behaves_like 'bot users', :support_bot
+ it_behaves_like 'bot users', :migration_bot
+ it_behaves_like 'bot users', :ghost
+
+ it_behaves_like 'bot user avatars', :alert_bot, 'alert-bot.png'
+ it_behaves_like 'bot user avatars', :support_bot, 'support-bot.png'
end
end
diff --git a/spec/models/user_status_spec.rb b/spec/models/user_status_spec.rb
index fcc01cdae3d..2c0664bd165 100644
--- a/spec/models/user_status_spec.rb
+++ b/spec/models/user_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserStatus do
+RSpec.describe UserStatus do
it { is_expected.to validate_presence_of(:user) }
it { is_expected.to allow_value('smirk').for(:emoji) }
diff --git a/spec/models/users_statistics_spec.rb b/spec/models/users_statistics_spec.rb
index 4437a5469c6..b4b7ddb7c63 100644
--- a/spec/models/users_statistics_spec.rb
+++ b/spec/models/users_statistics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UsersStatistics do
+RSpec.describe UsersStatistics do
let(:users_statistics) { build(:users_statistics) }
describe 'scopes' do
diff --git a/spec/models/web_ide_terminal_spec.rb b/spec/models/web_ide_terminal_spec.rb
index 4103a26c75a..149fce33f43 100644
--- a/spec/models/web_ide_terminal_spec.rb
+++ b/spec/models/web_ide_terminal_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WebIdeTerminal do
+RSpec.describe WebIdeTerminal do
let(:build) { create(:ci_build) }
subject { described_class.new(build) }
diff --git a/spec/models/wiki_page/meta_spec.rb b/spec/models/wiki_page/meta_spec.rb
index 0255dd802cf..aaac72cbc68 100644
--- a/spec/models/wiki_page/meta_spec.rb
+++ b/spec/models/wiki_page/meta_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WikiPage::Meta do
+RSpec.describe WikiPage::Meta do
let_it_be(:project) { create(:project, :wiki_repo) }
let_it_be(:other_project) { create(:project) }
diff --git a/spec/models/wiki_page/slug_spec.rb b/spec/models/wiki_page/slug_spec.rb
index 324dea6b320..cf256c67277 100644
--- a/spec/models/wiki_page/slug_spec.rb
+++ b/spec/models/wiki_page/slug_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WikiPage::Slug do
+RSpec.describe WikiPage::Slug do
let_it_be(:meta) { create(:wiki_page_meta) }
describe 'Associations' do
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index 8f2da8ff9a1..a2ca6441f28 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe WikiPage do
+RSpec.describe WikiPage do
let_it_be(:user) { create(:user) }
let(:container) { create(:project, :wiki_repo) }
let(:wiki) { Wiki.for_container(container, user) }
@@ -864,6 +864,24 @@ describe WikiPage do
end
end
+ describe '#diffs' do
+ subject { existing_page }
+
+ it 'returns a diff instance' do
+ diffs = subject.diffs(foo: 'bar')
+
+ expect(diffs).to be_a(Gitlab::Diff::FileCollection::WikiPage)
+ expect(diffs.diffable).to be_a(Commit)
+ expect(diffs.diffable.id).to eq(subject.version.id)
+ expect(diffs.project).to be(subject.wiki)
+ expect(diffs.diff_options).to include(
+ expanded: true,
+ paths: [subject.path],
+ foo: 'bar'
+ )
+ end
+ end
+
private
def get_slugs(page_or_dir)
diff --git a/spec/models/zoom_meeting_spec.rb b/spec/models/zoom_meeting_spec.rb
index 3dad957a1ce..00a0f92e848 100644
--- a/spec/models/zoom_meeting_spec.rb
+++ b/spec/models/zoom_meeting_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ZoomMeeting do
+RSpec.describe ZoomMeeting do
let(:project) { build(:project) }
describe 'Factory' do
diff --git a/spec/policies/alert_management/alert_policy_spec.rb b/spec/policies/alert_management/alert_policy_spec.rb
index 0d7624a0142..3e08d8b4ccc 100644
--- a/spec/policies/alert_management/alert_policy_spec.rb
+++ b/spec/policies/alert_management/alert_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AlertManagement::AlertPolicy, :models do
+RSpec.describe AlertManagement::AlertPolicy, :models do
let(:alert) { create(:alert_management_alert) }
let(:project) { alert.project }
let(:user) { create(:user) }
diff --git a/spec/policies/application_setting/term_policy_spec.rb b/spec/policies/application_setting/term_policy_spec.rb
index 2b5b9758ec2..00b48402fa6 100644
--- a/spec/policies/application_setting/term_policy_spec.rb
+++ b/spec/policies/application_setting/term_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ApplicationSetting::TermPolicy do
+RSpec.describe ApplicationSetting::TermPolicy do
include TermsHelper
let_it_be(:term) { create(:term) }
diff --git a/spec/policies/award_emoji_policy_spec.rb b/spec/policies/award_emoji_policy_spec.rb
index 2e3693c58d7..bd34a656e12 100644
--- a/spec/policies/award_emoji_policy_spec.rb
+++ b/spec/policies/award_emoji_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AwardEmojiPolicy do
+RSpec.describe AwardEmojiPolicy do
let(:user) { create(:user) }
let(:award_emoji) { create(:award_emoji, awardable: awardable) }
diff --git a/spec/policies/base_policy_spec.rb b/spec/policies/base_policy_spec.rb
index 67f7452528a..103f2e9bc39 100644
--- a/spec/policies/base_policy_spec.rb
+++ b/spec/policies/base_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BasePolicy do
+RSpec.describe BasePolicy do
include ExternalAuthorizationServiceHelpers
include AdminModeHelper
diff --git a/spec/policies/blob_policy_spec.rb b/spec/policies/blob_policy_spec.rb
index e48dd751a8f..fc46b25f25c 100644
--- a/spec/policies/blob_policy_spec.rb
+++ b/spec/policies/blob_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobPolicy, :enable_admin_mode do
+RSpec.describe BlobPolicy, :enable_admin_mode do
include_context 'ProjectPolicyTable context'
include ProjectHelpers
using RSpec::Parameterized::TableSyntax
diff --git a/spec/policies/board_policy_spec.rb b/spec/policies/board_policy_spec.rb
index 35eac8a02c4..6940e75ec37 100644
--- a/spec/policies/board_policy_spec.rb
+++ b/spec/policies/board_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BoardPolicy do
+RSpec.describe BoardPolicy do
let(:user) { create(:user) }
let(:project) { create(:project, :private) }
let(:group) { create(:group, :private) }
diff --git a/spec/policies/ci/build_policy_spec.rb b/spec/policies/ci/build_policy_spec.rb
index 5857369a550..d2547338855 100644
--- a/spec/policies/ci/build_policy_spec.rb
+++ b/spec/policies/ci/build_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildPolicy do
+RSpec.describe Ci::BuildPolicy do
let(:user) { create(:user) }
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
diff --git a/spec/policies/ci/pipeline_policy_spec.rb b/spec/policies/ci/pipeline_policy_spec.rb
index 293fe1fc5b9..fcd96bc6653 100644
--- a/spec/policies/ci/pipeline_policy_spec.rb
+++ b/spec/policies/ci/pipeline_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelinePolicy, :models do
+RSpec.describe Ci::PipelinePolicy, :models do
let(:user) { create(:user) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
diff --git a/spec/policies/ci/pipeline_schedule_policy_spec.rb b/spec/policies/ci/pipeline_schedule_policy_spec.rb
index d503401f7cf..b455384d17a 100644
--- a/spec/policies/ci/pipeline_schedule_policy_spec.rb
+++ b/spec/policies/ci/pipeline_schedule_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelineSchedulePolicy, :models do
+RSpec.describe Ci::PipelineSchedulePolicy, :models do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:pipeline_schedule, reload: true) { create(:ci_pipeline_schedule, :nightly, project: project) }
diff --git a/spec/policies/ci/trigger_policy_spec.rb b/spec/policies/ci/trigger_policy_spec.rb
index 28e5a2b2cd6..b8b54e57035 100644
--- a/spec/policies/ci/trigger_policy_spec.rb
+++ b/spec/policies/ci/trigger_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::TriggerPolicy do
+RSpec.describe Ci::TriggerPolicy do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:trigger) { create(:ci_trigger, project: project, owner: create(:user)) }
diff --git a/spec/policies/clusters/cluster_policy_spec.rb b/spec/policies/clusters/cluster_policy_spec.rb
index 26cfc19862a..0b931c6f927 100644
--- a/spec/policies/clusters/cluster_policy_spec.rb
+++ b/spec/policies/clusters/cluster_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::ClusterPolicy, :models do
+RSpec.describe Clusters::ClusterPolicy, :models do
let(:cluster) { create(:cluster, :project) }
let(:project) { cluster.project }
let(:user) { create(:user) }
diff --git a/spec/policies/clusters/instance_policy_spec.rb b/spec/policies/clusters/instance_policy_spec.rb
index dfe480d7fa4..f90841fc311 100644
--- a/spec/policies/clusters/instance_policy_spec.rb
+++ b/spec/policies/clusters/instance_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::InstancePolicy do
+RSpec.describe Clusters::InstancePolicy do
let(:user) { create(:user) }
let(:policy) { described_class.new(user, Clusters::Instance.new) }
diff --git a/spec/policies/commit_policy_spec.rb b/spec/policies/commit_policy_spec.rb
index 40183f51e9e..0d3dcc97565 100644
--- a/spec/policies/commit_policy_spec.rb
+++ b/spec/policies/commit_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CommitPolicy do
+RSpec.describe CommitPolicy do
describe '#rules' do
let(:user) { create(:user) }
let(:commit) { project.repository.head_commit }
diff --git a/spec/policies/concerns/policy_actor_spec.rb b/spec/policies/concerns/policy_actor_spec.rb
index 27db9710a38..7271cbb4a9d 100644
--- a/spec/policies/concerns/policy_actor_spec.rb
+++ b/spec/policies/concerns/policy_actor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PolicyActor do
+RSpec.describe PolicyActor do
it 'implements all the methods from user' do
methods = subject.instance_methods
diff --git a/spec/policies/deploy_key_policy_spec.rb b/spec/policies/deploy_key_policy_spec.rb
index 545647e2c67..d84b80a8738 100644
--- a/spec/policies/deploy_key_policy_spec.rb
+++ b/spec/policies/deploy_key_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeployKeyPolicy do
+RSpec.describe DeployKeyPolicy do
subject { described_class.new(current_user, deploy_key) }
describe 'updating a deploy_key' do
diff --git a/spec/policies/deploy_keys_project_policy_spec.rb b/spec/policies/deploy_keys_project_policy_spec.rb
index 952da86b7a7..3be55e9238c 100644
--- a/spec/policies/deploy_keys_project_policy_spec.rb
+++ b/spec/policies/deploy_keys_project_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeployKeysProjectPolicy do
+RSpec.describe DeployKeysProjectPolicy do
subject { described_class.new(current_user, deploy_key.deploy_keys_project_for(project)) }
describe 'updating a deploy_keys_project' do
diff --git a/spec/policies/deploy_token_policy_spec.rb b/spec/policies/deploy_token_policy_spec.rb
index 43e23ee55ac..f218828052e 100644
--- a/spec/policies/deploy_token_policy_spec.rb
+++ b/spec/policies/deploy_token_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeployTokenPolicy do
+RSpec.describe DeployTokenPolicy do
let(:current_user) { create(:user) }
let(:project) { create(:project) }
let(:deploy_token) { create(:deploy_token, projects: [project]) }
diff --git a/spec/policies/design_management/design_policy_spec.rb b/spec/policies/design_management/design_policy_spec.rb
index a566aecc4b7..5dde5f896c9 100644
--- a/spec/policies/design_management/design_policy_spec.rb
+++ b/spec/policies/design_management/design_policy_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe DesignManagement::DesignPolicy do
+RSpec.describe DesignManagement::DesignPolicy do
include DesignManagementTestHelpers
include_context 'ProjectPolicy context'
diff --git a/spec/policies/environment_policy_spec.rb b/spec/policies/environment_policy_spec.rb
index 75fca464ec8..649b1a770c0 100644
--- a/spec/policies/environment_policy_spec.rb
+++ b/spec/policies/environment_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EnvironmentPolicy do
+RSpec.describe EnvironmentPolicy do
using RSpec::Parameterized::TableSyntax
let(:user) { create(:user) }
diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb
index e8ba4eed4ec..4954eafe338 100644
--- a/spec/policies/global_policy_spec.rb
+++ b/spec/policies/global_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GlobalPolicy do
+RSpec.describe GlobalPolicy do
include TermsHelper
let_it_be(:project_bot) { create(:user, :project_bot) }
@@ -130,6 +130,24 @@ describe GlobalPolicy do
end
end
+ describe 'using project statistics filters' do
+ context 'regular user' do
+ it { is_expected.not_to be_allowed(:use_project_statistics_filters) }
+ end
+
+ context 'admin' do
+ let(:current_user) { create(:user, :admin) }
+
+ context 'when admin mode is enabled', :enable_admin_mode do
+ it { is_expected.to be_allowed(:use_project_statistics_filters) }
+ end
+
+ context 'when admin mode is disabled' do
+ it { is_expected.to be_disallowed(:use_project_statistics_filters) }
+ end
+ end
+ end
+
shared_examples 'access allowed when terms accepted' do |ability|
it { is_expected.not_to be_allowed(ability) }
diff --git a/spec/policies/group_member_policy_spec.rb b/spec/policies/group_member_policy_spec.rb
index a4f3301a064..4215fa09301 100644
--- a/spec/policies/group_member_policy_spec.rb
+++ b/spec/policies/group_member_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupMemberPolicy do
+RSpec.describe GroupMemberPolicy do
let(:guest) { create(:user) }
let(:owner) { create(:user) }
let(:group) { create(:group, :private) }
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index 6b17a8285a2..733cc9bd9cb 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupPolicy do
+RSpec.describe GroupPolicy do
include_context 'GroupPolicy context'
context 'public group with no user' do
@@ -154,7 +154,7 @@ describe GroupPolicy do
context 'admin' do
let(:current_user) { admin }
- it do
+ specify do
expect_allowed(*read_group_permissions)
expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
@@ -162,6 +162,10 @@ describe GroupPolicy do
expect_allowed(*maintainer_permissions)
expect_allowed(*owner_permissions)
end
+
+ context 'with admin mode', :enable_admin_mode do
+ specify { expect_allowed(*admin_permissions) }
+ end
end
describe 'private nested group use the highest access level from the group and inherited permissions' do
@@ -661,4 +665,61 @@ describe GroupPolicy do
end
end
end
+
+ describe 'design activity' do
+ let_it_be(:group) { create(:group, :public) }
+ let(:current_user) { nil }
+
+ subject { described_class.new(current_user, group) }
+
+ context 'when design management is not available' do
+ it { is_expected.not_to be_allowed(:read_design_activity) }
+
+ context 'even when there are projects in the group' do
+ before do
+ create_list(:project_group_link, 2, group: group)
+ end
+
+ it { is_expected.not_to be_allowed(:read_design_activity) }
+ end
+ end
+
+ context 'when design management is available globally' do
+ include DesignManagementTestHelpers
+
+ before do
+ enable_design_management
+ end
+
+ context 'the group has no projects' do
+ it { is_expected.not_to be_allowed(:read_design_activity) }
+ end
+
+ context 'the group has a project' do
+ let(:project) { create(:project, :public) }
+
+ before do
+ create(:project_group_link, project: project, group: group)
+ end
+
+ it { is_expected.to be_allowed(:read_design_activity) }
+
+ context 'which does not have design management enabled' do
+ before do
+ project.update(lfs_enabled: false)
+ end
+
+ it { is_expected.not_to be_allowed(:read_design_activity) }
+
+ context 'but another project does' do
+ before do
+ create(:project_group_link, project: create(:project, :public), group: group)
+ end
+
+ it { is_expected.to be_allowed(:read_design_activity) }
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/policies/identity_provider_policy_spec.rb b/spec/policies/identity_provider_policy_spec.rb
index 52b6d2c89ba..f6b4e15cff9 100644
--- a/spec/policies/identity_provider_policy_spec.rb
+++ b/spec/policies/identity_provider_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IdentityProviderPolicy do
+RSpec.describe IdentityProviderPolicy do
subject(:policy) { described_class.new(user, provider) }
let(:user) { User.new }
diff --git a/spec/policies/issuable_policy_spec.rb b/spec/policies/issuable_policy_spec.rb
index 18e35308ecd..20eb09e11c9 100644
--- a/spec/policies/issuable_policy_spec.rb
+++ b/spec/policies/issuable_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssuablePolicy, models: true do
+RSpec.describe IssuablePolicy, models: true do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project) }
diff --git a/spec/policies/issue_policy_spec.rb b/spec/policies/issue_policy_spec.rb
index 9d52079e4be..b3ca37b17c2 100644
--- a/spec/policies/issue_policy_spec.rb
+++ b/spec/policies/issue_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssuePolicy do
+RSpec.describe IssuePolicy do
include ExternalAuthorizationServiceHelpers
let(:guest) { create(:user) }
diff --git a/spec/policies/merge_request_policy_spec.rb b/spec/policies/merge_request_policy_spec.rb
index 31ced5db953..2f3cb2e998a 100644
--- a/spec/policies/merge_request_policy_spec.rb
+++ b/spec/policies/merge_request_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestPolicy do
+RSpec.describe MergeRequestPolicy do
include ExternalAuthorizationServiceHelpers
let(:guest) { create(:user) }
@@ -24,6 +24,7 @@ describe MergeRequestPolicy do
mr_perms = %i[create_merge_request_in
create_merge_request_from
read_merge_request
+ approve_merge_request
create_note].freeze
shared_examples_for 'a denied user' do
diff --git a/spec/policies/metrics/dashboard/annotation_policy_spec.rb b/spec/policies/metrics/dashboard/annotation_policy_spec.rb
index 4dc5f4cd0b4..0c59b39ae3e 100644
--- a/spec/policies/metrics/dashboard/annotation_policy_spec.rb
+++ b/spec/policies/metrics/dashboard/annotation_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::AnnotationPolicy, :models do
+RSpec.describe Metrics::Dashboard::AnnotationPolicy, :models do
shared_examples 'metrics dashboard annotation policy' do
context 'when guest' do
before do
diff --git a/spec/policies/namespace/root_storage_statistics_policy_spec.rb b/spec/policies/namespace/root_storage_statistics_policy_spec.rb
index 8d53050fffb..e6b58bca4a8 100644
--- a/spec/policies/namespace/root_storage_statistics_policy_spec.rb
+++ b/spec/policies/namespace/root_storage_statistics_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Namespace::RootStorageStatisticsPolicy do
+RSpec.describe Namespace::RootStorageStatisticsPolicy do
using RSpec::Parameterized::TableSyntax
describe '#rules' do
diff --git a/spec/policies/namespace_policy_spec.rb b/spec/policies/namespace_policy_spec.rb
index 01162dc0fc4..f2f411e48d6 100644
--- a/spec/policies/namespace_policy_spec.rb
+++ b/spec/policies/namespace_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NamespacePolicy do
+RSpec.describe NamespacePolicy do
let(:user) { create(:user) }
let(:owner) { create(:user) }
let(:admin) { create(:admin) }
diff --git a/spec/policies/note_policy_spec.rb b/spec/policies/note_policy_spec.rb
index 1e3bd0d9147..a4cc3a1e9af 100644
--- a/spec/policies/note_policy_spec.rb
+++ b/spec/policies/note_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NotePolicy do
+RSpec.describe NotePolicy do
describe '#rules' do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
diff --git a/spec/policies/packages/package_policy_spec.rb b/spec/policies/packages/package_policy_spec.rb
new file mode 100644
index 00000000000..13935974b44
--- /dev/null
+++ b/spec/policies/packages/package_policy_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::PackagePolicy do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:package) { create(:package, project: project) }
+
+ subject(:policy) { described_class.new(user, package) }
+
+ context 'when the user is part of the project' do
+ before do
+ project.add_reporter(user)
+ end
+
+ it 'allows read_package' do
+ expect(policy).to be_allowed(:read_package)
+ end
+ end
+
+ context 'when the user is not part of the project' do
+ it 'disallows read_package for any Package' do
+ expect(policy).to be_disallowed(:read_package)
+ end
+ end
+end
diff --git a/spec/policies/personal_snippet_policy_spec.rb b/spec/policies/personal_snippet_policy_spec.rb
index 5fc48717d86..d546805ce01 100644
--- a/spec/policies/personal_snippet_policy_spec.rb
+++ b/spec/policies/personal_snippet_policy_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
# Snippet visibility scenarios are included in more details in spec/support/snippet_visibility.rb
-describe PersonalSnippetPolicy do
+RSpec.describe PersonalSnippetPolicy do
let(:regular_user) { create(:user) }
let(:external_user) { create(:user, :external) }
let(:admin_user) { create(:user, :admin) }
diff --git a/spec/policies/project_member_policy_spec.rb b/spec/policies/project_member_policy_spec.rb
new file mode 100644
index 00000000000..ab8f8b83e7f
--- /dev/null
+++ b/spec/policies/project_member_policy_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ProjectMemberPolicy do
+ let(:project) { create(:project) }
+ let(:maintainer_user) { create(:user) }
+ let(:member) { create(:project_member, project: project, user: member_user) }
+
+ subject { described_class.new(maintainer_user, member) }
+
+ before do
+ create(:project_member, :maintainer, project: project, user: maintainer_user)
+ end
+
+ context 'with regular member' do
+ let(:member_user) { create(:user) }
+
+ it { is_expected.to be_allowed(:update_project_member) }
+ it { is_expected.to be_allowed(:destroy_project_member) }
+
+ it { is_expected.not_to be_allowed(:destroy_project_bot_member) }
+ end
+
+ context 'with a bot member' do
+ let(:member_user) { create(:user, :project_bot) }
+
+ it { is_expected.to be_allowed(:destroy_project_bot_member) }
+
+ it { is_expected.not_to be_allowed(:update_project_member) }
+ it { is_expected.not_to be_allowed(:destroy_project_member) }
+ end
+end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 6ec63ba61ca..dc6ed94309b 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectPolicy do
+RSpec.describe ProjectPolicy do
include ExternalAuthorizationServiceHelpers
include_context 'ProjectPolicy context'
let_it_be(:other_user) { create(:user) }
@@ -30,7 +30,7 @@ describe ProjectPolicy do
admin_issue admin_label admin_list read_commit_status read_build
read_container_image read_pipeline read_environment read_deployment
read_merge_request download_wiki_code read_sentry_issue read_metrics_dashboard_annotation
- metrics_dashboard
+ metrics_dashboard read_confidential_issues
]
end
@@ -46,6 +46,7 @@ describe ProjectPolicy do
resolve_note create_container_image update_container_image destroy_container_image daily_statistics
create_environment update_environment create_deployment update_deployment create_release update_release
create_metrics_dashboard_annotation delete_metrics_dashboard_annotation update_metrics_dashboard_annotation
+ read_terraform_state
]
end
@@ -496,6 +497,33 @@ describe ProjectPolicy do
end
end
+ context 'support bot' do
+ let(:current_user) { User.support_bot }
+
+ subject { described_class.new(current_user, project) }
+
+ context 'with service desk disabled' do
+ it { expect_allowed(:guest_access) }
+ it { expect_disallowed(:create_note, :read_project) }
+ end
+
+ context 'with service desk enabled' do
+ before do
+ allow(project).to receive(:service_desk_enabled?).and_return(true)
+ end
+
+ it { expect_allowed(:reporter_access, :create_note, :read_issue) }
+
+ context 'when issues are protected members only' do
+ before do
+ project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
+ end
+
+ it { expect_allowed(:reporter_access, :create_note, :read_issue) }
+ end
+ end
+ end
+
describe 'read_prometheus_alerts' do
subject { described_class.new(current_user, project) }
@@ -855,6 +883,28 @@ describe ProjectPolicy do
end
end
+ describe 'design permissions' do
+ subject { described_class.new(guest, project) }
+
+ let(:design_permissions) do
+ %i[read_design_activity read_design]
+ end
+
+ context 'when design management is not available' do
+ it { is_expected.not_to be_allowed(*design_permissions) }
+ end
+
+ context 'when design management is available' do
+ include DesignManagementTestHelpers
+
+ before do
+ enable_design_management
+ end
+
+ it { is_expected.to be_allowed(*design_permissions) }
+ end
+ end
+
describe 'read_build_report_results' do
subject { described_class.new(guest, project) }
@@ -892,4 +942,64 @@ describe ProjectPolicy do
it { is_expected.to be_disallowed(:read_build_report_results) }
end
end
+
+ describe 'read_package' do
+ subject { described_class.new(current_user, project) }
+
+ context 'with admin' do
+ let(:current_user) { admin }
+
+ it { is_expected.to be_allowed(:read_package) }
+
+ context 'when repository is disabled' do
+ before do
+ project.project_feature.update(repository_access_level: ProjectFeature::DISABLED)
+ end
+
+ it { is_expected.to be_disallowed(:read_package) }
+ end
+ end
+
+ context 'with owner' do
+ let(:current_user) { owner }
+
+ it { is_expected.to be_allowed(:read_package) }
+ end
+
+ context 'with maintainer' do
+ let(:current_user) { maintainer }
+
+ it { is_expected.to be_allowed(:read_package) }
+ end
+
+ context 'with developer' do
+ let(:current_user) { developer }
+
+ it { is_expected.to be_allowed(:read_package) }
+ end
+
+ context 'with reporter' do
+ let(:current_user) { reporter }
+
+ it { is_expected.to be_allowed(:read_package) }
+ end
+
+ context 'with guest' do
+ let(:current_user) { guest }
+
+ it { is_expected.to be_allowed(:read_package) }
+ end
+
+ context 'with non member' do
+ let(:current_user) { create(:user) }
+
+ it { is_expected.to be_allowed(:read_package) }
+ end
+
+ context 'with anonymous' do
+ let(:current_user) { nil }
+
+ it { is_expected.to be_allowed(:read_package) }
+ end
+ end
end
diff --git a/spec/policies/project_snippet_policy_spec.rb b/spec/policies/project_snippet_policy_spec.rb
index 3864666f587..bdf9eaedbf1 100644
--- a/spec/policies/project_snippet_policy_spec.rb
+++ b/spec/policies/project_snippet_policy_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
# Snippet visibility scenarios are included in more details in spec/support/snippet_visibility.rb
-describe ProjectSnippetPolicy do
+RSpec.describe ProjectSnippetPolicy do
let_it_be(:regular_user) { create(:user) }
let_it_be(:other_user) { create(:user) }
let_it_be(:external_user) { create(:user, :external) }
diff --git a/spec/policies/project_statistics_policy_spec.rb b/spec/policies/project_statistics_policy_spec.rb
index 50dfbf7291b..74630dc38ad 100644
--- a/spec/policies/project_statistics_policy_spec.rb
+++ b/spec/policies/project_statistics_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectStatisticsPolicy do
+RSpec.describe ProjectStatisticsPolicy do
using RSpec::Parameterized::TableSyntax
describe '#rules' do
diff --git a/spec/policies/protected_branch_policy_spec.rb b/spec/policies/protected_branch_policy_spec.rb
index ea7fd093e38..bb6dbff18a0 100644
--- a/spec/policies/protected_branch_policy_spec.rb
+++ b/spec/policies/protected_branch_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProtectedBranchPolicy do
+RSpec.describe ProtectedBranchPolicy do
let(:user) { create(:user) }
let(:name) { 'feature' }
let(:protected_branch) { create(:protected_branch, name: name) }
diff --git a/spec/policies/releases/source_policy_spec.rb b/spec/policies/releases/source_policy_spec.rb
deleted file mode 100644
index 1bc6d5415d3..00000000000
--- a/spec/policies/releases/source_policy_spec.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Releases::SourcePolicy do
- using RSpec::Parameterized::TableSyntax
-
- let(:policy) { described_class.new(user, source) }
-
- let_it_be(:public_user) { create(:user) }
- let_it_be(:guest) { create(:user) }
- let_it_be(:reporter) { create(:user) }
-
- let(:release) { create(:release, project: project) }
- let(:source) { release.sources.first }
-
- shared_examples 'source code access' do
- it "allows access a release's source code" do
- expect(policy).to be_allowed(:read_release_sources)
- end
- end
-
- shared_examples 'no source code access' do
- it "does not allow access a release's source code" do
- expect(policy).to be_disallowed(:read_release_sources)
- end
- end
-
- context 'a private project' do
- let_it_be(:project) { create(:project, :private) }
-
- context 'accessed by a public user' do
- let(:user) { public_user }
-
- it_behaves_like 'no source code access'
- end
-
- context 'accessed by a user with Guest permissions' do
- let(:user) { guest }
-
- before do
- project.add_guest(user)
- end
-
- it_behaves_like 'no source code access'
- end
-
- context 'accessed by a user with Reporter permissions' do
- let(:user) { reporter }
-
- before do
- project.add_reporter(user)
- end
-
- it_behaves_like 'source code access'
- end
- end
-
- context 'a public project' do
- let_it_be(:project) { create(:project, :public) }
-
- context 'accessed by a public user' do
- let(:user) { public_user }
-
- it_behaves_like 'source code access'
- end
-
- context 'accessed by a user with Guest permissions' do
- let(:user) { guest }
-
- before do
- project.add_guest(user)
- end
-
- it_behaves_like 'source code access'
- end
-
- context 'accessed by a user with Reporter permissions' do
- let(:user) { reporter }
-
- before do
- project.add_reporter(user)
- end
-
- it_behaves_like 'source code access'
- end
- end
-end
diff --git a/spec/policies/resource_label_event_policy_spec.rb b/spec/policies/resource_label_event_policy_spec.rb
index 4db2390c818..eff2b0e1af5 100644
--- a/spec/policies/resource_label_event_policy_spec.rb
+++ b/spec/policies/resource_label_event_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResourceLabelEventPolicy do
+RSpec.describe ResourceLabelEventPolicy do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :private) }
let_it_be(:issue) { create(:issue, project: project) }
diff --git a/spec/policies/todo_policy_spec.rb b/spec/policies/todo_policy_spec.rb
index be6fecd1045..b4876baa504 100644
--- a/spec/policies/todo_policy_spec.rb
+++ b/spec/policies/todo_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TodoPolicy do
+RSpec.describe TodoPolicy do
let_it_be(:author) { create(:user) }
let_it_be(:user1) { create(:user) }
diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb
index 63c4bd05836..1cc3581ebdd 100644
--- a/spec/policies/user_policy_spec.rb
+++ b/spec/policies/user_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserPolicy do
+RSpec.describe UserPolicy do
let(:current_user) { create(:user) }
let(:user) { create(:user) }
diff --git a/spec/policies/wiki_page_policy_spec.rb b/spec/policies/wiki_page_policy_spec.rb
index 0dedccb6e88..093db9f8374 100644
--- a/spec/policies/wiki_page_policy_spec.rb
+++ b/spec/policies/wiki_page_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WikiPagePolicy, :enable_admin_mode do
+RSpec.describe WikiPagePolicy, :enable_admin_mode do
include_context 'ProjectPolicyTable context'
include ProjectHelpers
using RSpec::Parameterized::TableSyntax
diff --git a/spec/presenters/alert_management/alert_presenter_spec.rb b/spec/presenters/alert_management/alert_presenter_spec.rb
new file mode 100644
index 00000000000..b1bf7029f3e
--- /dev/null
+++ b/spec/presenters/alert_management/alert_presenter_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe AlertManagement::AlertPresenter do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:generic_payload) do
+ {
+ 'title' => 'Alert title',
+ 'start_time' => '2020-04-27T10:10:22.265949279Z',
+ 'custom' => { 'param' => 73 }
+ }
+ end
+ let_it_be(:alert) do
+ create(:alert_management_alert, :with_description, :with_host, :with_service, :with_monitoring_tool, project: project, payload: generic_payload)
+ end
+
+ subject(:presenter) { described_class.new(alert) }
+
+ describe '#issue_description' do
+ let(:markdown_line_break) { ' ' }
+
+ it 'returns an alert issue description' do
+ expect(presenter.issue_description).to eq(
+ <<~MARKDOWN.chomp
+ #### Summary
+
+ **Start time:** #{presenter.start_time}#{markdown_line_break}
+ **Severity:** #{presenter.severity}#{markdown_line_break}
+ **Service:** #{alert.service}#{markdown_line_break}
+ **Monitoring tool:** #{alert.monitoring_tool}#{markdown_line_break}
+ **Hosts:** #{alert.hosts.join(' ')}#{markdown_line_break}
+ **Description:** #{alert.description}
+
+ #### Alert Details
+
+ **custom.param:** 73
+ MARKDOWN
+ )
+ end
+ end
+
+ describe '#metrics_dashboard_url' do
+ it 'is not defined' do
+ expect(presenter.metrics_dashboard_url).to be_nil
+ end
+ end
+end
diff --git a/spec/presenters/alert_management/prometheus_alert_presenter_spec.rb b/spec/presenters/alert_management/prometheus_alert_presenter_spec.rb
new file mode 100644
index 00000000000..95246914140
--- /dev/null
+++ b/spec/presenters/alert_management/prometheus_alert_presenter_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe AlertManagement::PrometheusAlertPresenter do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:payload) do
+ {
+ 'annotations' => {
+ 'title' => 'Alert title',
+ 'gitlab_incident_markdown' => '**`markdown example`**',
+ 'custom annotation' => 'custom annotation value'
+ },
+ 'startsAt' => '2020-04-27T10:10:22.265949279Z',
+ 'generatorURL' => 'http://8d467bd4607a:9090/graph?g0.expr=vector%281%29&g0.tab=1'
+ }
+ end
+ let(:alert) do
+ create(:alert_management_alert, :prometheus, project: project, payload: payload)
+ end
+
+ subject(:presenter) { described_class.new(alert) }
+
+ describe '#issue_description' do
+ let(:markdown_line_break) { ' ' }
+
+ it 'returns an alert issue description' do
+ expect(presenter.issue_description).to eq(
+ <<~MARKDOWN.chomp
+ #### Summary
+
+ **Start time:** #{presenter.start_time}#{markdown_line_break}
+ **Severity:** #{presenter.severity}#{markdown_line_break}
+ **full_query:** `vector(1)`#{markdown_line_break}
+ **Monitoring tool:** Prometheus
+
+ #### Alert Details
+
+ **custom annotation:** custom annotation value
+
+ ---
+
+ **`markdown example`**
+ MARKDOWN
+ )
+ end
+ end
+
+ describe '#metrics_dashboard_url' do
+ subject { presenter.metrics_dashboard_url }
+
+ context 'for a non-prometheus alert' do
+ it { is_expected.to be_nil }
+ end
+
+ context 'for a self-managed prometheus alert' do
+ include_context 'self-managed prometheus alert attributes'
+
+ it { is_expected.to eq(dashboard_url_for_alert) }
+ end
+
+ context 'for a gitlab-managed prometheus alert' do
+ include_context 'gitlab-managed prometheus alert attributes'
+
+ it { is_expected.to eq(dashboard_url_for_alert) }
+ end
+ end
+end
diff --git a/spec/presenters/award_emoji_presenter_spec.rb b/spec/presenters/award_emoji_presenter_spec.rb
index e2ada2a3c93..58ee985f165 100644
--- a/spec/presenters/award_emoji_presenter_spec.rb
+++ b/spec/presenters/award_emoji_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AwardEmojiPresenter do
+RSpec.describe AwardEmojiPresenter do
let(:emoji_name) { 'thumbsup' }
let(:award_emoji) { build(:award_emoji, name: emoji_name) }
let(:presenter) { described_class.new(award_emoji) }
diff --git a/spec/presenters/blob_presenter_spec.rb b/spec/presenters/blob_presenter_spec.rb
index ee7bfd1256d..bf926ce62b3 100644
--- a/spec/presenters/blob_presenter_spec.rb
+++ b/spec/presenters/blob_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobPresenter, :seed_helper do
+RSpec.describe BlobPresenter, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
let(:git_blob) do
diff --git a/spec/presenters/blobs/unfold_presenter_spec.rb b/spec/presenters/blobs/unfold_presenter_spec.rb
index 83004809536..4e9f83e8001 100644
--- a/spec/presenters/blobs/unfold_presenter_spec.rb
+++ b/spec/presenters/blobs/unfold_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Blobs::UnfoldPresenter do
+RSpec.describe Blobs::UnfoldPresenter do
include FakeBlobHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/presenters/ci/bridge_presenter_spec.rb b/spec/presenters/ci/bridge_presenter_spec.rb
index 2a4c40a7eaa..6291c3426e2 100644
--- a/spec/presenters/ci/bridge_presenter_spec.rb
+++ b/spec/presenters/ci/bridge_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BridgePresenter do
+RSpec.describe Ci::BridgePresenter do
let_it_be(:project) { create(:project) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
let_it_be(:bridge) { create(:ci_bridge, pipeline: pipeline, status: :failed) }
diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb
index 9cf6eb45c63..8d302b242b3 100644
--- a/spec/presenters/ci/build_presenter_spec.rb
+++ b/spec/presenters/ci/build_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildPresenter do
+RSpec.describe Ci::BuildPresenter do
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
diff --git a/spec/presenters/ci/build_runner_presenter_spec.rb b/spec/presenters/ci/build_runner_presenter_spec.rb
index de199d2bff9..ce4c5a2db7d 100644
--- a/spec/presenters/ci/build_runner_presenter_spec.rb
+++ b/spec/presenters/ci/build_runner_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildRunnerPresenter do
+RSpec.describe Ci::BuildRunnerPresenter do
let(:presenter) { described_class.new(build) }
let(:archive) { { paths: ['sample.txt'] } }
diff --git a/spec/presenters/ci/group_variable_presenter_spec.rb b/spec/presenters/ci/group_variable_presenter_spec.rb
index 3b81a425f5b..aaa6410266e 100644
--- a/spec/presenters/ci/group_variable_presenter_spec.rb
+++ b/spec/presenters/ci/group_variable_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::GroupVariablePresenter do
+RSpec.describe Ci::GroupVariablePresenter do
include Gitlab::Routing.url_helpers
let(:group) { create(:group) }
diff --git a/spec/presenters/ci/legacy_stage_presenter_spec.rb b/spec/presenters/ci/legacy_stage_presenter_spec.rb
index ccf4e48de6e..5268ef0f246 100644
--- a/spec/presenters/ci/legacy_stage_presenter_spec.rb
+++ b/spec/presenters/ci/legacy_stage_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::LegacyStagePresenter do
+RSpec.describe Ci::LegacyStagePresenter do
let(:legacy_stage) { create(:ci_stage) }
let(:presenter) { described_class.new(legacy_stage) }
diff --git a/spec/presenters/ci/pipeline_presenter_spec.rb b/spec/presenters/ci/pipeline_presenter_spec.rb
index e8b66682b97..158daad97f5 100644
--- a/spec/presenters/ci/pipeline_presenter_spec.rb
+++ b/spec/presenters/ci/pipeline_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelinePresenter do
+RSpec.describe Ci::PipelinePresenter do
include Gitlab::Routing
let(:user) { create(:user) }
diff --git a/spec/presenters/ci/trigger_presenter_spec.rb b/spec/presenters/ci/trigger_presenter_spec.rb
index 41cb436f928..bac1c94e0b7 100644
--- a/spec/presenters/ci/trigger_presenter_spec.rb
+++ b/spec/presenters/ci/trigger_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::TriggerPresenter do
+RSpec.describe Ci::TriggerPresenter do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
diff --git a/spec/presenters/ci/variable_presenter_spec.rb b/spec/presenters/ci/variable_presenter_spec.rb
index 70cf2f539b6..30fedf78035 100644
--- a/spec/presenters/ci/variable_presenter_spec.rb
+++ b/spec/presenters/ci/variable_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::VariablePresenter do
+RSpec.describe Ci::VariablePresenter do
include Gitlab::Routing.url_helpers
let(:project) { create(:project) }
diff --git a/spec/presenters/clusterable_presenter_spec.rb b/spec/presenters/clusterable_presenter_spec.rb
index 2c0a7f3e9b2..d19abd4e4d8 100644
--- a/spec/presenters/clusterable_presenter_spec.rb
+++ b/spec/presenters/clusterable_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ClusterablePresenter do
+RSpec.describe ClusterablePresenter do
include Gitlab::Routing.url_helpers
describe '.fabricate' do
diff --git a/spec/presenters/clusters/cluster_presenter_spec.rb b/spec/presenters/clusters/cluster_presenter_spec.rb
index 6a1360807b7..5b75b281297 100644
--- a/spec/presenters/clusters/cluster_presenter_spec.rb
+++ b/spec/presenters/clusters/cluster_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::ClusterPresenter do
+RSpec.describe Clusters::ClusterPresenter do
include Gitlab::Routing.url_helpers
let(:cluster) { create(:cluster, :provided_by_gcp, :project) }
@@ -249,4 +249,126 @@ describe Clusters::ClusterPresenter do
it { is_expected.to be_truthy }
end
end
+
+ describe '#health_data' do
+ shared_examples 'cluster health data' do
+ let(:user) { create(:user) }
+ let(:cluster_presenter) { cluster.present(current_user: user) }
+
+ let(:clusterable_presenter) do
+ ClusterablePresenter.fabricate(clusterable, current_user: user)
+ end
+
+ subject { cluster_presenter.health_data(clusterable_presenter) }
+
+ it do
+ is_expected.to include('clusters-path': clusterable_presenter.index_path,
+ 'dashboard-endpoint': clusterable_presenter.metrics_dashboard_path(cluster),
+ 'documentation-path': help_page_path('user/project/clusters/index', anchor: 'monitoring-your-kubernetes-cluster-ultimate'),
+ 'add-dashboard-documentation-path': help_page_path('user/project/integrations/prometheus.md', anchor: 'adding-a-new-dashboard-to-your-project'),
+ 'empty-getting-started-svg-path': match_asset_path('/assets/illustrations/monitoring/getting_started.svg'),
+ 'empty-loading-svg-path': match_asset_path('/assets/illustrations/monitoring/loading.svg'),
+ 'empty-no-data-svg-path': match_asset_path('/assets/illustrations/monitoring/no_data.svg'),
+ 'empty-no-data-small-svg-path': match_asset_path('illustrations/chart-empty-state-small.svg'),
+ 'empty-unable-to-connect-svg-path': match_asset_path('/assets/illustrations/monitoring/unable_to_connect.svg'),
+ 'settings-path': '',
+ 'project-path': '',
+ 'tags-path': '')
+ end
+ end
+
+ context 'with project cluster' do
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+ let(:clusterable) { cluster.project }
+
+ it_behaves_like 'cluster health data'
+ end
+
+ context 'with group cluster' do
+ let(:cluster) { create(:cluster, :group, :provided_by_gcp) }
+ let(:clusterable) { cluster.group }
+
+ it_behaves_like 'cluster health data'
+ end
+ end
+
+ describe '#gitlab_managed_apps_logs_path' do
+ context 'user can read logs' do
+ let(:project) { cluster.project }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ it 'returns path to logs' do
+ expect(presenter.gitlab_managed_apps_logs_path).to eq k8s_project_logs_path(project, cluster_id: cluster.id, format: :json)
+ end
+
+ context 'cluster has elastic stack application installed' do
+ before do
+ create(:clusters_applications_elastic_stack, :installed, cluster: cluster)
+ end
+
+ it 'returns path to logs' do
+ expect(presenter.gitlab_managed_apps_logs_path).to eq elasticsearch_project_logs_path(project, cluster_id: cluster.id, format: :json)
+ end
+ end
+ end
+
+ context 'group cluster' do
+ let(:cluster) { create(:cluster, cluster_type: :group_type, groups: [group]) }
+ let(:group) { create(:group, name: 'Foo') }
+
+ context 'user can read logs' do
+ before do
+ group.add_maintainer(user)
+ end
+
+ context 'there are projects within group' do
+ let!(:project) { create(:project, namespace: group) }
+
+ it 'returns path to logs' do
+ expect(presenter.gitlab_managed_apps_logs_path).to eq k8s_project_logs_path(project, cluster_id: cluster.id, format: :json)
+ end
+ end
+
+ context 'there are no projects within group' do
+ it 'returns nil' do
+ expect(presenter.gitlab_managed_apps_logs_path).to be_nil
+ end
+ end
+ end
+ end
+
+ context 'instance cluster' do
+ let(:cluster) { create(:cluster, cluster_type: :instance_type) }
+ let!(:project) { create(:project) }
+ let(:user) { create(:admin) }
+
+ before do
+ project.add_maintainer(user)
+ stub_feature_flags(user_mode_in_session: false)
+ end
+
+ context 'user can read logs' do
+ it 'returns path to logs' do
+ expect(presenter.gitlab_managed_apps_logs_path).to eq k8s_project_logs_path(project, cluster_id: cluster.id, format: :json)
+ end
+ end
+ end
+
+ context 'user can NOT read logs' do
+ let(:cluster) { create(:cluster, cluster_type: :instance_type) }
+ let!(:project) { create(:project) }
+
+ before do
+ project.add_developer(user)
+ stub_feature_flags(user_mode_in_session: false)
+ end
+
+ it 'returns nil' do
+ expect(presenter.gitlab_managed_apps_logs_path).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/presenters/commit_presenter_spec.rb b/spec/presenters/commit_presenter_spec.rb
index bc749acfa3a..bc6be07f415 100644
--- a/spec/presenters/commit_presenter_spec.rb
+++ b/spec/presenters/commit_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CommitPresenter do
+RSpec.describe CommitPresenter do
let(:project) { create(:project, :repository) }
let(:commit) { project.commit }
let(:user) { create(:user) }
diff --git a/spec/presenters/commit_status_presenter_spec.rb b/spec/presenters/commit_status_presenter_spec.rb
index b02497d4c11..4b2441d656e 100644
--- a/spec/presenters/commit_status_presenter_spec.rb
+++ b/spec/presenters/commit_status_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CommitStatusPresenter do
+RSpec.describe CommitStatusPresenter do
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
diff --git a/spec/presenters/dev_ops_score/metric_presenter_spec.rb b/spec/presenters/dev_ops_score/metric_presenter_spec.rb
index b6eab3f2e74..8b7b2c88578 100644
--- a/spec/presenters/dev_ops_score/metric_presenter_spec.rb
+++ b/spec/presenters/dev_ops_score/metric_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DevOpsScore::MetricPresenter do
+RSpec.describe DevOpsScore::MetricPresenter do
subject { described_class.new(metric) }
let(:metric) { build(:dev_ops_score_metric) }
diff --git a/spec/presenters/event_presenter_spec.rb b/spec/presenters/event_presenter_spec.rb
index eb94d838370..2d4872ea29e 100644
--- a/spec/presenters/event_presenter_spec.rb
+++ b/spec/presenters/event_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EventPresenter do
+RSpec.describe EventPresenter do
include Gitlab::Routing.url_helpers
let_it_be(:group) { create(:group) }
diff --git a/spec/presenters/gitlab/blame_presenter_spec.rb b/spec/presenters/gitlab/blame_presenter_spec.rb
index d2a173b557c..b163926154b 100644
--- a/spec/presenters/gitlab/blame_presenter_spec.rb
+++ b/spec/presenters/gitlab/blame_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BlamePresenter do
+RSpec.describe Gitlab::BlamePresenter do
let(:project) { create(:project, :repository) }
let(:path) { 'files/ruby/popen.rb' }
let(:commit) { project.commit('master') }
diff --git a/spec/presenters/group_clusterable_presenter_spec.rb b/spec/presenters/group_clusterable_presenter_spec.rb
index d40ca856f7b..27360201e81 100644
--- a/spec/presenters/group_clusterable_presenter_spec.rb
+++ b/spec/presenters/group_clusterable_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupClusterablePresenter do
+RSpec.describe GroupClusterablePresenter do
include Gitlab::Routing.url_helpers
let(:presenter) { described_class.new(group) }
@@ -94,4 +94,10 @@ describe GroupClusterablePresenter do
it { is_expected.to eq(group_cluster_path(group, cluster)) }
end
+
+ describe '#metrics_dashboard_path' do
+ subject { presenter.metrics_dashboard_path(cluster) }
+
+ it { is_expected.to eq(metrics_dashboard_group_cluster_path(group, cluster)) }
+ end
end
diff --git a/spec/presenters/group_member_presenter_spec.rb b/spec/presenters/group_member_presenter_spec.rb
index 382b1881ab7..6bd3005fbb6 100644
--- a/spec/presenters/group_member_presenter_spec.rb
+++ b/spec/presenters/group_member_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupMemberPresenter do
+RSpec.describe GroupMemberPresenter do
let(:user) { double(:user) }
let(:group) { double(:group) }
let(:group_member) { double(:group_member, source: group) }
diff --git a/spec/presenters/instance_clusterable_presenter_spec.rb b/spec/presenters/instance_clusterable_presenter_spec.rb
index 4265e2fcb69..6968e3a4da3 100644
--- a/spec/presenters/instance_clusterable_presenter_spec.rb
+++ b/spec/presenters/instance_clusterable_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe InstanceClusterablePresenter do
+RSpec.describe InstanceClusterablePresenter do
include Gitlab::Routing.url_helpers
let(:presenter) { described_class.new(instance) }
@@ -26,4 +26,10 @@ describe InstanceClusterablePresenter do
it { is_expected.to eq(clear_cache_admin_cluster_path(cluster)) }
end
+
+ describe '#metrics_dashboard_path' do
+ subject { presenter.metrics_dashboard_path(cluster) }
+
+ it { is_expected.to eq(metrics_dashboard_admin_cluster_path(cluster)) }
+ end
end
diff --git a/spec/presenters/issue_presenter_spec.rb b/spec/presenters/issue_presenter_spec.rb
index 4a4caef9d28..f08cd0f2026 100644
--- a/spec/presenters/issue_presenter_spec.rb
+++ b/spec/presenters/issue_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssuePresenter do
+RSpec.describe IssuePresenter do
include Gitlab::Routing.url_helpers
let(:user) { create(:user) }
diff --git a/spec/presenters/label_presenter_spec.rb b/spec/presenters/label_presenter_spec.rb
index 9578d017af5..cb6e991bd8e 100644
--- a/spec/presenters/label_presenter_spec.rb
+++ b/spec/presenters/label_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LabelPresenter do
+RSpec.describe LabelPresenter do
include Gitlab::Routing.url_helpers
let_it_be(:group) { create(:group) }
diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb
index f184e767f8c..f1e581efd44 100644
--- a/spec/presenters/merge_request_presenter_spec.rb
+++ b/spec/presenters/merge_request_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestPresenter do
+RSpec.describe MergeRequestPresenter do
let(:resource) { create(:merge_request, source_project: project) }
let(:project) { create(:project) }
let(:user) { create(:user) }
@@ -613,4 +613,22 @@ describe MergeRequestPresenter do
end
end
end
+
+ describe '#api_approvals_path' do
+ subject { described_class.new(resource, current_user: user).api_approvals_path }
+
+ it { is_expected.to eq(expose_path("/api/v4/projects/#{project.id}/merge_requests/#{resource.iid}/approvals")) }
+ end
+
+ describe '#api_approve_path' do
+ subject { described_class.new(resource, current_user: user).api_approve_path }
+
+ it { is_expected.to eq(expose_path("/api/v4/projects/#{project.id}/merge_requests/#{resource.iid}/approve")) }
+ end
+
+ describe '#api_unapprove_path' do
+ subject { described_class.new(resource, current_user: user).api_unapprove_path }
+
+ it { is_expected.to eq(expose_path("/api/v4/projects/#{project.id}/merge_requests/#{resource.iid}/unapprove")) }
+ end
end
diff --git a/spec/presenters/milestone_presenter_spec.rb b/spec/presenters/milestone_presenter_spec.rb
index 3d7b3ad6d78..1f23bb31fda 100644
--- a/spec/presenters/milestone_presenter_spec.rb
+++ b/spec/presenters/milestone_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MilestonePresenter do
+RSpec.describe MilestonePresenter do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:milestone) { create(:milestone, group: group) }
diff --git a/spec/presenters/packages/composer/packages_presenter_spec.rb b/spec/presenters/packages/composer/packages_presenter_spec.rb
new file mode 100644
index 00000000000..0445a346180
--- /dev/null
+++ b/spec/presenters/packages/composer/packages_presenter_spec.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Packages::Composer::PackagesPresenter do
+ using RSpec::Parameterized::TableSyntax
+
+ let_it_be(:package_name) { 'sample-project' }
+ let_it_be(:json) { { 'name' => package_name } }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, :custom_repo, files: { 'composer.json' => json.to_json }, group: group) }
+ let_it_be(:package1) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) }
+ let_it_be(:package2) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '2.0.0', json: json) }
+
+ let(:branch) { project.repository.find_branch('master') }
+
+ let(:packages) { [package1, package2] }
+ let(:presenter) { described_class.new(group, packages) }
+
+ describe '#package_versions' do
+ subject { presenter.package_versions }
+
+ def expected_json(package)
+ {
+ 'dist' => {
+ 'reference' => branch.target,
+ 'shasum' => '',
+ 'type' => 'zip',
+ 'url' => "http://localhost/api/v4/projects/#{project.id}/packages/composer/archives/#{package.name}.zip?sha=#{branch.target}"
+ },
+ 'name' => package.name,
+ 'uid' => package.id,
+ 'version' => package.version
+ }
+ end
+
+ it 'returns the packages json' do
+ packages = subject['packages'][package_name]
+
+ expect(packages['1.0.0']).to eq(expected_json(package1))
+ expect(packages['2.0.0']).to eq(expected_json(package2))
+ end
+ end
+
+ describe '#provider' do
+ subject { presenter.provider}
+
+ let(:expected_json) do
+ {
+ 'providers' => {
+ package_name => {
+ 'sha256' => /^\h+$/
+ }
+ }
+ }
+ end
+
+ it 'returns the provider json' do
+ expect(subject).to match(expected_json)
+ end
+ end
+
+ describe '#root' do
+ subject { presenter.root }
+
+ let(:expected_json) do
+ {
+ 'packages' => [],
+ 'provider-includes' => { 'p/%hash%.json' => { 'sha256' => /^\h+$/ } },
+ 'providers-url' => "/api/v4/group/#{group.id}/-/packages/composer/%package%.json"
+ }
+ end
+
+ it 'returns the provider json' do
+ expect(subject).to match(expected_json)
+ end
+ end
+end
diff --git a/spec/presenters/packages/conan/package_presenter_spec.rb b/spec/presenters/packages/conan/package_presenter_spec.rb
new file mode 100644
index 00000000000..3bc649c5da4
--- /dev/null
+++ b/spec/presenters/packages/conan/package_presenter_spec.rb
@@ -0,0 +1,181 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Packages::Conan::PackagePresenter do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:conan_package_reference) { '123456789'}
+
+ RSpec.shared_examples 'not selecting a package with the wrong type' do
+ context 'with a nuget package with same name and version' do
+ let_it_be(:wrong_package) { create(:nuget_package, name: 'wrong', version: '1.0.0', project: project) }
+
+ let(:recipe) { "#{wrong_package.name}/#{wrong_package.version}" }
+
+ it { is_expected.to be_empty }
+ end
+ end
+
+ describe '#recipe_urls' do
+ subject { described_class.new(recipe, user, project).recipe_urls }
+
+ context 'no existing package' do
+ let(:recipe) { "my-pkg/v1.0.0/#{project.full_path}/stable" }
+
+ it { is_expected.to be_empty }
+ end
+
+ it_behaves_like 'not selecting a package with the wrong type'
+
+ context 'existing package' do
+ let(:package) { create(:conan_package, project: project) }
+ let(:recipe) { package.conan_recipe }
+
+ let(:expected_result) do
+ {
+ "conanfile.py" => "#{Settings.build_base_gitlab_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanfile.py",
+ "conanmanifest.txt" => "#{Settings.build_base_gitlab_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanmanifest.txt"
+ }
+ end
+
+ it { is_expected.to eq(expected_result) }
+ end
+ end
+
+ describe '#recipe_snapshot' do
+ subject { described_class.new(recipe, user, project).recipe_snapshot }
+
+ context 'no existing package' do
+ let(:recipe) { "my-pkg/v1.0.0/#{project.full_path}/stable" }
+
+ it { is_expected.to be_empty }
+ end
+
+ it_behaves_like 'not selecting a package with the wrong type'
+
+ context 'existing package' do
+ let(:package) { create(:conan_package, project: project) }
+ let(:recipe) { package.conan_recipe }
+
+ let(:expected_result) do
+ {
+ "conanfile.py" => '12345abcde',
+ "conanmanifest.txt" => '12345abcde'
+ }
+ end
+
+ it { is_expected.to eq(expected_result) }
+ end
+ end
+
+ describe '#package_urls' do
+ let(:reference) { conan_package_reference }
+
+ subject do
+ described_class.new(
+ recipe, user, project, conan_package_reference: reference
+ ).package_urls
+ end
+
+ context 'no existing package' do
+ let(:recipe) { "my-pkg/v1.0.0/#{project.full_path}/stable" }
+
+ it { is_expected.to be_empty }
+ end
+
+ it_behaves_like 'not selecting a package with the wrong type'
+
+ context 'existing package' do
+ let(:package) { create(:conan_package, project: project) }
+ let(:recipe) { package.conan_recipe }
+
+ let(:expected_result) do
+ {
+ "conaninfo.txt" => "#{Settings.build_base_gitlab_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/#{conan_package_reference}/0/conaninfo.txt",
+ "conanmanifest.txt" => "#{Settings.build_base_gitlab_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/#{conan_package_reference}/0/conanmanifest.txt",
+ "conan_package.tgz" => "#{Settings.build_base_gitlab_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/#{conan_package_reference}/0/conan_package.tgz"
+ }
+ end
+
+ it { is_expected.to eq(expected_result) }
+
+ context 'multiple packages with different references' do
+ let(:info_file) { create(:conan_package_file, :conan_package_info, package: package) }
+ let(:manifest_file) { create(:conan_package_file, :conan_package_manifest, package: package) }
+ let(:package_file) { create(:conan_package_file, :conan_package, package: package) }
+ let(:alternative_reference) { 'abcdefghi' }
+
+ before do
+ [info_file, manifest_file, package_file].each do |file|
+ file.conan_file_metadatum.conan_package_reference = alternative_reference
+ file.save
+ end
+ end
+
+ it { is_expected.to eq(expected_result) }
+
+ context 'requesting the alternative reference' do
+ let(:reference) { alternative_reference }
+
+ let(:expected_result) do
+ {
+ "conaninfo.txt" => "#{Settings.build_base_gitlab_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/#{alternative_reference}/0/conaninfo.txt",
+ "conanmanifest.txt" => "#{Settings.build_base_gitlab_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/#{alternative_reference}/0/conanmanifest.txt",
+ "conan_package.tgz" => "#{Settings.build_base_gitlab_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/#{alternative_reference}/0/conan_package.tgz"
+ }
+ end
+
+ it { is_expected.to eq(expected_result) }
+ end
+
+ it 'returns empty if the reference does not exist' do
+ result = described_class.new(
+ recipe, user, project, conan_package_reference: 'doesnotexist'
+ ).package_urls
+
+ expect(result).to eq({})
+ end
+ end
+ end
+ end
+
+ describe '#package_snapshot' do
+ let(:reference) { conan_package_reference }
+
+ subject do
+ described_class.new(
+ recipe, user, project, conan_package_reference: reference
+ ).package_snapshot
+ end
+
+ context 'no existing package' do
+ let(:recipe) { "my-pkg/v1.0.0/#{project.full_path}/stable" }
+
+ it { is_expected.to be_empty }
+ end
+
+ it_behaves_like 'not selecting a package with the wrong type'
+
+ context 'existing package' do
+ let(:package) { create(:conan_package, project: project) }
+ let(:recipe) { package.conan_recipe }
+
+ let(:expected_result) do
+ {
+ "conaninfo.txt" => '12345abcde',
+ "conanmanifest.txt" => '12345abcde',
+ "conan_package.tgz" => '12345abcde'
+ }
+ end
+
+ it { is_expected.to eq(expected_result) }
+
+ context 'when requested with invalid reference' do
+ let(:reference) { 'invalid' }
+
+ it { is_expected.to eq({}) }
+ end
+ end
+ end
+end
diff --git a/spec/presenters/packages/detail/package_presenter_spec.rb b/spec/presenters/packages/detail/package_presenter_spec.rb
new file mode 100644
index 00000000000..34582957364
--- /dev/null
+++ b/spec/presenters/packages/detail/package_presenter_spec.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Packages::Detail::PackagePresenter do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, creator: user) }
+ let_it_be(:package) { create(:npm_package, :with_build, project: project) }
+ let(:presenter) { described_class.new(package) }
+
+ let_it_be(:user_info) { { name: user.name, avatar_url: user.avatar_url } }
+ let!(:expected_package_files) do
+ npm_file = package.package_files.first
+ [{
+ created_at: npm_file.created_at,
+ download_path: npm_file.download_path,
+ file_name: npm_file.file_name,
+ size: npm_file.size
+ }]
+ end
+ let(:pipeline_info) do
+ pipeline = package.build_info.pipeline
+ {
+ created_at: pipeline.created_at,
+ id: pipeline.id,
+ sha: pipeline.sha,
+ ref: pipeline.ref,
+ git_commit_message: pipeline.git_commit_message,
+ user: user_info,
+ project: {
+ name: pipeline.project.name,
+ web_url: pipeline.project.web_url
+ }
+ }
+ end
+ let!(:dependency_links) { [] }
+ let!(:expected_package_details) do
+ {
+ id: package.id,
+ created_at: package.created_at,
+ name: package.name,
+ package_files: expected_package_files,
+ package_type: package.package_type,
+ project_id: package.project_id,
+ tags: package.tags.as_json,
+ updated_at: package.updated_at,
+ version: package.version,
+ dependency_links: dependency_links
+ }
+ end
+
+ context 'detail_view' do
+ context 'with build_info' do
+ let_it_be(:package) { create(:npm_package, :with_build, project: project) }
+ let(:expected_package_details) { super().merge(pipeline: pipeline_info) }
+
+ it 'returns details with pipeline' do
+ expect(presenter.detail_view).to eq expected_package_details
+ end
+ end
+
+ context 'without build info' do
+ let_it_be(:package) { create(:npm_package, project: project) }
+
+ it 'returns details without pipeline' do
+ expect(presenter.detail_view).to eq expected_package_details
+ end
+ end
+
+ context 'with nuget_metadatum' do
+ let_it_be(:package) { create(:nuget_package, project: project) }
+ let_it_be(:nuget_metadatum) { create(:nuget_metadatum, package: package) }
+ let(:expected_package_details) { super().merge(nuget_metadatum: nuget_metadatum) }
+
+ it 'returns nuget_metadatum' do
+ expect(presenter.detail_view).to eq expected_package_details
+ end
+ end
+
+ context 'with dependency_links' do
+ let_it_be(:package) { create(:nuget_package, project: project) }
+ let_it_be(:dependency_link) { create(:packages_dependency_link, package: package) }
+ let_it_be(:nuget_dependency) { create(:nuget_dependency_link_metadatum, dependency_link: dependency_link) }
+ let_it_be(:expected_link) do
+ {
+ name: dependency_link.dependency.name,
+ version_pattern: dependency_link.dependency.version_pattern,
+ target_framework: nuget_dependency.target_framework
+ }
+ end
+ let_it_be(:dependency_links) { [expected_link] }
+
+ it 'returns the correct dependency link' do
+ expect(presenter.detail_view).to eq expected_package_details
+ end
+ end
+ end
+end
diff --git a/spec/presenters/packages/npm/package_presenter_spec.rb b/spec/presenters/packages/npm/package_presenter_spec.rb
new file mode 100644
index 00000000000..0e8cda5bafd
--- /dev/null
+++ b/spec/presenters/packages/npm/package_presenter_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Packages::Npm::PackagePresenter do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:package_name) { "@#{project.root_namespace.path}/test" }
+ let!(:package1) { create(:npm_package, version: '1.0.4', project: project, name: package_name) }
+ let!(:package2) { create(:npm_package, version: '1.0.6', project: project, name: package_name) }
+ let!(:latest_package) { create(:npm_package, version: '1.0.11', project: project, name: package_name) }
+ let(:packages) { project.packages.npm.with_name(package_name).last_of_each_version }
+ let(:presenter) { described_class.new(package_name, packages) }
+
+ describe '#versions' do
+ subject { presenter.versions }
+
+ context 'for packages without dependencies' do
+ it { is_expected.to be_a(Hash) }
+ it { expect(subject[package1.version]).to match_schema('public_api/v4/packages/npm_package_version') }
+ it { expect(subject[package2.version]).to match_schema('public_api/v4/packages/npm_package_version') }
+
+ described_class::NPM_VALID_DEPENDENCY_TYPES.each do |dependency_type|
+ it { expect(subject.dig(package1.version, dependency_type)).to be nil }
+ it { expect(subject.dig(package2.version, dependency_type)).to be nil }
+ end
+ end
+
+ context 'for packages with dependencies' do
+ described_class::NPM_VALID_DEPENDENCY_TYPES.each do |dependency_type|
+ let!("package_dependency_link_for_#{dependency_type}") { create(:packages_dependency_link, package: package1, dependency_type: dependency_type) }
+ end
+
+ it { is_expected.to be_a(Hash) }
+ it { expect(subject[package1.version]).to match_schema('public_api/v4/packages/npm_package_version') }
+ it { expect(subject[package2.version]).to match_schema('public_api/v4/packages/npm_package_version') }
+ described_class::NPM_VALID_DEPENDENCY_TYPES.each do |dependency_type|
+ it { expect(subject.dig(package1.version, dependency_type.to_s)).to be_any }
+ end
+ end
+ end
+
+ describe '#dist_tags' do
+ subject { presenter.dist_tags }
+
+ context 'for packages without tags' do
+ it { is_expected.to be_a(Hash) }
+ it { expect(subject["latest"]).to eq(latest_package.version) }
+ end
+
+ context 'for packages with tags' do
+ let!(:package_tag1) { create(:packages_tag, package: package1, name: 'release_a') }
+ let!(:package_tag2) { create(:packages_tag, package: package1, name: 'test_release') }
+ let!(:package_tag3) { create(:packages_tag, package: package2, name: 'release_b') }
+ let!(:package_tag4) { create(:packages_tag, package: latest_package, name: 'release_c') }
+ let!(:package_tag5) { create(:packages_tag, package: latest_package, name: 'latest') }
+
+ it { is_expected.to be_a(Hash) }
+ it { expect(subject[package_tag1.name]).to eq(package1.version) }
+ it { expect(subject[package_tag2.name]).to eq(package1.version) }
+ it { expect(subject[package_tag3.name]).to eq(package2.version) }
+ it { expect(subject[package_tag4.name]).to eq(latest_package.version) }
+ it { expect(subject[package_tag5.name]).to eq(latest_package.version) }
+ end
+ end
+end
diff --git a/spec/presenters/packages/nuget/package_metadata_presenter_spec.rb b/spec/presenters/packages/nuget/package_metadata_presenter_spec.rb
new file mode 100644
index 00000000000..d5e7b23d785
--- /dev/null
+++ b/spec/presenters/packages/nuget/package_metadata_presenter_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::PackageMetadataPresenter do
+ include_context 'with expected presenters dependency groups'
+
+ let_it_be(:package) { create(:nuget_package, :with_metadatum) }
+ let_it_be(:tag1) { create(:packages_tag, name: 'tag1', package: package) }
+ let_it_be(:tag2) { create(:packages_tag, name: 'tag2', package: package) }
+ let_it_be(:presenter) { described_class.new(package) }
+
+ describe '#json_url' do
+ let_it_be(:expected_suffix) { "/api/v4/projects/#{package.project_id}/packages/nuget/metadata/#{package.name}/#{package.version}.json" }
+
+ subject { presenter.json_url }
+
+ it { is_expected.to end_with(expected_suffix) }
+ end
+
+ describe '#archive_url' do
+ let_it_be(:expected_suffix) { "/api/v4/projects/#{package.project_id}/packages/nuget/download/#{package.name}/#{package.version}/#{package.package_files.last.file_name}" }
+
+ subject { presenter.archive_url }
+
+ it { is_expected.to end_with(expected_suffix) }
+ end
+
+ describe '#catalog_entry' do
+ subject { presenter.catalog_entry }
+
+ before do
+ create_dependencies_for(package)
+ end
+
+ it 'returns an entry structure' do
+ entry = subject
+
+ expect(entry).to be_a Hash
+ %i[json_url archive_url].each { |field| expect(entry[field]).not_to be_blank }
+ %i[authors summary].each { |field| expect(entry[field]).to be_blank }
+ expect(entry[:dependency_groups]).to eq expected_dependency_groups(package.project_id, package.name, package.version)
+ expect(entry[:package_name]).to eq package.name
+ expect(entry[:package_version]).to eq package.version
+ expect(entry[:tags].split(::Packages::Tag::NUGET_TAGS_SEPARATOR)).to contain_exactly('tag1', 'tag2')
+
+ %i[project_url license_url icon_url].each do |field|
+ expect(entry.dig(:metadatum, field)).to eq(package.nuget_metadatum.send(field))
+ end
+ end
+ end
+end
diff --git a/spec/presenters/packages/nuget/packages_metadata_presenter_spec.rb b/spec/presenters/packages/nuget/packages_metadata_presenter_spec.rb
new file mode 100644
index 00000000000..b2bcdf8f03d
--- /dev/null
+++ b/spec/presenters/packages/nuget/packages_metadata_presenter_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::PackagesMetadataPresenter do
+ include_context 'with expected presenters dependency groups'
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:packages) { create_list(:nuget_package, 5, :with_metadatum, name: 'Dummy.Package', project: project) }
+ let_it_be(:presenter) { described_class.new(packages) }
+
+ describe '#count' do
+ subject { presenter.count }
+
+ it {is_expected.to eq 1}
+ end
+
+ describe '#items' do
+ let(:tag_names) { %w(tag1 tag2) }
+
+ subject { presenter.items }
+
+ before do
+ packages.each do |pkg|
+ tag_names.each { |tag| create(:packages_tag, package: pkg, name: tag) }
+
+ create_dependencies_for(pkg)
+ end
+ end
+
+ it 'returns an array' do
+ items = subject
+
+ expect(items).to be_a Array
+ expect(items.size).to eq 1
+ end
+
+ it 'returns a summary structure' do
+ item = subject.first
+
+ expect(item).to be_a Hash
+ %i[json_url lower_version upper_version].each { |field| expect(item[field]).not_to be_blank }
+ expect(item[:packages_count]).to eq packages.count
+ expect(item[:packages]).to be_a Array
+ expect(item[:packages].size).to eq packages.count
+ end
+
+ it 'returns the catalog entries' do
+ item = subject.first
+
+ item[:packages].each do |pkg|
+ expect(pkg).to be_a Hash
+ %i[json_url archive_url catalog_entry].each { |field| expect(pkg[field]).not_to be_blank }
+ catalog_entry = pkg[:catalog_entry]
+ %i[json_url archive_url package_name package_version].each { |field| expect(catalog_entry[field]).not_to be_blank }
+ %i[authors summary].each { |field| expect(catalog_entry[field]).to be_blank }
+ expect(catalog_entry[:dependency_groups]).to eq(expected_dependency_groups(project.id, catalog_entry[:package_name], catalog_entry[:package_version]))
+ expect(catalog_entry[:tags].split(::Packages::Tag::NUGET_TAGS_SEPARATOR)).to contain_exactly('tag1', 'tag2')
+
+ %i[project_url license_url icon_url].each do |field|
+ expect(catalog_entry.dig(:metadatum, field)).not_to be_blank
+ end
+ end
+ end
+ end
+end
diff --git a/spec/presenters/packages/nuget/packages_versions_presenter_spec.rb b/spec/presenters/packages/nuget/packages_versions_presenter_spec.rb
new file mode 100644
index 00000000000..36aa28243a4
--- /dev/null
+++ b/spec/presenters/packages/nuget/packages_versions_presenter_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::PackagesVersionsPresenter do
+ let_it_be(:packages) { create_list(:nuget_package, 5) }
+ let_it_be(:presenter) { described_class.new(::Packages::Package.all) }
+
+ describe '#versions' do
+ subject { presenter.versions }
+
+ it { is_expected.to match_array(packages.map(&:version).sort) }
+ end
+end
diff --git a/spec/presenters/packages/nuget/search_results_presenter_spec.rb b/spec/presenters/packages/nuget/search_results_presenter_spec.rb
new file mode 100644
index 00000000000..29ec8579dc1
--- /dev/null
+++ b/spec/presenters/packages/nuget/search_results_presenter_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::SearchResultsPresenter do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:package_a) { create(:nuget_package, :with_metadatum, project: project, name: 'DummyPackageA') }
+ let_it_be(:tag1) { create(:packages_tag, package: package_a, name: 'tag1') }
+ let_it_be(:tag2) { create(:packages_tag, package: package_a, name: 'tag2') }
+ let_it_be(:packages_b) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageB') }
+ let_it_be(:packages_c) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageC') }
+ let_it_be(:search_results) { OpenStruct.new(total_count: 3, results: [package_a, packages_b, packages_c].flatten) }
+ let_it_be(:presenter) { described_class.new(search_results) }
+ let(:total_count) { presenter.total_count }
+ let(:data) { presenter.data }
+
+ describe '#total_count' do
+ it 'expects to have 3 total elements' do
+ expect(total_count).to eq(3)
+ end
+ end
+
+ describe '#data' do
+ it 'returns the proper data structure' do
+ expect(data.size).to eq 3
+ pkg_a, pkg_b, pkg_c = data
+ expect_package_result(pkg_a, package_a.name, [package_a.version], %w(tag1 tag2), with_metadatum: true)
+ expect_package_result(pkg_b, packages_b.first.name, packages_b.map(&:version))
+ expect_package_result(pkg_c, packages_c.first.name, packages_c.map(&:version))
+ end
+
+ # rubocop:disable Metrics/AbcSize
+ def expect_package_result(package_json, name, versions, tags = [], with_metadatum: false)
+ expect(package_json[:type]).to eq 'Package'
+ expect(package_json[:authors]).to be_blank
+ expect(package_json[:name]).to eq(name)
+ expect(package_json[:summary]).to be_blank
+ expect(package_json[:total_downloads]).to eq 0
+ expect(package_json[:verified]).to be
+ expect(package_json[:version]).to eq VersionSorter.sort(versions).last # rubocop: disable Style/UnneededSort
+ versions.zip(package_json[:versions]).each do |version, version_json|
+ expect(version_json[:json_url]).to end_with("#{version}.json")
+ expect(version_json[:downloads]).to eq 0
+ expect(version_json[:version]).to eq version
+ end
+
+ if tags.any?
+ expect(package_json[:tags].split(::Packages::Tag::NUGET_TAGS_SEPARATOR)).to contain_exactly(*tags)
+ else
+ expect(package_json[:tags]).to be_blank
+ end
+
+ %i[project_url license_url icon_url].each do |field|
+ expect(package_json.dig(:metadatum, field)).to with_metadatum ? be_present : be_blank
+ end
+ end
+ # rubocop:enable Metrics/AbcSize
+ end
+end
diff --git a/spec/presenters/packages/nuget/service_index_presenter_spec.rb b/spec/presenters/packages/nuget/service_index_presenter_spec.rb
new file mode 100644
index 00000000000..19ef890e19f
--- /dev/null
+++ b/spec/presenters/packages/nuget/service_index_presenter_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Packages::Nuget::ServiceIndexPresenter do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:presenter) { described_class.new(project) }
+
+ describe '#version' do
+ subject { presenter.version }
+
+ it { is_expected.to eq '3.0.0' }
+ end
+
+ describe '#resources' do
+ subject { presenter.resources }
+
+ it 'has valid resources' do
+ expect(subject.size).to eq 8
+ subject.each do |resource|
+ %i[@id @type comment].each do |field|
+ expect(resource).to have_key(field)
+ expect(resource[field]).to be_a(String)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/presenters/packages/pypi/package_presenter_spec.rb b/spec/presenters/packages/pypi/package_presenter_spec.rb
new file mode 100644
index 00000000000..e4d234a4688
--- /dev/null
+++ b/spec/presenters/packages/pypi/package_presenter_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Packages::Pypi::PackagePresenter do
+ using RSpec::Parameterized::TableSyntax
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:package_name) { 'sample-project' }
+ let_it_be(:package1) { create(:pypi_package, project: project, name: package_name, version: '1.0.0') }
+ let_it_be(:package2) { create(:pypi_package, project: project, name: package_name, version: '2.0.0') }
+
+ let(:packages) { [package1, package2] }
+ let(:presenter) { described_class.new(packages, project) }
+
+ describe '#body' do
+ subject { presenter.body}
+
+ shared_examples_for "pypi package presenter" do
+ let(:file) { package.package_files.first }
+ let(:filename) { file.file_name }
+ let(:expected_file) { "<a href=\"http://localhost/api/v4/projects/#{project.id}/packages/pypi/files/#{file.file_sha256}/#{filename}#sha256=#{file.file_sha256}\" data-requires-python=\"#{expected_python_version}\">#{filename}</a><br>" }
+
+ before do
+ package.pypi_metadatum.required_python = python_version
+ end
+
+ it { is_expected.to include expected_file }
+ end
+
+ it_behaves_like "pypi package presenter" do
+ let(:python_version) { '>=2.7' }
+ let(:expected_python_version) { '&gt;=2.7' }
+ let(:package) { package1 }
+ end
+
+ it_behaves_like "pypi package presenter" do
+ let(:python_version) { '"><script>alert(1)</script>' }
+ let(:expected_python_version) { '&quot;&gt;&lt;script&gt;alert(1)&lt;/script&gt;' }
+ let(:package) { package1 }
+ end
+
+ it_behaves_like "pypi package presenter" do
+ let(:python_version) { '>=2.7, !=3.0' }
+ let(:expected_python_version) { '&gt;=2.7, !=3.0' }
+ let(:package) { package2 }
+ end
+ end
+end
diff --git a/spec/presenters/pages_domain_presenter_spec.rb b/spec/presenters/pages_domain_presenter_spec.rb
index 30ce59b7bfb..731279ce5b9 100644
--- a/spec/presenters/pages_domain_presenter_spec.rb
+++ b/spec/presenters/pages_domain_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PagesDomainPresenter do
+RSpec.describe PagesDomainPresenter do
using RSpec::Parameterized::TableSyntax
include LetsEncryptHelpers
diff --git a/spec/presenters/project_clusterable_presenter_spec.rb b/spec/presenters/project_clusterable_presenter_spec.rb
index b3dad4abde5..b518c63f0ca 100644
--- a/spec/presenters/project_clusterable_presenter_spec.rb
+++ b/spec/presenters/project_clusterable_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectClusterablePresenter do
+RSpec.describe ProjectClusterablePresenter do
include Gitlab::Routing.url_helpers
let(:presenter) { described_class.new(project) }
@@ -94,4 +94,10 @@ describe ProjectClusterablePresenter do
it { is_expected.to eq(project_cluster_path(project, cluster)) }
end
+
+ describe '#metrics_dashboard_path' do
+ subject { presenter.metrics_dashboard_path(cluster) }
+
+ it { is_expected.to eq(metrics_dashboard_project_cluster_path(project, cluster)) }
+ end
end
diff --git a/spec/presenters/project_hook_presenter_spec.rb b/spec/presenters/project_hook_presenter_spec.rb
index 773e8ccf51e..061ec38ae34 100644
--- a/spec/presenters/project_hook_presenter_spec.rb
+++ b/spec/presenters/project_hook_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectHookPresenter do
+RSpec.describe ProjectHookPresenter do
let(:web_hook_log) { create(:web_hook_log) }
let(:project) { web_hook_log.web_hook.project }
let(:web_hook) { web_hook_log.web_hook }
diff --git a/spec/presenters/project_member_presenter_spec.rb b/spec/presenters/project_member_presenter_spec.rb
index 743c89fc7c2..ad45a23c183 100644
--- a/spec/presenters/project_member_presenter_spec.rb
+++ b/spec/presenters/project_member_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectMemberPresenter do
+RSpec.describe ProjectMemberPresenter do
let(:user) { double(:user) }
let(:project) { double(:project) }
let(:project_member) { double(:project_member, source: project) }
diff --git a/spec/presenters/project_presenter_spec.rb b/spec/presenters/project_presenter_spec.rb
index 65ae85ea78f..eb1ff628d14 100644
--- a/spec/presenters/project_presenter_spec.rb
+++ b/spec/presenters/project_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectPresenter do
+RSpec.describe ProjectPresenter do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:presenter) { described_class.new(project, current_user: user) }
diff --git a/spec/presenters/projects/import_export/project_export_presenter_spec.rb b/spec/presenters/projects/import_export/project_export_presenter_spec.rb
index 052ca36974a..8463d01d95b 100644
--- a/spec/presenters/projects/import_export/project_export_presenter_spec.rb
+++ b/spec/presenters/projects/import_export/project_export_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ImportExport::ProjectExportPresenter do
+RSpec.describe Projects::ImportExport::ProjectExportPresenter do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:user) { create(:user) }
diff --git a/spec/presenters/projects/prometheus/alert_presenter_spec.rb b/spec/presenters/projects/prometheus/alert_presenter_spec.rb
index 8ee5a4d7b3f..89c5438b074 100644
--- a/spec/presenters/projects/prometheus/alert_presenter_spec.rb
+++ b/spec/presenters/projects/prometheus/alert_presenter_spec.rb
@@ -2,7 +2,9 @@
require 'spec_helper'
-describe Projects::Prometheus::AlertPresenter do
+RSpec.describe Projects::Prometheus::AlertPresenter do
+ include Gitlab::Routing.url_helpers
+
let_it_be(:project, reload: true) { create(:project) }
let(:presenter) { described_class.new(alert) }
@@ -14,7 +16,7 @@ describe Projects::Prometheus::AlertPresenter do
let(:metric_id) { gitlab_alert.prometheus_metric_id }
let(:alert) do
- create(:alerting_alert, project: project, metric_id: metric_id)
+ create(:alerting_alert, project: project, metric_id: metric_id, payload: payload)
end
end
@@ -171,7 +173,7 @@ describe Projects::Prometheus::AlertPresenter do
**Start time:** #{presenter.start_time}#{markdown_line_break}
**full_query:** `avg(metric) > 1.0`
- [](#{url})
+ [](#{presenter.metrics_dashboard_url})
MARKDOWN
end
@@ -180,68 +182,30 @@ describe Projects::Prometheus::AlertPresenter do
Timecop.freeze(starts_at) { example.run }
end
+ before do
+ payload.delete('startsAt')
+ end
+
it { is_expected.to eq(expected_markdown) }
end
context 'with a starting time available' do
- before do
- payload['startsAt'] = starts_at
- end
-
it { is_expected.to eq(expected_markdown) }
end
end
context 'for gitlab-managed prometheus alerts' do
- let(:gitlab_alert) { create(:prometheus_alert, project: project) }
- let(:metric_id) { gitlab_alert.prometheus_metric_id }
- let(:env_id) { gitlab_alert.environment_id }
+ include_context 'gitlab-managed prometheus alert attributes'
- before do
- payload['labels'] = { 'gitlab_alert_id' => metric_id }
+ let(:alert) do
+ create(:alerting_alert, project: project, metric_id: prometheus_metric_id, payload: payload)
end
- let(:url) { "http://localhost/#{project.full_path}/prometheus/alerts/#{metric_id}/metrics_dashboard?end=2018-03-12T09%3A36%3A00Z&environment_id=#{env_id}&start=2018-03-12T08%3A36%3A00Z" }
-
it_behaves_like 'markdown with metrics embed'
end
context 'for alerts from a self-managed prometheus' do
- let!(:environment) { create(:environment, project: project, name: 'production') }
- let(:url) { "http://localhost/#{project.full_path}/-/environments/#{environment.id}/metrics_dashboard?embed_json=#{CGI.escape(embed_content.to_json)}&end=2018-03-12T09%3A36%3A00Z&start=2018-03-12T08%3A36%3A00Z" }
-
- let(:title) { 'title' }
- let(:y_label) { 'y_label' }
- let(:query) { 'avg(metric) > 1.0' }
- let(:embed_content) do
- {
- panel_groups: [{
- panels: [{
- type: 'line-graph',
- title: title,
- y_label: y_label,
- metrics: [{ query_range: query }]
- }]
- }]
- }
- end
-
- before do
- # Setup embed time range
- payload['startsAt'] = starts_at
-
- # Setup query
- payload['generatorURL'] = "http://host?g0.expr=#{CGI.escape(query)}"
-
- # Setup environment
- payload['labels'] ||= {}
- payload['labels']['gitlab_environment_name'] = 'production'
-
- # Setup chart title & axis labels
- payload['annotations'] ||= {}
- payload['annotations']['title'] = 'title'
- payload['annotations']['gitlab_y_label'] = 'y_label'
- end
+ include_context 'self-managed prometheus alert attributes'
it_behaves_like 'markdown with metrics embed'
@@ -359,10 +323,7 @@ describe Projects::Prometheus::AlertPresenter do
end
describe '#performance_dashboard_link' do
- let(:expected_link) do
- Gitlab::Routing.url_helpers
- .metrics_project_environment_url(project, alert.environment)
- end
+ let(:expected_link) { metrics_project_environment_url(project, alert.environment) }
subject { presenter.performance_dashboard_link }
@@ -370,10 +331,7 @@ describe Projects::Prometheus::AlertPresenter do
end
describe '#incident_issues_link' do
- let(:expected_link) do
- Gitlab::Routing.url_helpers
- .project_issues_url(project, label_name: described_class::INCIDENT_LABEL_NAME)
- end
+ let(:expected_link) { project_issues_url(project, label_name: described_class::INCIDENT_LABEL_NAME) }
subject { presenter.incident_issues_link }
@@ -413,13 +371,35 @@ describe Projects::Prometheus::AlertPresenter do
end
describe '#performance_dashboard_link' do
- let(:expected_link) do
- Gitlab::Routing.url_helpers.metrics_project_environments_url(project)
- end
+ let(:expected_link) { metrics_project_environments_url(project) }
subject { presenter.performance_dashboard_link }
it { is_expected.to eq(expected_link) }
end
end
+
+ describe '#metrics_dashboard_url' do
+ subject { presenter.metrics_dashboard_url }
+
+ context 'for a non-prometheus alert' do
+ it { is_expected.to be_nil }
+ end
+
+ context 'for a self-managed prometheus alert' do
+ include_context 'self-managed prometheus alert attributes'
+
+ let(:prometheus_payload) { payload }
+
+ it { is_expected.to eq(dashboard_url_for_alert) }
+ end
+
+ context 'for a gitlab-managed prometheus alert' do
+ include_context 'gitlab-managed prometheus alert attributes'
+
+ let(:prometheus_payload) { payload }
+
+ it { is_expected.to eq(dashboard_url_for_alert) }
+ end
+ end
end
diff --git a/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb b/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb
index b9cb60e414f..7a679a03b53 100644
--- a/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb
+++ b/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::Settings::DeployKeysPresenter do
+RSpec.describe Projects::Settings::DeployKeysPresenter do
let(:project) { create(:project) }
let(:user) { create(:user) }
diff --git a/spec/presenters/release_presenter_spec.rb b/spec/presenters/release_presenter_spec.rb
index d1f023b8760..5577b3ad2e8 100644
--- a/spec/presenters/release_presenter_spec.rb
+++ b/spec/presenters/release_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ReleasePresenter do
+RSpec.describe ReleasePresenter do
include Gitlab::Routing.url_helpers
let_it_be(:project) { create(:project, :repository) }
@@ -112,4 +112,36 @@ describe ReleasePresenter do
it { is_expected.to be_nil }
end
end
+
+ describe '#assets_count' do
+ subject { presenter.assets_count }
+
+ it 'returns the number of assets associated to the release' do
+ is_expected.to be release.assets_count
+ end
+
+ context 'when a user is not allowed to download release sources' do
+ let(:presenter) { described_class.new(release, current_user: guest) }
+
+ it 'returns the number of all non-source assets associated to the release' do
+ is_expected.to be release.assets_count(except: [:sources])
+ end
+ end
+ end
+
+ describe '#name' do
+ subject { presenter.name }
+
+ it 'returns the release name' do
+ is_expected.to eq release.name
+ end
+
+ context "when a user is not allowed to access any repository information" do
+ let(:presenter) { described_class.new(release, current_user: guest) }
+
+ it 'returns a replacement name to avoid potentially leaking tag information' do
+ is_expected.to eq "Release-#{release.id}"
+ end
+ end
+ end
end
diff --git a/spec/presenters/sentry_error_presenter_spec.rb b/spec/presenters/sentry_error_presenter_spec.rb
index 5f3f1d33b86..af9e7c8a2b2 100644
--- a/spec/presenters/sentry_error_presenter_spec.rb
+++ b/spec/presenters/sentry_error_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SentryErrorPresenter do
+RSpec.describe SentryErrorPresenter do
let(:error) { build(:detailed_error_tracking_error) }
let(:presenter) { described_class.new(error) }
diff --git a/spec/presenters/service_hook_presenter_spec.rb b/spec/presenters/service_hook_presenter_spec.rb
index bea57768e3e..adef34a882b 100644
--- a/spec/presenters/service_hook_presenter_spec.rb
+++ b/spec/presenters/service_hook_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ServiceHookPresenter do
+RSpec.describe ServiceHookPresenter do
let(:web_hook_log) { create(:web_hook_log, web_hook: service_hook) }
let(:service_hook) { create(:service_hook, service: service) }
let(:service) { create(:drone_ci_service, project: project) }
diff --git a/spec/presenters/snippet_blob_presenter_spec.rb b/spec/presenters/snippet_blob_presenter_spec.rb
index eb7621cc591..7464c0ac15b 100644
--- a/spec/presenters/snippet_blob_presenter_spec.rb
+++ b/spec/presenters/snippet_blob_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SnippetBlobPresenter do
+RSpec.describe SnippetBlobPresenter do
describe '#rich_data' do
before do
allow_next_instance_of(described_class) do |instance|
@@ -109,22 +109,38 @@ describe SnippetBlobPresenter do
end
describe '#raw_path' do
- subject { described_class.new(snippet.blob).raw_path }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:personal_snippet) { create(:personal_snippet, :repository, author: user) }
+ let_it_be(:project_snippet) { create(:project_snippet, :repository, project: project, author: user) }
- context 'with ProjectSnippet' do
- let!(:project) { create(:project) }
- let(:snippet) { create(:project_snippet, project: project) }
+ before do
+ project.add_developer(user)
+ end
+
+ subject { described_class.new(snippet.blobs.first, current_user: user).raw_path }
- it 'returns the raw path' do
- expect(subject).to eq "/#{snippet.project.full_path}/snippets/#{snippet.id}/raw"
+ it_behaves_like 'snippet blob raw path'
+
+ context 'with snippet_multiple_files feature disabled' do
+ before do
+ stub_feature_flags(snippet_multiple_files: false)
end
- end
- context 'with PersonalSnippet' do
- let(:snippet) { create(:personal_snippet) }
+ context 'with ProjectSnippet' do
+ let(:snippet) { project_snippet }
- it 'returns the raw path' do
- expect(subject).to eq "/snippets/#{snippet.id}/raw"
+ it 'returns the raw path' do
+ expect(subject).to eq "/#{snippet.project.full_path}/snippets/#{snippet.id}/raw"
+ end
+ end
+
+ context 'with PersonalSnippet' do
+ let(:snippet) { personal_snippet }
+
+ it 'returns the raw path' do
+ expect(subject).to eq "/snippets/#{snippet.id}/raw"
+ end
end
end
end
diff --git a/spec/presenters/snippet_presenter_spec.rb b/spec/presenters/snippet_presenter_spec.rb
index 423e9edc219..98c291bdd02 100644
--- a/spec/presenters/snippet_presenter_spec.rb
+++ b/spec/presenters/snippet_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SnippetPresenter do
+RSpec.describe SnippetPresenter do
include Gitlab::Routing.url_helpers
let_it_be(:user) { create(:user) }
diff --git a/spec/presenters/tree_entry_presenter_spec.rb b/spec/presenters/tree_entry_presenter_spec.rb
index 0c29fe3e5ff..d29a7a6ab04 100644
--- a/spec/presenters/tree_entry_presenter_spec.rb
+++ b/spec/presenters/tree_entry_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TreeEntryPresenter do
+RSpec.describe TreeEntryPresenter do
include Gitlab::Routing.url_helpers
let(:project) { create(:project, :repository) }
diff --git a/spec/presenters/web_hook_log_presenter_spec.rb b/spec/presenters/web_hook_log_presenter_spec.rb
index 8812a0ba594..68c8c6e2a1b 100644
--- a/spec/presenters/web_hook_log_presenter_spec.rb
+++ b/spec/presenters/web_hook_log_presenter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WebHookLogPresenter do
+RSpec.describe WebHookLogPresenter do
include Gitlab::Routing.url_helpers
describe '#details_path' do
diff --git a/spec/rack_servers/puma_spec.rb b/spec/rack_servers/puma_spec.rb
index a4b37905af3..80595b267fa 100644
--- a/spec/rack_servers/puma_spec.rb
+++ b/spec/rack_servers/puma_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
require 'fileutils'
require 'excon'
-describe 'Puma' do
+RSpec.describe 'Puma' do
before(:all) do
project_root = Rails.root.to_s
config_lines = File.read(Rails.root.join('config/puma.example.development.rb'))
diff --git a/spec/rack_servers/unicorn_spec.rb b/spec/rack_servers/unicorn_spec.rb
index 6a02ebcd048..5887b49d269 100644
--- a/spec/rack_servers/unicorn_spec.rb
+++ b/spec/rack_servers/unicorn_spec.rb
@@ -6,7 +6,7 @@ require 'excon'
require 'spec_helper'
-describe 'Unicorn' do
+RSpec.describe 'Unicorn' do
before(:all) do
project_root = File.expand_path('../..', __dir__)
diff --git a/spec/requests/api/access_requests_spec.rb b/spec/requests/api/access_requests_spec.rb
index 52bc81cff18..223d740a004 100644
--- a/spec/requests/api/access_requests_spec.rb
+++ b/spec/requests/api/access_requests_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::AccessRequests do
+RSpec.describe API::AccessRequests do
let_it_be(:maintainer) { create(:user) }
let_it_be(:developer) { create(:user) }
let_it_be(:access_requester) { create(:user) }
diff --git a/spec/requests/api/admin/ci/variables_spec.rb b/spec/requests/api/admin/ci/variables_spec.rb
index 185fde17e1b..812ee93ad21 100644
--- a/spec/requests/api/admin/ci/variables_spec.rb
+++ b/spec/requests/api/admin/ci/variables_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::API::Admin::Ci::Variables do
+RSpec.describe ::API::Admin::Ci::Variables do
let_it_be(:admin) { create(:admin) }
let_it_be(:user) { create(:user) }
diff --git a/spec/requests/api/admin/instance_clusters_spec.rb b/spec/requests/api/admin/instance_clusters_spec.rb
new file mode 100644
index 00000000000..b68541b5d92
--- /dev/null
+++ b/spec/requests/api/admin/instance_clusters_spec.rb
@@ -0,0 +1,461 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::API::Admin::InstanceClusters do
+ include KubernetesHelpers
+
+ let_it_be(:regular_user) { create(:user) }
+ let_it_be(:admin_user) { create(:admin) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:project_cluster) do
+ create(:cluster, :project, :provided_by_gcp,
+ user: admin_user,
+ projects: [project])
+ end
+ let(:project_cluster_id) { project_cluster.id }
+
+ describe "GET /admin/clusters" do
+ let_it_be(:clusters) do
+ create_list(:cluster, 3, :provided_by_gcp, :instance, :production_environment)
+ end
+
+ context "when authenticated as a non-admin user" do
+ it 'returns 403' do
+ get api('/admin/clusters', regular_user)
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context "when authenticated as admin" do
+ before do
+ get api("/admin/clusters", admin_user)
+ end
+
+ it 'returns 200' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'includes pagination headers' do
+ expect(response).to include_pagination_headers
+ end
+
+ it 'only returns the instance clusters' do
+ cluster_ids = json_response.map { |cluster| cluster['id'] }
+ expect(cluster_ids).to match_array(clusters.pluck(:id))
+ expect(cluster_ids).not_to include(project_cluster_id)
+ end
+ end
+ end
+
+ describe "GET /admin/clusters/:cluster_id" do
+ let_it_be(:platform_kubernetes) do
+ create(:cluster_platform_kubernetes, :configured)
+ end
+
+ let_it_be(:cluster) do
+ create(:cluster, :instance, :provided_by_gcp, :with_domain,
+ platform_kubernetes: platform_kubernetes,
+ user: admin_user)
+ end
+
+ let(:cluster_id) { cluster.id }
+
+ context "when authenticated as admin" do
+ before do
+ get api("/admin/clusters/#{cluster_id}", admin_user)
+ end
+
+ context "when no cluster associated to the ID" do
+ let(:cluster_id) { 1337 }
+
+ it 'returns 404' do
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context "when cluster with cluster_id exists" do
+ it 'returns 200' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns the cluster with cluster_id' do
+ expect(json_response['id']).to eq(cluster.id)
+ end
+
+ it 'returns the cluster information' do
+ expect(json_response['provider_type']).to eq('gcp')
+ expect(json_response['platform_type']).to eq('kubernetes')
+ expect(json_response['environment_scope']).to eq('*')
+ expect(json_response['cluster_type']).to eq('instance_type')
+ expect(json_response['domain']).to eq('example.com')
+ end
+
+ it 'returns kubernetes platform information' do
+ platform = json_response['platform_kubernetes']
+
+ expect(platform['api_url']).to eq('https://kubernetes.example.com')
+ expect(platform['ca_cert']).to be_present
+ end
+
+ it 'returns user information' do
+ user = json_response['user']
+
+ expect(user['id']).to eq(admin_user.id)
+ expect(user['username']).to eq(admin_user.username)
+ end
+
+ it 'returns GCP provider information' do
+ gcp_provider = json_response['provider_gcp']
+
+ expect(gcp_provider['cluster_id']).to eq(cluster.id)
+ expect(gcp_provider['status_name']).to eq('created')
+ expect(gcp_provider['gcp_project_id']).to eq('test-gcp-project')
+ expect(gcp_provider['zone']).to eq('us-central1-a')
+ expect(gcp_provider['machine_type']).to eq('n1-standard-2')
+ expect(gcp_provider['num_nodes']).to eq(3)
+ expect(gcp_provider['endpoint']).to eq('111.111.111.111')
+ end
+
+ context 'when cluster has no provider' do
+ let(:cluster) do
+ create(:cluster, :instance, :provided_by_user, :production_environment)
+ end
+
+ it 'does not include GCP provider info' do
+ expect(json_response['provider_gcp']).not_to be_present
+ end
+ end
+
+ context 'when trying to get a project cluster via the instance cluster endpoint' do
+ it 'returns 404' do
+ get api("/admin/clusters/#{project_cluster_id}", admin_user)
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context "when authenticated as a non-admin user" do
+ it 'returns 403' do
+ get api("/admin/clusters/#{cluster_id}", regular_user)
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+ end
+
+ describe "POST /admin/clusters/add" do
+ let(:api_url) { 'https://example.com' }
+ let(:authorization_type) { 'rbac' }
+ let(:clusterable) { Clusters::Instance.new }
+
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'sample-token',
+ authorization_type: authorization_type
+ }
+ end
+
+ let(:cluster_params) do
+ {
+ name: 'test-instance-cluster',
+ domain: 'domain.example.com',
+ managed: false,
+ platform_kubernetes_attributes: platform_kubernetes_attributes,
+ clusterable: clusterable
+ }
+ end
+
+ let(:multiple_cluster_params) do
+ {
+ name: 'multiple-instance-cluster',
+ environment_scope: 'staging/*',
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ let(:invalid_cluster_params) do
+ {
+ environment_scope: 'production/*',
+ domain: 'domain.example.com',
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ context 'authorized user' do
+ before do
+ post api('/admin/clusters/add', admin_user), params: cluster_params
+ end
+
+ context 'with valid params' do
+ it 'responds with 201' do
+ expect(response).to have_gitlab_http_status(:created)
+ end
+
+ it 'creates a new Clusters::Cluster', :aggregate_failures do
+ cluster_result = Clusters::Cluster.find(json_response["id"])
+ platform_kubernetes = cluster_result.platform
+ expect(cluster_result).to be_user
+ expect(cluster_result).to be_kubernetes
+ expect(cluster_result.clusterable).to be_a Clusters::Instance
+ expect(cluster_result.cluster_type).to eq('instance_type')
+ expect(cluster_result.name).to eq('test-instance-cluster')
+ expect(cluster_result.domain).to eq('domain.example.com')
+ expect(cluster_result.environment_scope).to eq('*')
+ expect(cluster_result.enabled).to eq(true)
+ expect(platform_kubernetes.authorization_type).to eq('rbac')
+ expect(cluster_result.managed).to be_falsy
+ expect(platform_kubernetes.api_url).to eq("https://example.com")
+ expect(platform_kubernetes.token).to eq('sample-token')
+ end
+
+ context 'when user does not indicate authorization type' do
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'sample-token'
+ }
+ end
+
+ it 'defaults to RBAC' do
+ cluster_result = Clusters::Cluster.find(json_response['id'])
+
+ expect(cluster_result.platform_kubernetes.rbac?).to be_truthy
+ end
+ end
+
+ context 'when user sets authorization type as ABAC' do
+ let(:authorization_type) { 'abac' }
+
+ it 'creates an ABAC cluster' do
+ cluster_result = Clusters::Cluster.find(json_response['id'])
+
+ expect(cluster_result.platform.abac?).to be_truthy
+ end
+ end
+
+ context 'when an instance cluster already exists' do
+ it 'allows user to add multiple clusters' do
+ post api('/admin/clusters/add', admin_user), params: multiple_cluster_params
+
+ expect(Clusters::Instance.new.clusters.count).to eq(2)
+ end
+ end
+ end
+
+ context 'with invalid params' do
+ context 'when missing a required parameter' do
+ it 'responds with 400' do
+ post api('/admin/clusters/add', admin_user), params: invalid_cluster_params
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['error']).to eql('name is missing')
+ end
+ end
+
+ context 'with a malformed api url' do
+ let(:api_url) { 'invalid_api_url' }
+
+ it 'responds with 400' do
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'returns validation errors' do
+ expect(json_response['message']['platform_kubernetes.api_url'].first).to be_present
+ end
+ end
+ end
+ end
+
+ context 'non-authorized user' do
+ it 'responds with 403' do
+ post api('/admin/clusters/add', regular_user), params: cluster_params
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ describe 'PUT /admin/clusters/:cluster_id' do
+ let(:api_url) { 'https://example.com' }
+
+ let(:update_params) do
+ {
+ domain: domain,
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ let(:domain) { 'new-domain.com' }
+ let(:platform_kubernetes_attributes) { {} }
+
+ let_it_be(:cluster) do
+ create(:cluster, :instance, :provided_by_gcp, domain: 'old-domain.com')
+ end
+
+ context 'authorized user' do
+ before do
+ put api("/admin/clusters/#{cluster.id}", admin_user), params: update_params
+
+ cluster.reload
+ end
+
+ context 'with valid params' do
+ it 'responds with 200' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'updates cluster attributes' do
+ expect(cluster.domain).to eq('new-domain.com')
+ end
+ end
+
+ context 'with invalid params' do
+ let(:domain) { 'invalid domain' }
+
+ it 'responds with 400' do
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'does not update cluster attributes' do
+ expect(cluster.domain).to eq('old-domain.com')
+ end
+
+ it 'returns validation errors' do
+ expect(json_response['message']['domain'].first).to match('contains invalid characters (valid characters: [a-z0-9\\-])')
+ end
+ end
+
+ context 'with a GCP cluster' do
+ context 'when user tries to change GCP specific fields' do
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: 'https://new-api-url.com',
+ token: 'new-sample-token'
+ }
+ end
+
+ it 'responds with 400' do
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'returns validation error' do
+ expect(json_response['message']['platform_kubernetes.base'].first).to eq(_('Cannot modify managed Kubernetes cluster'))
+ end
+ end
+
+ context 'when user tries to change domain' do
+ let(:domain) { 'new-domain.com' }
+
+ it 'responds with 200' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ context 'with an user cluster' do
+ let(:api_url) { 'https://new-api-url.com' }
+
+ let(:cluster) do
+ create(:cluster, :instance, :provided_by_user, :production_environment)
+ end
+
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'new-sample-token'
+ }
+ end
+
+ let(:update_params) do
+ {
+ name: 'new-name',
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ it 'responds with 200' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'updates platform kubernetes attributes' do
+ platform_kubernetes = cluster.platform_kubernetes
+
+ expect(cluster.name).to eq('new-name')
+ expect(platform_kubernetes.api_url).to eq('https://new-api-url.com')
+ expect(platform_kubernetes.token).to eq('new-sample-token')
+ end
+ end
+
+ context 'with a cluster that does not exist' do
+ let(:cluster_id) { 1337 }
+
+ it 'returns 404' do
+ put api("/admin/clusters/#{cluster_id}", admin_user), params: update_params
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when trying to update a project cluster via the instance cluster endpoint' do
+ it 'returns 404' do
+ put api("/admin/clusters/#{project_cluster_id}", admin_user), params: update_params
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'non-authorized user' do
+ it 'responds with 403' do
+ put api("/admin/clusters/#{cluster.id}", regular_user), params: update_params
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ describe 'DELETE /admin/clusters/:cluster_id' do
+ let(:cluster_params) { { cluster_id: cluster.id } }
+
+ let_it_be(:cluster) do
+ create(:cluster, :instance, :provided_by_gcp)
+ end
+
+ context 'authorized user' do
+ before do
+ delete api("/admin/clusters/#{cluster.id}", admin_user), params: cluster_params
+ end
+
+ it 'responds with 204' do
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+
+ it 'deletes the cluster' do
+ expect(Clusters::Cluster.exists?(id: cluster.id)).to be_falsy
+ end
+
+ context 'with a cluster that does not exist' do
+ let(:cluster_id) { 1337 }
+
+ it 'returns 404' do
+ delete api("/admin/clusters/#{cluster_id}", admin_user)
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when trying to update a project cluster via the instance cluster endpoint' do
+ it 'returns 404' do
+ delete api("/admin/clusters/#{project_cluster_id}", admin_user)
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'non-authorized user' do
+ it 'responds with 403' do
+ delete api("/admin/clusters/#{cluster.id}", regular_user), params: cluster_params
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/admin/sidekiq_spec.rb b/spec/requests/api/admin/sidekiq_spec.rb
index 303b62f4436..3c488816bed 100644
--- a/spec/requests/api/admin/sidekiq_spec.rb
+++ b/spec/requests/api/admin/sidekiq_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Admin::Sidekiq, :clean_gitlab_redis_queues do
+RSpec.describe API::Admin::Sidekiq, :clean_gitlab_redis_queues do
let_it_be(:admin) { create(:admin) }
describe 'DELETE /admin/sidekiq/queues/:queue_name' do
diff --git a/spec/requests/api/api_guard/admin_mode_middleware_spec.rb b/spec/requests/api/api_guard/admin_mode_middleware_spec.rb
index 7175076e56d..4b477f829a7 100644
--- a/spec/requests/api/api_guard/admin_mode_middleware_spec.rb
+++ b/spec/requests/api/api_guard/admin_mode_middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::APIGuard::AdminModeMiddleware, :do_not_mock_admin_mode, :request_store do
+RSpec.describe API::APIGuard::AdminModeMiddleware, :do_not_mock_admin_mode, :request_store do
let(:user) { create(:admin) }
it 'is loaded' do
diff --git a/spec/requests/api/api_spec.rb b/spec/requests/api/api_spec.rb
index 201c0d1796c..bd0426601db 100644
--- a/spec/requests/api/api_spec.rb
+++ b/spec/requests/api/api_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::API do
+RSpec.describe API::API do
include GroupAPIHelpers
describe 'Record user last activity in after hook' do
@@ -36,6 +36,14 @@ describe API::API do
expect(response).to have_gitlab_http_status(:ok)
end
+ it 'does not authorize user for revoked token' do
+ revoked = create(:personal_access_token, :revoked, user: user, scopes: [:read_api])
+
+ get api('/groups', personal_access_token: revoked)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
it 'does not authorize user for post request' do
params = attributes_for_group_api
diff --git a/spec/requests/api/appearance_spec.rb b/spec/requests/api/appearance_spec.rb
index f8c3db70d16..69176e18d2e 100644
--- a/spec/requests/api/appearance_spec.rb
+++ b/spec/requests/api/appearance_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Appearance, 'Appearance' do
+RSpec.describe API::Appearance, 'Appearance' do
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
diff --git a/spec/requests/api/applications_spec.rb b/spec/requests/api/applications_spec.rb
index cd341ad134e..63fbf6e32dd 100644
--- a/spec/requests/api/applications_spec.rb
+++ b/spec/requests/api/applications_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Applications, :api do
+RSpec.describe API::Applications, :api do
let(:admin_user) { create(:user, admin: true) }
let(:user) { create(:user, admin: false) }
let!(:application) { create(:application, name: 'another_application', redirect_uri: 'http://other_application.url', scopes: '') }
@@ -74,14 +74,15 @@ describe API::Applications, :api do
expect(json_response['error']).to eq('scopes is missing')
end
- it 'does not allow creating an application with confidential set to nil' do
+ it 'defaults to creating an application with confidential' do
expect do
post api('/applications', admin_user), params: { name: 'application_name', redirect_uri: 'http://application.url', scopes: '', confidential: nil }
- end.not_to change { Doorkeeper::Application.count }
+ end.to change { Doorkeeper::Application.count }.by(1)
- expect(response).to have_gitlab_http_status(:bad_request)
+ expect(response).to have_gitlab_http_status(:created)
expect(json_response).to be_a Hash
- expect(json_response['message']['confidential'].first).to eq('is not included in the list')
+ expect(json_response['callback_url']).to eq('http://application.url')
+ expect(json_response['confidential']).to be true
end
end
diff --git a/spec/requests/api/avatar_spec.rb b/spec/requests/api/avatar_spec.rb
index 45e34b7894b..656a086e550 100644
--- a/spec/requests/api/avatar_spec.rb
+++ b/spec/requests/api/avatar_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Avatar do
+RSpec.describe API::Avatar do
let(:gravatar_service) { double('GravatarService') }
describe 'GET /avatar' do
diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb
index 543fe970abd..1c825949ae8 100644
--- a/spec/requests/api/award_emoji_spec.rb
+++ b/spec/requests/api/award_emoji_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::AwardEmoji do
+RSpec.describe API::AwardEmoji do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
diff --git a/spec/requests/api/badges_spec.rb b/spec/requests/api/badges_spec.rb
index d7f9b7d010b..99d224cb8e9 100644
--- a/spec/requests/api/badges_spec.rb
+++ b/spec/requests/api/badges_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Badges do
+RSpec.describe API::Badges do
let(:maintainer) { create(:user, username: 'maintainer_user') }
let(:developer) { create(:user) }
let(:access_requester) { create(:user) }
diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb
index d761b371821..f0d3afd0af7 100644
--- a/spec/requests/api/boards_spec.rb
+++ b/spec/requests/api/boards_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Boards do
+RSpec.describe API::Boards do
let_it_be(:user) { create(:user) }
let_it_be(:non_member) { create(:user) }
let_it_be(:guest) { create(:user) }
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index f2dc5b1c045..46acd92803f 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Branches do
+RSpec.describe API::Branches do
let_it_be(:user) { create(:user) }
let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
let(:guest) { create(:user).tap { |u| project.add_guest(u) } }
@@ -17,6 +17,7 @@ describe API::Branches do
before do
project.add_maintainer(user)
project.repository.add_branch(user, 'ends-with.txt', branch_sha)
+ stub_feature_flags(branch_list_keyset_pagination: false)
end
describe "GET /projects/:id/repository/branches" do
@@ -29,16 +30,6 @@ describe API::Branches do
end
end
- it 'returns the repository branches' do
- get api(route, current_user), params: { per_page: 100 }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('public_api/v4/branches')
- expect(response).to include_pagination_headers
- branch_names = json_response.map { |x| x['name'] }
- expect(branch_names).to match_array(project.repository.branch_names)
- end
-
def check_merge_status(json_response)
merged, unmerged = json_response.partition { |branch| branch['merged'] }
merged_branches = merged.map { |branch| branch['name'] }
@@ -47,22 +38,107 @@ describe API::Branches do
expect(project.repository.merged_branch_names(unmerged_branches)).to be_empty
end
- it 'determines only a limited number of merged branch names' do
- expect(API::Entities::Branch).to receive(:represent).with(anything, has_up_to_merged_branch_names_count(2)).and_call_original
+ context 'with branch_list_keyset_pagination feature off' do
+ context 'with legacy pagination params' do
+ it 'returns the repository branches' do
+ get api(route, current_user), params: { per_page: 100 }
- get api(route, current_user), params: { per_page: 2 }
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/branches')
+ expect(response).to include_pagination_headers
+ branch_names = json_response.map { |x| x['name'] }
+ expect(branch_names).to match_array(project.repository.branch_names)
+ end
- expect(response).to have_gitlab_http_status(:ok)
+ it 'determines only a limited number of merged branch names' do
+ expect(API::Entities::Branch).to receive(:represent).with(anything, has_up_to_merged_branch_names_count(2)).and_call_original
+
+ get api(route, current_user), params: { per_page: 2 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.count).to eq 2
+
+ check_merge_status(json_response)
+ end
- check_merge_status(json_response)
+ it 'merge status matches reality on paginated input' do
+ expected_first_branch_name = project.repository.branches_sorted_by('name')[20].name
+
+ get api(route, current_user), params: { per_page: 20, page: 2 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.count).to eq 20
+ expect(json_response.first['name']).to eq(expected_first_branch_name)
+
+ check_merge_status(json_response)
+ end
+ end
+
+ context 'with gitaly pagination params ' do
+ it 'merge status matches reality on paginated input' do
+ expected_first_branch_name = project.repository.branches_sorted_by('name').first.name
+
+ get api(route, current_user), params: { per_page: 20, page_token: 'feature' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.count).to eq 20
+ expect(json_response.first['name']).to eq(expected_first_branch_name)
+
+ check_merge_status(json_response)
+ end
+ end
end
- it 'merge status matches reality on paginated input' do
- get api(route, current_user), params: { per_page: 20, page: 2 }
+ context 'with branch_list_keyset_pagination feature on' do
+ before do
+ stub_feature_flags(branch_list_keyset_pagination: true)
+ end
- expect(response).to have_gitlab_http_status(:ok)
+ context 'with gitaly pagination params ' do
+ it 'returns the repository branches' do
+ get api(route, current_user), params: { per_page: 100 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/branches')
+ branch_names = json_response.map { |x| x['name'] }
+ expect(branch_names).to match_array(project.repository.branch_names)
+ end
+
+ it 'determines only a limited number of merged branch names' do
+ expect(API::Entities::Branch).to receive(:represent).with(anything, has_up_to_merged_branch_names_count(2)).and_call_original
+
+ get api(route, current_user), params: { per_page: 2 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.count).to eq 2
+
+ check_merge_status(json_response)
+ end
- check_merge_status(json_response)
+ it 'merge status matches reality on paginated input' do
+ expected_first_branch_name = project.repository.branches_sorted_by('name').drop_while { |b| b.name <= 'feature' }.first.name
+
+ get api(route, current_user), params: { per_page: 20, page_token: 'feature' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.count).to eq 20
+ expect(json_response.first['name']).to eq(expected_first_branch_name)
+
+ check_merge_status(json_response)
+ end
+ end
+
+ context 'with legacy pagination params' do
+ it 'ignores legacy pagination params' do
+ expected_first_branch_name = project.repository.branches_sorted_by('name').first.name
+ get api(route, current_user), params: { per_page: 20, page: 2 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.first['name']).to eq(expected_first_branch_name)
+
+ check_merge_status(json_response)
+ end
+ end
end
context 'when repository is disabled' do
diff --git a/spec/requests/api/broadcast_messages_spec.rb b/spec/requests/api/broadcast_messages_spec.rb
index 9bfbbe0daab..b5b6ce106e5 100644
--- a/spec/requests/api/broadcast_messages_spec.rb
+++ b/spec/requests/api/broadcast_messages_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::BroadcastMessages do
+RSpec.describe API::BroadcastMessages do
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
let_it_be(:message) { create(:broadcast_message) }
diff --git a/spec/requests/api/pipeline_schedules_spec.rb b/spec/requests/api/ci/pipeline_schedules_spec.rb
index 98eaf36b14e..e0199b7b51c 100644
--- a/spec/requests/api/pipeline_schedules_spec.rb
+++ b/spec/requests/api/ci/pipeline_schedules_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::PipelineSchedules do
+RSpec.describe API::Ci::PipelineSchedules do
let_it_be(:developer) { create(:user) }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, public_builds: false) }
@@ -24,7 +24,7 @@ describe API::PipelineSchedules do
.each do |pipeline_schedule|
create(:user).tap do |user|
project.add_developer(user)
- pipeline_schedule.update(owner: user)
+ pipeline_schedule.update!(owner: user)
end
pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
end
diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/ci/pipelines_spec.rb
index f57223f1de5..c9ca806e2c4 100644
--- a/spec/requests/api/pipelines_spec.rb
+++ b/spec/requests/api/ci/pipelines_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Pipelines do
+RSpec.describe API::Ci::Pipelines do
let_it_be(:user) { create(:user) }
let_it_be(:non_member) { create(:user) }
@@ -106,11 +106,11 @@ describe API::Pipelines do
end
end
- HasStatus::AVAILABLE_STATUSES.each do |target|
+ Ci::HasStatus::AVAILABLE_STATUSES.each do |target|
context "when status is #{target}" do
before do
create(:ci_pipeline, project: project, status: target)
- exception_status = HasStatus::AVAILABLE_STATUSES - [target]
+ exception_status = Ci::HasStatus::AVAILABLE_STATUSES - [target]
create(:ci_pipeline, project: project, status: exception_status.sample)
end
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/ci/runner_spec.rb
index 774615757b9..c8718309bf2 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/ci/runner_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Runner, :clean_gitlab_redis_shared_state do
+RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
include StubGitlabCalls
include RedisHelpers
include WorkhorseHelpers
@@ -13,7 +13,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
stub_feature_flags(ci_enable_live_trace: true)
stub_gitlab_calls
stub_application_setting(runners_registration_token: registration_token)
- allow_any_instance_of(Ci::Runner).to receive(:cache_attributes)
+ allow_any_instance_of(::Ci::Runner).to receive(:cache_attributes)
end
describe '/api/v4/runners' do
@@ -38,7 +38,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
it 'creates runner with default values' do
post api('/runners'), params: { token: registration_token }
- runner = Ci::Runner.first
+ runner = ::Ci::Runner.first
expect(response).to have_gitlab_http_status(:created)
expect(json_response['id']).to eq(runner.id)
@@ -57,7 +57,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
expect(response).to have_gitlab_http_status(:created)
expect(project.runners.size).to eq(1)
- runner = Ci::Runner.first
+ runner = ::Ci::Runner.first
expect(runner.token).not_to eq(registration_token)
expect(runner.token).not_to eq(project.runners_token)
expect(runner).to be_project_type
@@ -72,7 +72,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
expect(response).to have_gitlab_http_status(:created)
expect(group.runners.reload.size).to eq(1)
- runner = Ci::Runner.first
+ runner = ::Ci::Runner.first
expect(runner.token).not_to eq(registration_token)
expect(runner.token).not_to eq(group.runners_token)
expect(runner).to be_group_type
@@ -88,7 +88,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.description).to eq('server.hostname')
+ expect(::Ci::Runner.first.description).to eq('server.hostname')
end
end
@@ -100,7 +100,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.tag_list.sort).to eq(%w(tag1 tag2))
+ expect(::Ci::Runner.first.tag_list.sort).to eq(%w(tag1 tag2))
end
end
@@ -114,8 +114,8 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.run_untagged).to be false
- expect(Ci::Runner.first.tag_list.sort).to eq(['tag'])
+ expect(::Ci::Runner.first.run_untagged).to be false
+ expect(::Ci::Runner.first.tag_list.sort).to eq(['tag'])
end
end
@@ -141,7 +141,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.locked).to be true
+ expect(::Ci::Runner.first.locked).to be true
end
end
@@ -154,7 +154,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.active).to be true
+ expect(::Ci::Runner.first.active).to be true
end
end
@@ -166,7 +166,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.active).to be false
+ expect(::Ci::Runner.first.active).to be false
end
end
end
@@ -180,7 +180,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.ref_protected?).to be true
+ expect(::Ci::Runner.first.ref_protected?).to be true
end
end
@@ -192,7 +192,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.ref_protected?).to be false
+ expect(::Ci::Runner.first.ref_protected?).to be false
end
end
end
@@ -205,7 +205,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.maximum_timeout).to eq(9000)
+ expect(::Ci::Runner.first.maximum_timeout).to eq(9000)
end
context 'when maximum job timeout is empty' do
@@ -216,7 +216,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.maximum_timeout).to be_nil
+ expect(::Ci::Runner.first.maximum_timeout).to be_nil
end
end
end
@@ -232,7 +232,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.read_attribute(param.to_sym)).to eq(value)
+ expect(::Ci::Runner.first.read_attribute(param.to_sym)).to eq(value)
end
end
end
@@ -243,7 +243,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
headers: { 'X-Forwarded-For' => '123.111.123.111' }
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.ip_address).to eq('123.111.123.111')
+ expect(::Ci::Runner.first.ip_address).to eq('123.111.123.111')
end
end
@@ -271,7 +271,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
delete api('/runners'), params: { token: runner.token }
expect(response).to have_gitlab_http_status(:no_content)
- expect(Ci::Runner.count).to eq(0)
+ expect(::Ci::Runner.count).to eq(0)
end
it_behaves_like '412 response' do
@@ -518,6 +518,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
request_job info: { platform: :darwin }
expect(response).to have_gitlab_http_status(:created)
+ expect(response.headers['Content-Type']).to eq('application/json')
expect(response.headers).not_to have_key('X-GitLab-Last-Update')
expect(runner.reload.platform).to eq('darwin')
expect(json_response['id']).to eq(job.id)
@@ -537,7 +538,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
it 'creates persistent ref' do
- expect_any_instance_of(Ci::PersistentRef).to receive(:create_ref)
+ expect_any_instance_of(::Ci::PersistentRef).to receive(:create_ref)
.with(job.sha, "refs/#{Repository::REF_PIPELINES}/#{job.commit_id}")
request_job info: { platform: :darwin }
@@ -569,6 +570,24 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
end
+ context 'when a Gitaly exception is thrown during response' do
+ before do
+ allow_next_instance_of(Ci::BuildRunnerPresenter) do |instance|
+ allow(instance).to receive(:artifacts).and_raise(GRPC::DeadlineExceeded)
+ end
+ end
+
+ it 'fails the job as a scheduler failure' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ expect(job.reload.failed?).to be_truthy
+ expect(job.failure_reason).to eq('scheduler_failure')
+ expect(job.runner_id).to eq(runner.id)
+ expect(job.runner_session).to be_nil
+ end
+ end
+
context 'when GIT_DEPTH is not specified and there is no default git depth for the project' do
before do
project.update!(ci_default_git_depth: nil)
@@ -651,9 +670,9 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when job is for a release' do
let!(:job) { create(:ci_build, :release_options, pipeline: pipeline) }
- context 'when `release_steps` is passed by the runner' do
+ context 'when `multi_build_steps` is passed by the runner' do
it 'exposes release info' do
- request_job info: { features: { release_steps: true } }
+ request_job info: { features: { multi_build_steps: true } }
expect(response).to have_gitlab_http_status(:created)
expect(response.headers).not_to have_key('X-GitLab-Last-Update')
@@ -668,7 +687,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
{
"name" => "release",
"script" =>
- "release-cli create --ref \"$CI_COMMIT_SHA\" --name \"Release $CI_COMMIT_SHA\" --tag-name \"release-$CI_COMMIT_SHA\" --description \"Created using the release-cli $EXTRA_DESCRIPTION\"",
+ ["release-cli create --name \"Release $CI_COMMIT_SHA\" --description \"Created using the release-cli $EXTRA_DESCRIPTION\" --tag-name \"release-$CI_COMMIT_SHA\" --ref \"$CI_COMMIT_SHA\""],
"timeout" => 3600,
"when" => "on_success",
"allow_failure" => false
@@ -677,7 +696,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
end
- context 'when `release_steps` is not passed by the runner' do
+ context 'when `multi_build_steps` is not passed by the runner' do
it 'drops the job' do
request_job
@@ -749,7 +768,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when concurrently updating a job' do
before do
- expect_any_instance_of(Ci::Build).to receive(:run!)
+ expect_any_instance_of(::Ci::Build).to receive(:run!)
.and_raise(ActiveRecord::StaleObjectError.new(nil, nil))
end
@@ -890,7 +909,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
let!(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, builds: [job], trigger: trigger) }
before do
- project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
+ project.variables << ::Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
end
shared_examples 'expected variables behavior' do
@@ -1090,7 +1109,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
def request_job(token = runner.token, **params)
new_params = params.merge(token: token, last_update: last_update)
- post api('/jobs/request'), params: new_params, headers: { 'User-Agent' => user_agent }
+ post api('/jobs/request'), params: new_params.to_json, headers: { 'User-Agent' => user_agent, 'Content-Type': 'application/json' }
end
end
@@ -1099,7 +1118,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
let_it_be(:project) { create(:project, :repository) }
let(:runner) { create(:ci_runner, :project, projects: [project]) }
- let(:service) { Ci::CreateWebIdeTerminalService.new(project, user, ref: 'master').execute }
+ let(:service) { ::Ci::CreateWebIdeTerminalService.new(project, user, ref: 'master').execute }
let(:pipeline) { service[:pipeline] }
let(:build) { pipeline.builds.first }
let(:job) { {} }
@@ -1592,8 +1611,105 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
job.run!
end
+ shared_examples_for 'rejecting artifacts that are too large' do
+ let(:filesize) { 100.megabytes.to_i }
+ let(:sample_max_size) { (filesize / 1.megabyte) - 10 } # Set max size to be smaller than file size to trigger error
+
+ shared_examples_for 'failed request' do
+ it 'responds with payload too large error' do
+ send_request
+
+ expect(response).to have_gitlab_http_status(:payload_too_large)
+ end
+ end
+
+ context 'based on plan limit setting' do
+ let(:application_max_size) { sample_max_size + 100 }
+ let(:limit_name) { "#{Ci::JobArtifact::PLAN_LIMIT_PREFIX}archive" }
+
+ before do
+ create(:plan_limits, :default_plan, limit_name => sample_max_size)
+ stub_application_setting(max_artifacts_size: application_max_size)
+ end
+
+ context 'and feature flag ci_max_artifact_size_per_type is enabled' do
+ before do
+ stub_feature_flags(ci_max_artifact_size_per_type: true)
+ end
+
+ it_behaves_like 'failed request'
+ end
+
+ context 'and feature flag ci_max_artifact_size_per_type is disabled' do
+ before do
+ stub_feature_flags(ci_max_artifact_size_per_type: false)
+ end
+
+ it 'bases of project closest setting' do
+ send_request
+
+ expect(response).to have_gitlab_http_status(success_code)
+ end
+ end
+ end
+
+ context 'based on application setting' do
+ before do
+ stub_application_setting(max_artifacts_size: sample_max_size)
+ end
+
+ it_behaves_like 'failed request'
+ end
+
+ context 'based on root namespace setting' do
+ let(:application_max_size) { sample_max_size + 10 }
+
+ before do
+ stub_application_setting(max_artifacts_size: application_max_size)
+ root_namespace.update!(max_artifacts_size: sample_max_size)
+ end
+
+ it_behaves_like 'failed request'
+ end
+
+ context 'based on child namespace setting' do
+ let(:application_max_size) { sample_max_size + 10 }
+ let(:root_namespace_max_size) { sample_max_size + 10 }
+
+ before do
+ stub_application_setting(max_artifacts_size: application_max_size)
+ root_namespace.update!(max_artifacts_size: root_namespace_max_size)
+ namespace.update!(max_artifacts_size: sample_max_size)
+ end
+
+ it_behaves_like 'failed request'
+ end
+
+ context 'based on project setting' do
+ let(:application_max_size) { sample_max_size + 10 }
+ let(:root_namespace_max_size) { sample_max_size + 10 }
+ let(:child_namespace_max_size) { sample_max_size + 10 }
+
+ before do
+ stub_application_setting(max_artifacts_size: application_max_size)
+ root_namespace.update!(max_artifacts_size: root_namespace_max_size)
+ namespace.update!(max_artifacts_size: child_namespace_max_size)
+ project.update!(max_artifacts_size: sample_max_size)
+ end
+
+ it_behaves_like 'failed request'
+ end
+ end
+
describe 'POST /api/v4/jobs/:id/artifacts/authorize' do
context 'when using token as parameter' do
+ context 'and the artifact is too large' do
+ it_behaves_like 'rejecting artifacts that are too large' do
+ let(:success_code) { :ok }
+ let(:send_request) { authorize_artifacts_with_token_in_params(filesize: filesize) }
+ end
+ end
+
context 'posting artifacts to running job' do
subject do
authorize_artifacts_with_token_in_params
@@ -1651,56 +1767,6 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
end
end
-
- context 'when artifact is too large' do
- let(:sample_max_size) { 100 }
-
- shared_examples_for 'rejecting too large artifacts' do
- it 'fails to post' do
- authorize_artifacts_with_token_in_params(filesize: sample_max_size.megabytes.to_i)
-
- expect(response).to have_gitlab_http_status(:payload_too_large)
- end
- end
-
- context 'based on application setting' do
- before do
- stub_application_setting(max_artifacts_size: sample_max_size)
- end
-
- it_behaves_like 'rejecting too large artifacts'
- end
-
- context 'based on root namespace setting' do
- before do
- stub_application_setting(max_artifacts_size: 200)
- root_namespace.update!(max_artifacts_size: sample_max_size)
- end
-
- it_behaves_like 'rejecting too large artifacts'
- end
-
- context 'based on child namespace setting' do
- before do
- stub_application_setting(max_artifacts_size: 200)
- root_namespace.update!(max_artifacts_size: 200)
- namespace.update!(max_artifacts_size: sample_max_size)
- end
-
- it_behaves_like 'rejecting too large artifacts'
- end
-
- context 'based on project setting' do
- before do
- stub_application_setting(max_artifacts_size: 200)
- root_namespace.update!(max_artifacts_size: 200)
- namespace.update!(max_artifacts_size: 200)
- project.update!(max_artifacts_size: sample_max_size)
- end
-
- it_behaves_like 'rejecting too large artifacts'
- end
- end
end
context 'when using token as header' do
@@ -1757,19 +1823,36 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
expect(json_response['ProcessLsif']).to be_truthy
end
- it 'fails to authorize too large artifact' do
- authorize_artifacts_with_token_in_headers(artifact_type: :lsif, filesize: 30.megabytes)
+ it 'adds ProcessLsifReferences header' do
+ authorize_artifacts_with_token_in_headers(artifact_type: :lsif)
- expect(response).to have_gitlab_http_status(:payload_too_large)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['ProcessLsifReferences']).to be_truthy
end
context 'code_navigation feature flag is disabled' do
- it 'does not add ProcessLsif header' do
+ it 'responds with a forbidden error' do
stub_feature_flags(code_navigation: false)
+ authorize_artifacts_with_token_in_headers(artifact_type: :lsif)
+
+ aggregate_failures do
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(json_response['ProcessLsif']).to be_falsy
+ expect(json_response['ProcessLsifReferences']).to be_falsy
+ end
+ end
+ end
+ context 'code_navigation_references feature flag is disabled' do
+ it 'sets ProcessLsifReferences header to false' do
+ stub_feature_flags(code_navigation_references: false)
authorize_artifacts_with_token_in_headers(artifact_type: :lsif)
- expect(response).to have_gitlab_http_status(:forbidden)
+ aggregate_failures do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['ProcessLsif']).to be_truthy
+ expect(json_response['ProcessLsifReferences']).to be_falsy
+ end
end
end
end
@@ -1799,6 +1882,32 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
expect { upload_artifacts(file_upload, headers_with_token) }.to change { runner.reload.contacted_at }
end
+ context 'when the artifact is too large' do
+ it_behaves_like 'rejecting artifacts that are too large' do
+ # This filesize validation also happens in non remote stored files,
+ # it's just that it's hard to stub the filesize in other cases to be
+ # more than a megabyte.
+ let!(:fog_connection) do
+ stub_artifacts_object_storage(direct_upload: true)
+ end
+ let(:object) do
+ fog_connection.directories.new(key: 'artifacts').files.create(
+ key: 'tmp/uploads/12312300',
+ body: 'content'
+ )
+ end
+ let(:file_upload) { fog_to_uploaded_file(object) }
+ let(:send_request) do
+ upload_artifacts(file_upload, headers_with_token, 'file.remote_id' => '12312300')
+ end
+ let(:success_code) { :created }
+
+ before do
+ allow(object).to receive(:content_length).and_return(filesize)
+ end
+ end
+ end
+
context 'when artifacts are being stored inside of tmp path' do
before do
# by configuring this path we allow to pass temp file from any path
@@ -1877,16 +1986,6 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
end
- context 'when artifacts file is too large' do
- it 'fails to post too large artifact' do
- stub_application_setting(max_artifacts_size: 0)
-
- upload_artifacts(file_upload, headers_with_token)
-
- expect(response).to have_gitlab_http_status(:payload_too_large)
- end
- end
-
context 'when artifacts post request does not contain file' do
it 'fails to post artifacts without file' do
post api("/jobs/#{job.id}/artifacts"), params: {}, headers: headers_with_token
@@ -2258,7 +2357,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
FileUtils.remove_entry(new_tmpdir)
end
- it' "fails to post artifacts for outside of tmp path"' do
+ it 'fails to post artifacts for outside of tmp path' do
upload_artifacts(file_upload, headers_with_token)
expect(response).to have_gitlab_http_status(:bad_request)
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/ci/runners_spec.rb
index 67c258260bf..670456e5dba 100644
--- a/spec/requests/api/runners_spec.rb
+++ b/spec/requests/api/ci/runners_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Runners do
+RSpec.describe API::Ci::Runners do
let_it_be(:admin) { create(:user, :admin) }
let_it_be(:user) { create(:user) }
let_it_be(:user2) { create(:user) }
@@ -266,7 +266,7 @@ describe API::Runners do
delete api("/runners/#{unused_project_runner.id}", admin)
expect(response).to have_gitlab_http_status(:no_content)
- end.to change { Ci::Runner.project_type.count }.by(-1)
+ end.to change { ::Ci::Runner.project_type.count }.by(-1)
end
end
@@ -284,7 +284,7 @@ describe API::Runners do
end
end
- it 'returns 404 if runner does not exists' do
+ it 'returns 404 if runner does not exist' do
get api('/runners/0', admin)
expect(response).to have_gitlab_http_status(:not_found)
@@ -437,7 +437,7 @@ describe API::Runners do
end
end
- it 'returns 404 if runner does not exists' do
+ it 'returns 404 if runner does not exist' do
update_runner(0, admin, description: 'test')
expect(response).to have_gitlab_http_status(:not_found)
@@ -493,7 +493,7 @@ describe API::Runners do
delete api("/runners/#{shared_runner.id}", admin)
expect(response).to have_gitlab_http_status(:no_content)
- end.to change { Ci::Runner.instance_type.count }.by(-1)
+ end.to change { ::Ci::Runner.instance_type.count }.by(-1)
end
it_behaves_like '412 response' do
@@ -507,11 +507,11 @@ describe API::Runners do
delete api("/runners/#{project_runner.id}", admin)
expect(response).to have_gitlab_http_status(:no_content)
- end.to change { Ci::Runner.project_type.count }.by(-1)
+ end.to change { ::Ci::Runner.project_type.count }.by(-1)
end
end
- it 'returns 404 if runner does not exists' do
+ it 'returns 404 if runner does not exist' do
delete api('/runners/0', admin)
expect(response).to have_gitlab_http_status(:not_found)
@@ -542,7 +542,7 @@ describe API::Runners do
delete api("/runners/#{project_runner.id}", user)
expect(response).to have_gitlab_http_status(:no_content)
- end.to change { Ci::Runner.project_type.count }.by(-1)
+ end.to change { ::Ci::Runner.project_type.count }.by(-1)
end
it 'does not delete group runner with guest access' do
@@ -574,7 +574,7 @@ describe API::Runners do
delete api("/runners/#{group_runner_a.id}", user)
expect(response).to have_gitlab_http_status(:no_content)
- end.to change { Ci::Runner.group_type.count }.by(-1)
+ end.to change { ::Ci::Runner.group_type.count }.by(-1)
end
it 'deletes inherited group runner with owner access' do
@@ -582,7 +582,7 @@ describe API::Runners do
delete api("/runners/#{group_runner_b.id}", user)
expect(response).to have_gitlab_http_status(:no_content)
- end.to change { Ci::Runner.group_type.count }.by(-1)
+ end.to change { ::Ci::Runner.group_type.count }.by(-1)
end
it_behaves_like '412 response' do
@@ -968,7 +968,7 @@ describe API::Runners do
end
it 'does not enable locked runner' do
- project_runner2.update(locked: true)
+ project_runner2.update!(locked: true)
expect do
post api("/projects/#{project.id}/runners", user), params: { runner_id: project_runner2.id }
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index 0c0bf8b4df0..bec15b788c3 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::CommitStatuses do
+RSpec.describe API::CommitStatuses do
let!(:project) { create(:project, :repository) }
let(:commit) { project.repository.commit }
let(:guest) { create_user(:guest) }
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index a423c92e2fb..724e3177173 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'mime/types'
-describe API::Commits do
+RSpec.describe API::Commits do
include ProjectForksHelper
let(:user) { create(:user) }
diff --git a/spec/requests/api/composer_packages_spec.rb b/spec/requests/api/composer_packages_spec.rb
new file mode 100644
index 00000000000..d756a7700f6
--- /dev/null
+++ b/spec/requests/api/composer_packages_spec.rb
@@ -0,0 +1,302 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe API::ComposerPackages do
+ include PackagesManagerApiSpecHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group, reload: true) { create(:group, :public) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:package_name) { 'package-name' }
+ let_it_be(:project, reload: true) { create(:project, :custom_repo, files: { 'composer.json' => { name: package_name }.to_json }, group: group) }
+ let(:headers) { {} }
+
+ describe 'GET /api/v4/group/:id/-/packages/composer/packages' do
+ let(:url) { "/group/#{group.id}/-/packages/composer/packages.json" }
+
+ subject { get api(url), headers: headers }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ let!(:package) { create(:composer_package, :with_metadatum, project: project) }
+
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'Composer package index' | :success
+ 'PUBLIC' | :guest | true | true | 'Composer package index' | :success
+ 'PUBLIC' | :developer | true | false | 'Composer package index' | :success
+ 'PUBLIC' | :guest | true | false | 'Composer package index' | :success
+ 'PUBLIC' | :developer | false | true | 'Composer package index' | :success
+ 'PUBLIC' | :guest | false | true | 'Composer package index' | :success
+ 'PUBLIC' | :developer | false | false | 'Composer package index' | :success
+ 'PUBLIC' | :guest | false | false | 'Composer package index' | :success
+ 'PUBLIC' | :anonymous | false | true | 'Composer package index' | :success
+ 'PRIVATE' | :developer | true | true | 'Composer package index' | :success
+ 'PRIVATE' | :guest | true | true | 'Composer package index' | :success
+ 'PRIVATE' | :developer | true | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | true | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | false | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :not_found
+ end
+
+ with_them do
+ include_context 'Composer api group access', params[:project_visibility_level], params[:user_role], params[:user_token] do
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+ end
+
+ it_behaves_like 'rejects Composer access with unknown group id'
+ end
+ end
+
+ describe 'GET /api/v4/group/:id/-/packages/composer/p/:sha.json' do
+ let(:sha) { '123' }
+ let(:url) { "/group/#{group.id}/-/packages/composer/p/#{sha}.json" }
+ let!(:package) { create(:composer_package, :with_metadatum, project: project) }
+
+ subject { get api(url), headers: headers }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'Composer provider index' | :success
+ 'PUBLIC' | :guest | true | true | 'Composer provider index' | :success
+ 'PUBLIC' | :developer | true | false | 'Composer provider index' | :success
+ 'PUBLIC' | :guest | true | false | 'Composer provider index' | :success
+ 'PUBLIC' | :developer | false | true | 'Composer provider index' | :success
+ 'PUBLIC' | :guest | false | true | 'Composer provider index' | :success
+ 'PUBLIC' | :developer | false | false | 'Composer provider index' | :success
+ 'PUBLIC' | :guest | false | false | 'Composer provider index' | :success
+ 'PUBLIC' | :anonymous | false | true | 'Composer provider index' | :success
+ 'PRIVATE' | :developer | true | true | 'Composer provider index' | :success
+ 'PRIVATE' | :guest | true | true | 'Composer empty provider index' | :success
+ 'PRIVATE' | :developer | true | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | true | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | false | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :not_found
+ end
+
+ with_them do
+ include_context 'Composer api group access', params[:project_visibility_level], params[:user_role], params[:user_token] do
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+ end
+
+ it_behaves_like 'rejects Composer access with unknown group id'
+ end
+ end
+
+ describe 'GET /api/v4/group/:id/-/packages/composer/*package_name.json' do
+ let(:package_name) { 'foobar' }
+ let(:url) { "/group/#{group.id}/-/packages/composer/#{package_name}.json" }
+
+ subject { get api(url), headers: headers }
+
+ context 'without the need for a license' do
+ context 'with no packages' do
+ include_context 'Composer user type', :developer, true do
+ it_behaves_like 'returning response status', :not_found
+ end
+ end
+
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ let!(:package) { create(:composer_package, :with_metadatum, name: package_name, project: project) }
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'Composer package api request' | :success
+ 'PUBLIC' | :guest | true | true | 'Composer package api request' | :success
+ 'PUBLIC' | :developer | true | false | 'Composer package api request' | :success
+ 'PUBLIC' | :guest | true | false | 'Composer package api request' | :success
+ 'PUBLIC' | :developer | false | true | 'Composer package api request' | :success
+ 'PUBLIC' | :guest | false | true | 'Composer package api request' | :success
+ 'PUBLIC' | :developer | false | false | 'Composer package api request' | :success
+ 'PUBLIC' | :guest | false | false | 'Composer package api request' | :success
+ 'PUBLIC' | :anonymous | false | true | 'Composer package api request' | :success
+ 'PRIVATE' | :developer | true | true | 'Composer package api request' | :success
+ 'PRIVATE' | :guest | true | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | true | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | true | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | false | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :not_found
+ end
+
+ with_them do
+ include_context 'Composer api group access', params[:project_visibility_level], params[:user_role], params[:user_token] do
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+ end
+
+ it_behaves_like 'rejects Composer access with unknown group id'
+ end
+ end
+
+ describe 'POST /api/v4/projects/:id/packages/composer' do
+ let(:url) { "/projects/#{project.id}/packages/composer" }
+ let(:params) { {} }
+
+ before(:all) do
+ project.repository.add_tag(user, 'v1.2.99', 'master')
+ end
+
+ subject { post api(url), headers: headers, params: params }
+
+ shared_examples 'composer package publish' do
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'Composer package creation' | :created
+ 'PUBLIC' | :guest | true | true | 'process Composer api request' | :forbidden
+ 'PUBLIC' | :developer | true | false | 'process Composer api request' | :unauthorized
+ 'PUBLIC' | :guest | true | false | 'process Composer api request' | :unauthorized
+ 'PUBLIC' | :developer | false | true | 'process Composer api request' | :forbidden
+ 'PUBLIC' | :guest | false | true | 'process Composer api request' | :forbidden
+ 'PUBLIC' | :developer | false | false | 'process Composer api request' | :unauthorized
+ 'PUBLIC' | :guest | false | false | 'process Composer api request' | :unauthorized
+ 'PUBLIC' | :anonymous | false | true | 'process Composer api request' | :unauthorized
+ 'PRIVATE' | :developer | true | true | 'Composer package creation' | :created
+ 'PRIVATE' | :guest | true | true | 'process Composer api request' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'process Composer api request' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'process Composer api request' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process Composer api request' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'process Composer api request' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :unauthorized
+ end
+
+ with_them do
+ include_context 'Composer api project access', params[:project_visibility_level], params[:user_role], params[:user_token] do
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+ end
+
+ it_behaves_like 'rejects Composer access with unknown project id'
+ end
+ end
+
+ context 'with no tag or branch params' do
+ let(:headers) { build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'process Composer api request', :developer, :bad_request
+ end
+
+ context 'with a tag' do
+ context 'with an existing branch' do
+ let(:params) { { tag: 'v1.2.99' } }
+
+ it_behaves_like 'composer package publish'
+ end
+
+ context 'with a non existing tag' do
+ let(:params) { { tag: 'non-existing-tag' } }
+ let(:headers) { build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'process Composer api request', :developer, :not_found
+ end
+ end
+
+ context 'with a branch' do
+ context 'with an existing branch' do
+ let(:params) { { branch: 'master' } }
+
+ it_behaves_like 'composer package publish'
+ end
+
+ context 'with a non existing branch' do
+ let(:params) { { branch: 'non-existing-branch' } }
+ let(:headers) { build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'process Composer api request', :developer, :not_found
+ end
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/composer/archives/*package_name?sha=:sha' do
+ let(:sha) { '123' }
+ let(:url) { "/projects/#{project.id}/packages/composer/archives/#{package_name}.zip" }
+ let(:params) { { sha: sha } }
+
+ subject { get api(url), headers: headers, params: params }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ let!(:package) { create(:composer_package, :with_metadatum, name: package_name, project: project) }
+
+ context 'when the sha does not match the package name' do
+ let(:sha) { '123' }
+
+ it_behaves_like 'process Composer api request', :anonymous, :not_found
+ end
+
+ context 'when the package name does not match the sha' do
+ let(:branch) { project.repository.find_branch('master') }
+ let(:sha) { branch.target }
+ let(:url) { "/projects/#{project.id}/packages/composer/archives/unexisting-package-name.zip" }
+
+ it_behaves_like 'process Composer api request', :anonymous, :not_found
+ end
+
+ context 'with a match package name and sha' do
+ let(:branch) { project.repository.find_branch('master') }
+ let(:sha) { branch.target }
+
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :expected_status) do
+ 'PUBLIC' | :developer | true | true | :success
+ 'PUBLIC' | :guest | true | true | :success
+ 'PUBLIC' | :developer | true | false | :success
+ 'PUBLIC' | :guest | true | false | :success
+ 'PUBLIC' | :developer | false | true | :success
+ 'PUBLIC' | :guest | false | true | :success
+ 'PUBLIC' | :developer | false | false | :success
+ 'PUBLIC' | :guest | false | false | :success
+ 'PUBLIC' | :anonymous | false | true | :success
+ 'PRIVATE' | :developer | true | true | :success
+ 'PRIVATE' | :guest | true | true | :success
+ 'PRIVATE' | :developer | true | false | :success
+ 'PRIVATE' | :guest | true | false | :success
+ 'PRIVATE' | :developer | false | true | :success
+ 'PRIVATE' | :guest | false | true | :success
+ 'PRIVATE' | :developer | false | false | :success
+ 'PRIVATE' | :guest | false | false | :success
+ 'PRIVATE' | :anonymous | false | true | :success
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like 'process Composer api request', params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+ end
+
+ it_behaves_like 'rejects Composer access with unknown project id'
+ end
+ end
+end
diff --git a/spec/requests/api/conan_packages_spec.rb b/spec/requests/api/conan_packages_spec.rb
new file mode 100644
index 00000000000..1d88eaef79c
--- /dev/null
+++ b/spec/requests/api/conan_packages_spec.rb
@@ -0,0 +1,840 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe API::ConanPackages do
+ include WorkhorseHelpers
+ include PackagesManagerApiSpecHelpers
+
+ let(:package) { create(:conan_package) }
+ let_it_be(:personal_access_token) { create(:personal_access_token) }
+ let_it_be(:user) { personal_access_token.user }
+ let(:project) { package.project }
+
+ let(:base_secret) { SecureRandom.base64(64) }
+ let(:auth_token) { personal_access_token.token }
+ let(:job) { create(:ci_build, user: user) }
+ let(:job_token) { job.token }
+ let(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
+ let(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
+
+ let(:headers) do
+ { 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials('foo', auth_token) }
+ end
+
+ let(:jwt_secret) do
+ OpenSSL::HMAC.hexdigest(
+ OpenSSL::Digest::SHA256.new,
+ base_secret,
+ Gitlab::ConanToken::HMAC_KEY
+ )
+ end
+
+ before do
+ project.add_developer(user)
+ allow(Settings).to receive(:attr_encrypted_db_key_base).and_return(base_secret)
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/ping' do
+ it 'responds with 401 Unauthorized when no token provided' do
+ get api('/packages/conan/v1/ping')
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'responds with 200 OK when valid token is provided' do
+ jwt = build_jwt(personal_access_token)
+ get api('/packages/conan/v1/ping'), headers: build_token_auth_header(jwt.encoded)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers['X-Conan-Server-Capabilities']).to eq("")
+ end
+
+ it 'responds with 200 OK when valid job token is provided' do
+ jwt = build_jwt_from_job(job)
+ get api('/packages/conan/v1/ping'), headers: build_token_auth_header(jwt.encoded)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers['X-Conan-Server-Capabilities']).to eq("")
+ end
+
+ it 'responds with 200 OK when valid deploy token is provided' do
+ jwt = build_jwt_from_deploy_token(deploy_token)
+ get api('/packages/conan/v1/ping'), headers: build_token_auth_header(jwt.encoded)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers['X-Conan-Server-Capabilities']).to eq("")
+ end
+
+ it 'responds with 401 Unauthorized when invalid access token ID is provided' do
+ jwt = build_jwt(double(id: 12345), user_id: personal_access_token.user_id)
+ get api('/packages/conan/v1/ping'), headers: build_token_auth_header(jwt.encoded)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'responds with 401 Unauthorized when invalid user is provided' do
+ jwt = build_jwt(personal_access_token, user_id: 12345)
+ get api('/packages/conan/v1/ping'), headers: build_token_auth_header(jwt.encoded)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'responds with 401 Unauthorized when the provided JWT is signed with different secret' do
+ jwt = build_jwt(personal_access_token, secret: SecureRandom.base64(32))
+ get api('/packages/conan/v1/ping'), headers: build_token_auth_header(jwt.encoded)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'responds with 401 Unauthorized when invalid JWT is provided' do
+ get api('/packages/conan/v1/ping'), headers: build_token_auth_header('invalid-jwt')
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ context 'packages feature disabled' do
+ it 'responds with 404 Not Found' do
+ stub_packages_setting(enabled: false)
+ get api('/packages/conan/v1/ping')
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/conans/search' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+
+ get api('/packages/conan/v1/conans/search'), headers: headers, params: params
+ end
+
+ subject { json_response['results'] }
+
+ context 'returns packages with a matching name' do
+ let(:params) { { q: package.conan_recipe } }
+
+ it { is_expected.to contain_exactly(package.conan_recipe) }
+ end
+
+ context 'returns packages using a * wildcard' do
+ let(:params) { { q: "#{package.name[0, 3]}*" } }
+
+ it { is_expected.to contain_exactly(package.conan_recipe) }
+ end
+
+ context 'does not return non-matching packages' do
+ let(:params) { { q: "foo" } }
+
+ it { is_expected.to be_blank }
+ end
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/users/authenticate' do
+ subject { get api('/packages/conan/v1/users/authenticate'), headers: headers }
+
+ context 'when using invalid token' do
+ let(:auth_token) { 'invalid_token' }
+
+ it 'responds with 401' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ context 'when valid JWT access token is provided' do
+ it 'responds with 200' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'token has valid validity time' do
+ Timecop.freeze do
+ subject
+
+ payload = JSONWebToken::HMACToken.decode(
+ response.body, jwt_secret).first
+ expect(payload['access_token']).to eq(personal_access_token.id)
+ expect(payload['user_id']).to eq(personal_access_token.user_id)
+
+ duration = payload['exp'] - payload['iat']
+ expect(duration).to eq(1.hour)
+ end
+ end
+ end
+
+ context 'with valid job token' do
+ let(:auth_token) { job_token }
+
+ it 'responds with 200' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'with valid deploy token' do
+ let(:auth_token) { deploy_token.token }
+
+ it 'responds with 200' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/users/check_credentials' do
+ it 'responds with a 200 OK with PAT' do
+ get api('/packages/conan/v1/users/check_credentials'), headers: headers
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ context 'with job token' do
+ let(:auth_token) { job_token }
+
+ it 'responds with a 200 OK with job token' do
+ get api('/packages/conan/v1/users/check_credentials'), headers: headers
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'with deploy token' do
+ let(:auth_token) { deploy_token.token }
+
+ it 'responds with a 200 OK with job token' do
+ get api('/packages/conan/v1/users/check_credentials'), headers: headers
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ it 'responds with a 401 Unauthorized when an invalid token is used' do
+ get api('/packages/conan/v1/users/check_credentials'), headers: build_token_auth_header('invalid-token')
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ shared_examples 'rejects invalid recipe' do
+ context 'with invalid recipe path' do
+ let(:recipe_path) { '../../foo++../..' }
+
+ it 'returns 400' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+
+ shared_examples 'rejects recipe for invalid project' do
+ context 'with invalid recipe path' do
+ let(:recipe_path) { 'aa/bb/not-existing-project/ccc' }
+
+ it 'returns forbidden' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ shared_examples 'rejects recipe for not found package' do
+ context 'with invalid recipe path' do
+ let(:recipe_path) do
+ 'aa/bb/%{project}/ccc' % { project: ::Packages::Conan::Metadatum.package_username_from(full_path: project.full_path) }
+ end
+
+ it 'returns not found' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ shared_examples 'empty recipe for not found package' do
+ context 'with invalid recipe url' do
+ let(:recipe_path) do
+ 'aa/bb/%{project}/ccc' % { project: ::Packages::Conan::Metadatum.package_username_from(full_path: project.full_path) }
+ end
+
+ it 'returns not found' do
+ allow(::Packages::Conan::PackagePresenter).to receive(:new)
+ .with(
+ 'aa/bb@%{project}/ccc' % { project: ::Packages::Conan::Metadatum.package_username_from(full_path: project.full_path) },
+ user,
+ project,
+ any_args
+ ).and_return(presenter)
+ allow(presenter).to receive(:recipe_snapshot) { {} }
+ allow(presenter).to receive(:package_snapshot) { {} }
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body).to eq("{}")
+ end
+ end
+ end
+
+ shared_examples 'recipe download_urls' do
+ let(:recipe_path) { package.conan_recipe_path }
+
+ it 'returns the download_urls for the recipe files' do
+ expected_response = {
+ 'conanfile.py' => "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanfile.py",
+ 'conanmanifest.txt' => "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanmanifest.txt"
+ }
+
+ allow(presenter).to receive(:recipe_urls) { expected_response }
+
+ subject
+
+ expect(json_response).to eq(expected_response)
+ end
+ end
+
+ shared_examples 'package download_urls' do
+ let(:recipe_path) { package.conan_recipe_path }
+
+ it 'returns the download_urls for the package files' do
+ expected_response = {
+ 'conaninfo.txt' => "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conaninfo.txt",
+ 'conanmanifest.txt' => "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conanmanifest.txt",
+ 'conan_package.tgz' => "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conan_package.tgz"
+ }
+
+ allow(presenter).to receive(:package_urls) { expected_response }
+
+ subject
+
+ expect(json_response).to eq(expected_response)
+ end
+ end
+
+ context 'recipe endpoints' do
+ let(:jwt) { build_jwt(personal_access_token) }
+ let(:headers) { build_token_auth_header(jwt.encoded) }
+ let(:conan_package_reference) { '123456789' }
+ let(:presenter) { double('::Packages::Conan::PackagePresenter') }
+
+ before do
+ allow(::Packages::Conan::PackagePresenter).to receive(:new)
+ .with(package.conan_recipe, user, package.project, any_args)
+ .and_return(presenter)
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel' do
+ let(:recipe_path) { package.conan_recipe_path }
+
+ subject { get api("/packages/conan/v1/conans/#{recipe_path}"), headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'rejects recipe for invalid project'
+ it_behaves_like 'empty recipe for not found package'
+
+ context 'with existing package' do
+ it 'returns a hash of files with their md5 hashes' do
+ expected_response = {
+ 'conanfile.py' => 'md5hash1',
+ 'conanmanifest.txt' => 'md5hash2'
+ }
+
+ allow(presenter).to receive(:recipe_snapshot) { expected_response }
+
+ subject
+
+ expect(json_response).to eq(expected_response)
+ end
+ end
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference' do
+ let(:recipe_path) { package.conan_recipe_path }
+
+ subject { get api("/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}"), headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'rejects recipe for invalid project'
+ it_behaves_like 'empty recipe for not found package'
+
+ context 'with existing package' do
+ it 'returns a hash of md5 values for the files' do
+ expected_response = {
+ 'conaninfo.txt' => "md5hash1",
+ 'conanmanifest.txt' => "md5hash2",
+ 'conan_package.tgz' => "md5hash3"
+ }
+
+ allow(presenter).to receive(:package_snapshot) { expected_response }
+
+ subject
+
+ expect(json_response).to eq(expected_response)
+ end
+ end
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/digest' do
+ subject { get api("/packages/conan/v1/conans/#{recipe_path}/digest"), headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'rejects recipe for invalid project'
+ it_behaves_like 'recipe download_urls'
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/download_urls' do
+ subject { get api("/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}/download_urls"), headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'rejects recipe for invalid project'
+ it_behaves_like 'package download_urls'
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/download_urls' do
+ subject { get api("/packages/conan/v1/conans/#{recipe_path}/download_urls"), headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'rejects recipe for invalid project'
+ it_behaves_like 'recipe download_urls'
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/digest' do
+ subject { get api("/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}/digest"), headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'rejects recipe for invalid project'
+ it_behaves_like 'package download_urls'
+ end
+
+ describe 'POST /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/upload_urls' do
+ let(:recipe_path) { package.conan_recipe_path }
+
+ let(:params) do
+ { "conanfile.py": 24,
+ "conanmanifext.txt": 123 }
+ end
+
+ subject { post api("/packages/conan/v1/conans/#{recipe_path}/upload_urls"), params: params, headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+
+ it 'returns a set of upload urls for the files requested' do
+ subject
+
+ expected_response = {
+ 'conanfile.py': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanfile.py",
+ 'conanmanifest.txt': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanmanifest.txt"
+ }
+
+ expect(response.body).to eq(expected_response.to_json)
+ end
+ end
+
+ describe 'POST /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/upload_urls' do
+ let(:recipe_path) { package.conan_recipe_path }
+
+ let(:params) do
+ { "conaninfo.txt": 24,
+ "conanmanifext.txt": 123,
+ "conan_package.tgz": 523 }
+ end
+
+ subject { post api("/packages/conan/v1/conans/#{recipe_path}/packages/123456789/upload_urls"), params: params, headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+
+ it 'returns a set of upload urls for the files requested' do
+ expected_response = {
+ 'conaninfo.txt': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conaninfo.txt",
+ 'conanmanifest.txt': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conanmanifest.txt",
+ 'conan_package.tgz': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conan_package.tgz"
+ }
+
+ subject
+
+ expect(response.body).to eq(expected_response.to_json)
+ end
+ end
+
+ describe 'DELETE /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel' do
+ let(:recipe_path) { package.conan_recipe_path }
+
+ subject { delete api("/packages/conan/v1/conans/#{recipe_path}"), headers: headers}
+
+ it_behaves_like 'rejects invalid recipe'
+
+ it 'returns unauthorized for users without valid permission' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ context 'with delete permissions' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'delete_package'
+
+ it 'deletes a package' do
+ expect { subject }.to change { Packages::Package.count }.from(2).to(1)
+ end
+ end
+ end
+ end
+
+ context 'file endpoints' do
+ let(:jwt) { build_jwt(personal_access_token) }
+ let(:headers) { build_token_auth_header(jwt.encoded) }
+ let(:recipe_path) { package.conan_recipe_path }
+
+ shared_examples 'denies download with no token' do
+ context 'with no private token' do
+ let(:headers) { {} }
+
+ it 'returns 400' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ shared_examples 'a public project with packages' do
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+ end
+
+ shared_examples 'an internal project with packages' do
+ before do
+ project.team.truncate
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ it_behaves_like 'denies download with no token'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+ end
+
+ shared_examples 'a private project with packages' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ it_behaves_like 'denies download with no token'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download when not enough permissions' do
+ project.add_guest(user)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ shared_examples 'a project is not found' do
+ let(:recipe_path) { 'not/package/for/project' }
+
+ it 'returns forbidden' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/
+:recipe_revision/export/:file_name' do
+ let(:recipe_file) { package.package_files.find_by(file_name: 'conanfile.py') }
+ let(:metadata) { recipe_file.conan_file_metadatum }
+
+ subject do
+ get api("/packages/conan/v1/files/#{recipe_path}/#{metadata.recipe_revision}/export/#{recipe_file.file_name}"),
+ headers: headers
+ end
+
+ it_behaves_like 'a public project with packages'
+ it_behaves_like 'an internal project with packages'
+ it_behaves_like 'a private project with packages'
+ it_behaves_like 'a project is not found'
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/
+:recipe_revision/package/:conan_package_reference/:package_revision/:file_name' do
+ let(:package_file) { package.package_files.find_by(file_name: 'conaninfo.txt') }
+ let(:metadata) { package_file.conan_file_metadatum }
+
+ subject do
+ get api("/packages/conan/v1/files/#{recipe_path}/#{metadata.recipe_revision}/package/#{metadata.conan_package_reference}/#{metadata.package_revision}/#{package_file.file_name}"),
+ headers: headers
+ end
+
+ it_behaves_like 'a public project with packages'
+ it_behaves_like 'an internal project with packages'
+ it_behaves_like 'a private project with packages'
+ it_behaves_like 'a project is not found'
+
+ context 'tracking the conan_package.tgz download' do
+ let(:package_file) { package.package_files.find_by(file_name: ::Packages::Conan::FileMetadatum::PACKAGE_BINARY) }
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'pull_package'
+ end
+ end
+ end
+
+ context 'file uploads' do
+ let(:jwt) { build_jwt(personal_access_token) }
+ let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let(:headers_with_token) { build_token_auth_header(jwt.encoded).merge(workhorse_header) }
+ let(:recipe_path) { "foo/bar/#{project.full_path.tr('/', '+')}/baz"}
+
+ shared_examples 'uploads a package file' do
+ context 'with object storage disabled' do
+ context 'without a file from workhorse' do
+ let(:params) { { file: nil } }
+
+ it_behaves_like 'package workhorse uploads'
+
+ it 'rejects the request' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'without a token' do
+ it 'rejects request without a token' do
+ headers_with_token.delete('HTTP_AUTHORIZATION')
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ context 'when params from workhorse are correct' do
+ it 'creates package and stores package file' do
+ expect { subject }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+
+ package_file = project.packages.last.package_files.reload.last
+ expect(package_file.file_name).to eq(params[:file].original_filename)
+ end
+
+ it "doesn't attempt to migrate file to object storage" do
+ expect(ObjectStorage::BackgroundMoveWorker).not_to receive(:perform_async)
+
+ subject
+ end
+ end
+ end
+
+ context 'with object storage enabled' do
+ context 'and direct upload enabled' do
+ let!(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: true)
+ end
+
+ let(:tmp_object) do
+ fog_connection.directories.new(key: 'packages').files.create(
+ key: "tmp/uploads/#{file_name}",
+ body: 'content'
+ )
+ end
+
+ let(:fog_file) { fog_to_uploaded_file(tmp_object) }
+
+ ['123123', '../../123123'].each do |remote_id|
+ context "with invalid remote_id: #{remote_id}" do
+ let(:params) do
+ {
+ file: fog_file,
+ 'file.remote_id' => remote_id
+ }
+ end
+
+ it 'responds with status 403' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ context 'with valid remote_id' do
+ let(:params) do
+ {
+ file: fog_file,
+ 'file.remote_id' => file_name
+ }
+ end
+
+ it 'creates package and stores package file' do
+ expect { subject }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+
+ package_file = project.packages.last.package_files.reload.last
+ expect(package_file.file_name).to eq(params[:file].original_filename)
+ expect(package_file.file.read).to eq('content')
+ end
+ end
+ end
+
+ it_behaves_like 'background upload schedules a file migration'
+ end
+ end
+
+ shared_examples 'workhorse authorization' do
+ it 'authorizes posting package with a valid token' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ end
+
+ it 'rejects request without a valid token' do
+ headers_with_token['HTTP_AUTHORIZATION'] = 'foo'
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'rejects request without a valid permission' do
+ project.add_guest(user)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'rejects requests that bypassed gitlab-workhorse' do
+ headers_with_token.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ context 'when using remote storage' do
+ context 'when direct upload is enabled' do
+ before do
+ stub_package_file_object_storage(enabled: true, direct_upload: true)
+ end
+
+ it 'responds with status 200, location of package remote store and object details' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(json_response).not_to have_key('TempPath')
+ expect(json_response['RemoteObject']).to have_key('ID')
+ expect(json_response['RemoteObject']).to have_key('GetURL')
+ expect(json_response['RemoteObject']).to have_key('StoreURL')
+ expect(json_response['RemoteObject']).to have_key('DeleteURL')
+ expect(json_response['RemoteObject']).not_to have_key('MultipartUpload')
+ end
+ end
+
+ context 'when direct upload is disabled' do
+ before do
+ stub_package_file_object_storage(enabled: true, direct_upload: false)
+ end
+
+ it 'handles as a local file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(json_response['TempPath']).to eq(::Packages::PackageFileUploader.workhorse_local_upload_path)
+ expect(json_response['RemoteObject']).to be_nil
+ end
+ end
+ end
+ end
+
+ describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:file_name/authorize' do
+ subject { put api("/packages/conan/v1/files/#{recipe_path}/0/export/conanfile.py/authorize"), headers: headers_with_token }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'workhorse authorization'
+ end
+
+ describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:conan_package_reference/:package_revision/:file_name/authorize' do
+ subject { put api("/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/conaninfo.txt/authorize"), headers: headers_with_token }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'workhorse authorization'
+ end
+
+ describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:file_name' do
+ let(:file_name) { 'conanfile.py' }
+ let(:params) { { file: temp_file(file_name) } }
+
+ subject do
+ workhorse_finalize(
+ "/api/v4/packages/conan/v1/files/#{recipe_path}/0/export/#{file_name}",
+ method: :put,
+ file_key: :file,
+ params: params,
+ headers: headers_with_token
+ )
+ end
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'uploads a package file'
+ end
+
+ describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:conan_package_reference/:package_revision/:file_name' do
+ let(:file_name) { 'conaninfo.txt' }
+ let(:params) { { file: temp_file(file_name) } }
+
+ subject do
+ workhorse_finalize(
+ "/api/v4/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/#{file_name}",
+ method: :put,
+ file_key: :file,
+ params: params,
+ headers: headers_with_token
+ )
+ end
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'uploads a package file'
+ context 'tracking the conan_package.tgz upload' do
+ let(:file_name) { ::Packages::Conan::FileMetadatum::PACKAGE_BINARY }
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'push_package'
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/container_registry_event_spec.rb b/spec/requests/api/container_registry_event_spec.rb
index 2cdf2656cb7..4d38ddddffd 100644
--- a/spec/requests/api/container_registry_event_spec.rb
+++ b/spec/requests/api/container_registry_event_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ContainerRegistryEvent do
+RSpec.describe API::ContainerRegistryEvent do
let(:secret_token) { 'secret_token' }
let(:events) { [{ action: 'push' }] }
let(:registry_headers) { { 'Content-Type' => ::API::ContainerRegistryEvent::DOCKER_DISTRIBUTION_EVENTS_V1_JSON } }
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
index e8cc6bc71ae..81cef653770 100644
--- a/spec/requests/api/deploy_keys_spec.rb
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::DeployKeys do
+RSpec.describe API::DeployKeys do
let(:user) { create(:user) }
let(:maintainer) { create(:user) }
let(:admin) { create(:admin) }
diff --git a/spec/requests/api/deploy_tokens_spec.rb b/spec/requests/api/deploy_tokens_spec.rb
index 2b86d59fbba..8ec4f888e2e 100644
--- a/spec/requests/api/deploy_tokens_spec.rb
+++ b/spec/requests/api/deploy_tokens_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::DeployTokens do
+RSpec.describe API::DeployTokens do
let_it_be(:user) { create(:user) }
let_it_be(:creator) { create(:user) }
let_it_be(:project) { create(:project, creator_id: creator.id) }
diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb
index ef2415a0cde..8113de96ac4 100644
--- a/spec/requests/api/deployments_spec.rb
+++ b/spec/requests/api/deployments_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Deployments do
+RSpec.describe API::Deployments do
let(:user) { create(:user) }
let(:non_member) { create(:user) }
diff --git a/spec/requests/api/discussions_spec.rb b/spec/requests/api/discussions_spec.rb
index cb3efb2cf5f..720ea429c2c 100644
--- a/spec/requests/api/discussions_spec.rb
+++ b/spec/requests/api/discussions_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Discussions do
+RSpec.describe API::Discussions do
let(:user) { create(:user) }
let!(:project) { create(:project, :public, :repository, namespace: user.namespace) }
let(:private_user) { create(:user) }
diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb
index a25a6485f47..f16cd58bb34 100644
--- a/spec/requests/api/doorkeeper_access_spec.rb
+++ b/spec/requests/api/doorkeeper_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'doorkeeper access' do
+RSpec.describe 'doorkeeper access' do
let!(:user) { create(:user) }
let!(:application) { Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) }
let!(:token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id, scopes: "api" }
diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb
index 91b3dd93433..b1ac8f9eeec 100644
--- a/spec/requests/api/environments_spec.rb
+++ b/spec/requests/api/environments_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Environments do
+RSpec.describe API::Environments do
let(:user) { create(:user) }
let(:non_member) { create(:user) }
let(:project) { create(:project, :private, :repository, namespace: user.namespace) }
diff --git a/spec/requests/api/error_tracking_spec.rb b/spec/requests/api/error_tracking_spec.rb
index deed9777025..8c9ca1b6a9d 100644
--- a/spec/requests/api/error_tracking_spec.rb
+++ b/spec/requests/api/error_tracking_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ErrorTracking do
+RSpec.describe API::ErrorTracking do
let_it_be(:user) { create(:user) }
let(:setting) { create(:project_error_tracking_setting) }
let(:project) { setting.project }
diff --git a/spec/requests/api/events_spec.rb b/spec/requests/api/events_spec.rb
index 58a55c2e6d0..6a8d5f91abd 100644
--- a/spec/requests/api/events_spec.rb
+++ b/spec/requests/api/events_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Events do
+RSpec.describe API::Events do
let(:user) { create(:user) }
let(:non_member) { create(:user) }
let(:private_project) { create(:project, :private, creator_id: user.id, namespace: user.namespace) }
diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb
index 59a9ed2f77d..2746e777306 100644
--- a/spec/requests/api/features_spec.rb
+++ b/spec/requests/api/features_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Features, stub_feature_flags: false do
+RSpec.describe API::Features, stub_feature_flags: false do
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index 198e4f64bcc..b50f63ed67c 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -2,7 +2,9 @@
require 'spec_helper'
-describe API::Files do
+RSpec.describe API::Files do
+ include RepoHelpers
+
let(:user) { create(:user) }
let!(:project) { create(:project, :repository, namespace: user.namespace ) }
let(:guest) { create(:user) { |u| project.add_guest(u) } }
@@ -183,6 +185,26 @@ describe API::Files do
expect(response.content_type).to eq('application/json')
end
+ context 'with filename with pathspec characters' do
+ let(:file_path) { ':wq' }
+ let(:newrev) { project.repository.commit('master').sha }
+
+ before do
+ create_file_in_repo(project, 'master', 'master', file_path, 'Test file')
+ end
+
+ it 'returns JSON wth commit SHA' do
+ params[:ref] = 'master'
+
+ get api(route(file_path), api_user), params: params
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['file_path']).to eq(file_path)
+ expect(json_response['file_name']).to eq(file_path)
+ expect(json_response['last_commit_id']).to eq(newrev)
+ end
+ end
+
it 'returns file by commit sha' do
# This file is deleted on HEAD
file_path = "files%2Fjs%2Fcommit%2Ejs%2Ecoffee"
diff --git a/spec/requests/api/freeze_periods_spec.rb b/spec/requests/api/freeze_periods_spec.rb
index 0b7828ebedf..5589d4d543d 100644
--- a/spec/requests/api/freeze_periods_spec.rb
+++ b/spec/requests/api/freeze_periods_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::FreezePeriods do
+RSpec.describe API::FreezePeriods do
let_it_be(:project) { create(:project, :repository, :private) }
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
diff --git a/spec/requests/api/go_proxy_spec.rb b/spec/requests/api/go_proxy_spec.rb
new file mode 100644
index 00000000000..91e455dac19
--- /dev/null
+++ b/spec/requests/api/go_proxy_spec.rb
@@ -0,0 +1,465 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::GoProxy do
+ include PackagesManagerApiSpecHelpers
+
+ let_it_be(:user) { create :user }
+ let_it_be(:project) { create :project_empty_repo, creator: user, path: 'my-go-lib' }
+ let_it_be(:base) { "#{Settings.build_gitlab_go_url}/#{project.full_path}" }
+
+ let_it_be(:oauth) { create :oauth_access_token, scopes: 'api', resource_owner: user }
+ let_it_be(:job) { create :ci_build, user: user }
+ let_it_be(:pa_token) { create :personal_access_token, user: user }
+
+ let_it_be(:modules) do
+ commits = [
+ create(:go_module_commit, :files, project: project, tag: 'v1.0.0', files: { 'README.md' => 'Hi' } ),
+ create(:go_module_commit, :module, project: project, tag: 'v1.0.1' ),
+ create(:go_module_commit, :package, project: project, tag: 'v1.0.2', path: 'pkg' ),
+ create(:go_module_commit, :module, project: project, tag: 'v1.0.3', name: 'mod' ),
+ create(:go_module_commit, :files, project: project, files: { 'y.go' => "package a\n" } ),
+ create(:go_module_commit, :module, project: project, name: 'v2' ),
+ create(:go_module_commit, :files, project: project, tag: 'v2.0.0', files: { 'v2/x.go' => "package a\n" })
+ ]
+
+ { sha: [commits[4].sha, commits[5].sha] }
+ end
+
+ before do
+ project.add_developer(user)
+
+ stub_feature_flags(go_proxy_disable_gomod_validation: false)
+
+ modules
+ end
+
+ shared_examples 'an unavailable resource' do
+ it 'returns not found' do
+ get_resource(user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ shared_examples 'a module version list resource' do |*versions, path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:resource) { "list" }
+
+ it "returns #{versions.empty? ? 'nothing' : versions.join(', ')}" do
+ get_resource(user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body.split("\n").to_set).to eq(versions.to_set)
+ end
+ end
+
+ shared_examples 'a missing module version list resource' do |path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:resource) { "list" }
+
+ it_behaves_like 'an unavailable resource'
+ end
+
+ shared_examples 'a module version information resource' do |version, path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:resource) { "#{version}.info" }
+
+ it "returns information for #{version}" do
+ get_resource(user)
+
+ time = project.repository.find_tag(version).dereferenced_target.committed_date
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_kind_of(Hash)
+ expect(json_response['Version']).to eq(version)
+ expect(json_response['Time']).to eq(time.strftime('%Y-%m-%dT%H:%M:%S.%L%:z'))
+ end
+ end
+
+ shared_examples 'a missing module version information resource' do |version, path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:resource) { "#{version}.info" }
+
+ it_behaves_like 'an unavailable resource'
+ end
+
+ shared_examples 'a module pseudo-version information resource' do |prefix, path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:commit) { project.repository.commit_by(oid: sha) }
+ let(:version) { fmt_pseudo_version prefix, commit }
+ let(:resource) { "#{version}.info" }
+
+ it "returns information for #{prefix}yyyymmddhhmmss-abcdefabcdef" do
+ get_resource(user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_kind_of(Hash)
+ expect(json_response['Version']).to eq(version)
+ expect(json_response['Time']).to eq(commit.committed_date.strftime('%Y-%m-%dT%H:%M:%S.%L%:z'))
+ end
+ end
+
+ shared_examples 'a missing module pseudo-version information resource' do |path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:commit) do
+ raise "tried to reference :commit without defining :sha" unless defined?(sha)
+
+ project.repository.commit_by(oid: sha)
+ end
+ let(:resource) { "#{version}.info" }
+
+ it_behaves_like 'an unavailable resource'
+ end
+
+ shared_examples 'a module file resource' do |version, path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:resource) { "#{version}.mod" }
+
+ it "returns #{path}/go.mod from the repo" do
+ get_resource(user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body.split("\n", 2).first).to eq("module #{module_name}")
+ end
+ end
+
+ shared_examples 'a missing module file resource' do |version, path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:resource) { "#{version}.mod" }
+
+ it_behaves_like 'an unavailable resource'
+ end
+
+ shared_examples 'a module archive resource' do |version, entries, path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:resource) { "#{version}.zip" }
+
+ it "returns an archive of #{path.empty? ? '/' : path} @ #{version} from the repo" do
+ get_resource(user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+
+ entries = entries.map { |e| "#{module_name}@#{version}/#{e}" }.to_set
+ actual = Set[]
+ Zip::InputStream.open(StringIO.new(response.body)) do |zip|
+ while (entry = zip.get_next_entry)
+ actual.add(entry.name)
+ end
+ end
+
+ expect(actual).to eq(entries)
+ end
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/list' do
+ context 'for the root module' do
+ it_behaves_like 'a module version list resource', 'v1.0.1', 'v1.0.2', 'v1.0.3'
+ end
+
+ context 'for the package' do
+ it_behaves_like 'a module version list resource', path: '/pkg'
+ end
+
+ context 'for the submodule' do
+ it_behaves_like 'a module version list resource', 'v1.0.3', path: '/mod'
+ end
+
+ context 'for the root module v2' do
+ it_behaves_like 'a module version list resource', 'v2.0.0', path: '/v2'
+ end
+
+ context 'with a URL encoded relative path component' do
+ it_behaves_like 'a missing module version list resource', path: '/%2E%2E%2Fxyz'
+ end
+
+ context 'with the feature disabled' do
+ before do
+ stub_feature_flags(go_proxy: false)
+ end
+
+ it_behaves_like 'a missing module version list resource'
+ end
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/:module_version.info' do
+ context 'with the root module v1.0.1' do
+ it_behaves_like 'a module version information resource', 'v1.0.1'
+ end
+
+ context 'with the submodule v1.0.3' do
+ it_behaves_like 'a module version information resource', 'v1.0.3', path: '/mod'
+ end
+
+ context 'with the root module v2.0.0' do
+ it_behaves_like 'a module version information resource', 'v2.0.0', path: '/v2'
+ end
+
+ context 'with an invalid path' do
+ it_behaves_like 'a missing module version information resource', 'v1.0.3', path: '/pkg'
+ end
+
+ context 'with an invalid version' do
+ it_behaves_like 'a missing module version information resource', 'v1.0.1', path: '/mod'
+ end
+
+ context 'with a pseudo-version for v1' do
+ it_behaves_like 'a module pseudo-version information resource', 'v1.0.4-0.' do
+ let(:sha) { modules[:sha][0] }
+ end
+ end
+
+ context 'with a pseudo-version for v2' do
+ it_behaves_like 'a module pseudo-version information resource', 'v2.0.0-', path: '/v2' do
+ let(:sha) { modules[:sha][1] }
+ end
+ end
+
+ context 'with a pseudo-version with an invalid timestamp' do
+ it_behaves_like 'a missing module pseudo-version information resource' do
+ let(:version) { "v1.0.4-0.00000000000000-#{modules[:sha][0][0..11]}" }
+ end
+ end
+
+ context 'with a pseudo-version with an invalid commit sha' do
+ it_behaves_like 'a missing module pseudo-version information resource' do
+ let(:sha) { modules[:sha][0] }
+ let(:version) { "v1.0.4-0.#{commit.committed_date.strftime('%Y%m%d%H%M%S')}-000000000000" }
+ end
+ end
+
+ context 'with a pseudo-version with a short commit sha' do
+ it_behaves_like 'a missing module pseudo-version information resource' do
+ let(:sha) { modules[:sha][0] }
+ let(:version) { "v1.0.4-0.#{commit.committed_date.strftime('%Y%m%d%H%M%S')}-#{modules[:sha][0][0..10]}" }
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/:module_version.mod' do
+ context 'with the root module v1.0.1' do
+ it_behaves_like 'a module file resource', 'v1.0.1'
+ end
+
+ context 'with the submodule v1.0.3' do
+ it_behaves_like 'a module file resource', 'v1.0.3', path: '/mod'
+ end
+
+ context 'with the root module v2.0.0' do
+ it_behaves_like 'a module file resource', 'v2.0.0', path: '/v2'
+ end
+
+ context 'with an invalid path' do
+ it_behaves_like 'a missing module file resource', 'v1.0.3', path: '/pkg'
+ end
+
+ context 'with an invalid version' do
+ it_behaves_like 'a missing module file resource', 'v1.0.1', path: '/mod'
+ end
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/:module_version.zip' do
+ context 'with the root module v1.0.1' do
+ it_behaves_like 'a module archive resource', 'v1.0.1', ['README.md', 'go.mod', 'a.go']
+ end
+
+ context 'with the root module v1.0.2' do
+ it_behaves_like 'a module archive resource', 'v1.0.2', ['README.md', 'go.mod', 'a.go', 'pkg/b.go']
+ end
+
+ context 'with the root module v1.0.3' do
+ it_behaves_like 'a module archive resource', 'v1.0.3', ['README.md', 'go.mod', 'a.go', 'pkg/b.go']
+ end
+
+ context 'with the submodule v1.0.3' do
+ it_behaves_like 'a module archive resource', 'v1.0.3', ['go.mod', 'a.go'], path: '/mod'
+ end
+
+ context 'with the root module v2.0.0' do
+ it_behaves_like 'a module archive resource', 'v2.0.0', ['go.mod', 'a.go', 'x.go'], path: '/v2'
+ end
+ end
+
+ context 'with an invalid module directive' do
+ let_it_be(:project) { create :project_empty_repo, :public, creator: user }
+ let_it_be(:base) { "#{Settings.build_gitlab_go_url}/#{project.full_path}" }
+
+ let_it_be(:modules) do
+ create(:go_module_commit, :files, project: project, files: { 'a.go' => "package\a" } )
+ create(:go_module_commit, :files, project: project, tag: 'v1.0.0', files: { 'go.mod' => "module not/a/real/module\n" })
+ create(:go_module_commit, :files, project: project, files: { 'v2/a.go' => "package a\n" } )
+ create(:go_module_commit, :files, project: project, tag: 'v2.0.0', files: { 'v2/go.mod' => "module #{base}\n" } )
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/list' do
+ context 'with a completely wrong directive for v1' do
+ it_behaves_like 'a module version list resource'
+ end
+
+ context 'with a directive omitting the suffix for v2' do
+ it_behaves_like 'a module version list resource', path: '/v2'
+ end
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/:module_version.info' do
+ context 'with a completely wrong directive for v1' do
+ it_behaves_like 'a missing module version information resource', 'v1.0.0'
+ end
+
+ context 'with a directive omitting the suffix for v2' do
+ it_behaves_like 'a missing module version information resource', 'v2.0.0', path: '/v2'
+ end
+ end
+ end
+
+ context 'with a case sensitive project and versions' do
+ let_it_be(:project) { create :project_empty_repo, :public, creator: user, path: 'MyGoLib' }
+ let_it_be(:base) { "#{Settings.build_gitlab_go_url}/#{project.full_path}" }
+ let_it_be(:base_encoded) { base.gsub(/[A-Z]/) { |s| "!#{s.downcase}"} }
+
+ let_it_be(:modules) do
+ create(:go_module_commit, :files, project: project, files: { 'README.md' => "Hi" })
+ create(:go_module_commit, :module, project: project, tag: 'v1.0.1-prerelease')
+ create(:go_module_commit, :package, project: project, tag: 'v1.0.1-Prerelease', path: 'pkg')
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/list' do
+ let(:resource) { "list" }
+
+ context 'with a case encoded path' do
+ it_behaves_like 'a module version list resource', 'v1.0.1-prerelease', 'v1.0.1-Prerelease' do
+ let(:module_name) { base_encoded }
+ end
+ end
+
+ context 'without a case encoded path' do
+ it_behaves_like 'a missing module version list resource' do
+ let(:module_name) { base.downcase }
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/:module_version.info' do
+ context 'with a case encoded path' do
+ it_behaves_like 'a module version information resource', 'v1.0.1-Prerelease' do
+ let(:module_name) { base_encoded }
+ let(:resource) { "v1.0.1-!prerelease.info" }
+ end
+ end
+
+ context 'without a case encoded path' do
+ it_behaves_like 'a module version information resource', 'v1.0.1-prerelease' do
+ let(:module_name) { base_encoded }
+ let(:resource) { "v1.0.1-prerelease.info" }
+ end
+ end
+ end
+ end
+
+ context 'with a private project' do
+ let(:module_name) { base }
+
+ before do
+ project.update(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/list' do
+ let(:resource) { "list" }
+
+ it 'returns ok with an oauth token' do
+ get_resource(oauth_access_token: oauth)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns ok with a job token' do
+ get_resource(oauth_access_token: job)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns ok with a personal access token' do
+ get_resource(personal_access_token: pa_token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns ok with a personal access token and basic authentication' do
+ get_resource(headers: build_basic_auth_header(user.username, pa_token.token))
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns unauthorized with no authentication' do
+ get_resource
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ context 'with a public project' do
+ let(:module_name) { base }
+
+ before do
+ project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/list' do
+ let(:resource) { "list" }
+
+ it 'returns ok with no authentication' do
+ get_resource
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ context 'with a non-existent project' do
+ def get_resource(user = nil, **params)
+ get api("/projects/not%2fa%2fproject/packages/go/#{base}/@v/list", user, params)
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/list' do
+ it 'returns not found with a user' do
+ get_resource(user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns not found with an oauth token' do
+ get_resource(oauth_access_token: oauth)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns not found with a job token' do
+ get_resource(oauth_access_token: job)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns not found with a personal access token' do
+ get_resource(personal_access_token: pa_token)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns unauthorized with no authentication' do
+ get_resource
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ def get_resource(user = nil, headers: {}, **params)
+ get api("/projects/#{project.id}/packages/go/#{module_name}/@v/#{resource}", user, params), headers: headers
+ end
+
+ def fmt_pseudo_version(prefix, commit)
+ "#{prefix}#{commit.committed_date.strftime('%Y%m%d%H%M%S')}-#{commit.sha[0..11]}"
+ end
+end
diff --git a/spec/requests/api/graphql/boards/board_lists_query_spec.rb b/spec/requests/api/graphql/boards/board_lists_query_spec.rb
index 3cc1468be02..8a89590c85a 100644
--- a/spec/requests/api/graphql/boards/board_lists_query_spec.rb
+++ b/spec/requests/api/graphql/boards/board_lists_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'get board lists' do
+RSpec.describe 'get board lists' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
diff --git a/spec/requests/api/graphql/boards/boards_query_spec.rb b/spec/requests/api/graphql/boards/boards_query_spec.rb
index a17554aba21..50004e5a8a1 100644
--- a/spec/requests/api/graphql/boards/boards_query_spec.rb
+++ b/spec/requests/api/graphql/boards/boards_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'get list of boards' do
+RSpec.describe 'get list of boards' do
include GraphqlHelpers
include_context 'group and project boards query context'
diff --git a/spec/requests/api/graphql/current_user/todos_query_spec.rb b/spec/requests/api/graphql/current_user/todos_query_spec.rb
index 321e1062a96..e298de0df01 100644
--- a/spec/requests/api/graphql/current_user/todos_query_spec.rb
+++ b/spec/requests/api/graphql/current_user/todos_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Query current user todos' do
+RSpec.describe 'Query current user todos' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/current_user_query_spec.rb b/spec/requests/api/graphql/current_user_query_spec.rb
index 2b38b8e98ab..dc832b42fa5 100644
--- a/spec/requests/api/graphql/current_user_query_spec.rb
+++ b/spec/requests/api/graphql/current_user_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting project information' do
+RSpec.describe 'getting project information' do
include GraphqlHelpers
let(:query) do
diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb
index 266c98d6f08..ee7dba545be 100644
--- a/spec/requests/api/graphql/gitlab_schema_spec.rb
+++ b/spec/requests/api/graphql/gitlab_schema_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'GitlabSchema configurations' do
+RSpec.describe 'GitlabSchema configurations' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/group/labels_query_spec.rb b/spec/requests/api/graphql/group/labels_query_spec.rb
index 6c34cbadf95..31556ffca30 100644
--- a/spec/requests/api/graphql/group/labels_query_spec.rb
+++ b/spec/requests/api/graphql/group/labels_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting group label information' do
+RSpec.describe 'getting group label information' do
include GraphqlHelpers
let_it_be(:group) { create(:group, :public) }
diff --git a/spec/requests/api/graphql/group/milestones_spec.rb b/spec/requests/api/graphql/group/milestones_spec.rb
index bad0024e7a3..380eaea17f8 100644
--- a/spec/requests/api/graphql/group/milestones_spec.rb
+++ b/spec/requests/api/graphql/group/milestones_spec.rb
@@ -2,21 +2,22 @@
require 'spec_helper'
-describe 'Milestones through GroupQuery' do
+RSpec.describe 'Milestones through GroupQuery' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be(:now) { Time.now }
- let_it_be(:group) { create(:group) }
- let_it_be(:milestone_1) { create(:milestone, group: group) }
- let_it_be(:milestone_2) { create(:milestone, group: group, state: :closed, start_date: now, due_date: now + 1.day) }
- let_it_be(:milestone_3) { create(:milestone, group: group, start_date: now, due_date: now + 2.days) }
- let_it_be(:milestone_4) { create(:milestone, group: group, state: :closed, start_date: now - 2.days, due_date: now - 1.day) }
- let_it_be(:milestone_from_other_group) { create(:milestone, group: create(:group)) }
-
- let(:milestone_data) { graphql_data['group']['milestones']['edges'] }
describe 'Get list of milestones from a group' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:milestone_1) { create(:milestone, group: group) }
+ let_it_be(:milestone_2) { create(:milestone, group: group, state: :closed, start_date: now, due_date: now + 1.day) }
+ let_it_be(:milestone_3) { create(:milestone, group: group, start_date: now, due_date: now + 2.days) }
+ let_it_be(:milestone_4) { create(:milestone, group: group, state: :closed, start_date: now - 2.days, due_date: now - 1.day) }
+ let_it_be(:milestone_from_other_group) { create(:milestone, group: create(:group)) }
+
+ let(:milestone_data) { graphql_data['group']['milestones']['edges'] }
+
context 'when the request is correct' do
before do
fetch_milestones(user)
@@ -72,21 +73,6 @@ describe 'Milestones through GroupQuery' do
submilestone_1.to_global_id.to_s, submilestone_2.to_global_id.to_s
)
end
-
- context 'when group_milestone_descendants is disabled' do
- before do
- stub_feature_flags(group_milestone_descendants: false)
- end
-
- it 'ignores descendant milestones' do
- fetch_milestones(user, args)
-
- expect_array_response(
- milestone_1.to_global_id.to_s, milestone_2.to_global_id.to_s,
- milestone_3.to_global_id.to_s, milestone_4.to_global_id.to_s
- )
- end
- end
end
def fetch_milestones(user = nil, args = {})
@@ -120,4 +106,89 @@ describe 'Milestones through GroupQuery' do
node_array(milestone_data, extract_attribute)
end
end
+
+ describe 'ensures each field returns the correct value' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
+ let_it_be(:milestone) { create(:milestone, group: group, start_date: now, due_date: now + 1.day) }
+ let_it_be(:open_issue) { create(:issue, project: project, milestone: milestone) }
+ let_it_be(:closed_issue) { create(:issue, :closed, project: project, milestone: milestone) }
+
+ let(:milestone_query) do
+ %{
+ id
+ title
+ description
+ state
+ webPath
+ dueDate
+ startDate
+ createdAt
+ updatedAt
+ projectMilestone
+ groupMilestone
+ subgroupMilestone
+ }
+ end
+
+ def post_query
+ full_query = graphql_query_for("group",
+ { full_path: group.full_path },
+ [query_graphql_field("milestones", nil, "nodes { #{milestone_query} }")]
+ )
+
+ post_graphql(full_query, current_user: user)
+
+ graphql_data.dig('group', 'milestones', 'nodes', 0)
+ end
+
+ it 'returns correct values for scalar fields' do
+ expect(post_query).to eq({
+ 'id' => global_id_of(milestone),
+ 'title' => milestone.title,
+ 'description' => milestone.description,
+ 'state' => 'active',
+ 'webPath' => milestone_path(milestone),
+ 'dueDate' => milestone.due_date.iso8601,
+ 'startDate' => milestone.start_date.iso8601,
+ 'createdAt' => milestone.created_at.iso8601,
+ 'updatedAt' => milestone.updated_at.iso8601,
+ 'projectMilestone' => false,
+ 'groupMilestone' => true,
+ 'subgroupMilestone' => false
+ })
+ end
+
+ context 'milestone statistics' do
+ let(:milestone_query) do
+ %{
+ stats {
+ totalIssuesCount
+ closedIssuesCount
+ }
+ }
+ end
+
+ it 'returns the correct milestone statistics' do
+ expect(post_query).to eq({
+ 'stats' => {
+ 'totalIssuesCount' => 2,
+ 'closedIssuesCount' => 1
+ }
+ })
+ end
+
+ context 'when the graphql_milestone_stats feature flag is disabled' do
+ before do
+ stub_feature_flags(graphql_milestone_stats: false)
+ end
+
+ it 'returns nil for the stats field' do
+ expect(post_query).to eq({
+ 'stats' => nil
+ })
+ end
+ end
+ end
+ end
end
diff --git a/spec/requests/api/graphql/group_query_spec.rb b/spec/requests/api/graphql/group_query_spec.rb
index c7b537a9923..d99bff2e349 100644
--- a/spec/requests/api/graphql/group_query_spec.rb
+++ b/spec/requests/api/graphql/group_query_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
# Based on spec/requests/api/groups_spec.rb
# Should follow closely in order to ensure all situations are covered
-describe 'getting group information', :do_not_mock_admin_mode do
+RSpec.describe 'getting group information', :do_not_mock_admin_mode do
include GraphqlHelpers
include UploadHelpers
diff --git a/spec/requests/api/graphql/metadata_query_spec.rb b/spec/requests/api/graphql/metadata_query_spec.rb
index 4c56c559cf9..6344ec371c8 100644
--- a/spec/requests/api/graphql/metadata_query_spec.rb
+++ b/spec/requests/api/graphql/metadata_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting project information' do
+RSpec.describe 'getting project information' do
include GraphqlHelpers
let(:query) { graphql_query_for('metadata', {}, all_graphql_fields_for('Metadata')) }
diff --git a/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb b/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb
index cb35411b7a5..c47920087dc 100644
--- a/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb
+++ b/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Getting Metrics Dashboard Annotations' do
+RSpec.describe 'Getting Metrics Dashboard Annotations' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/metrics/dashboard_query_spec.rb b/spec/requests/api/graphql/metrics/dashboard_query_spec.rb
index d9d9ea9ad61..456b0a5dea1 100644
--- a/spec/requests/api/graphql/metrics/dashboard_query_spec.rb
+++ b/spec/requests/api/graphql/metrics/dashboard_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Getting Metrics Dashboard' do
+RSpec.describe 'Getting Metrics Dashboard' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
@@ -62,12 +62,12 @@ describe 'Getting Metrics Dashboard' do
context 'invalid dashboard' do
let(:path) { '.gitlab/dashboards/metrics.yml' }
- let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "---\ndasboard: ''" }) }
+ let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "---\ndashboard: 'test'" }) }
it 'returns metrics dashboard' do
dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
- expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["dashboard: can't be blank", "panel_groups: can't be blank"])
+ expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["panel_groups: should be an array of panel_groups objects"])
end
end
@@ -78,7 +78,7 @@ describe 'Getting Metrics Dashboard' do
it 'returns metrics dashboard' do
dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
- expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["dashboard: can't be blank", "panel_groups: can't be blank"])
+ expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["dashboard: can't be blank", "panel_groups: should be an array of panel_groups objects"])
end
end
end
diff --git a/spec/requests/api/graphql/multiplexed_queries_spec.rb b/spec/requests/api/graphql/multiplexed_queries_spec.rb
index 9ebb57f6b9c..f79bac6ae3b 100644
--- a/spec/requests/api/graphql/multiplexed_queries_spec.rb
+++ b/spec/requests/api/graphql/multiplexed_queries_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'Multiplexed queries' do
+RSpec.describe 'Multiplexed queries' do
include GraphqlHelpers
it 'returns responses for multiple queries' do
diff --git a/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb b/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb
index a5159da84f3..4ad35e7f0d1 100644
--- a/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb
+++ b/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Deleting Sidekiq jobs', :clean_gitlab_redis_queues do
+RSpec.describe 'Deleting Sidekiq jobs', :clean_gitlab_redis_queues do
include GraphqlHelpers
let_it_be(:admin) { create(:admin) }
diff --git a/spec/requests/api/graphql/mutations/alert_management/alerts/create_alert_issue_spec.rb b/spec/requests/api/graphql/mutations/alert_management/alerts/create_alert_issue_spec.rb
index 5b5b2ec8788..6141a172253 100644
--- a/spec/requests/api/graphql/mutations/alert_management/alerts/create_alert_issue_spec.rb
+++ b/spec/requests/api/graphql/mutations/alert_management/alerts/create_alert_issue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Create an alert issue from an alert' do
+RSpec.describe 'Create an alert issue from an alert' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/alert_management/alerts/set_assignees_spec.rb b/spec/requests/api/graphql/mutations/alert_management/alerts/set_assignees_spec.rb
index 6663281e093..cd5cefa0a9a 100644
--- a/spec/requests/api/graphql/mutations/alert_management/alerts/set_assignees_spec.rb
+++ b/spec/requests/api/graphql/mutations/alert_management/alerts/set_assignees_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting assignees of an alert' do
+RSpec.describe 'Setting assignees of an alert' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/mutations/alert_management/alerts/todo/create_spec.rb b/spec/requests/api/graphql/mutations/alert_management/alerts/todo/create_spec.rb
new file mode 100644
index 00000000000..e5803f50474
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/alert_management/alerts/todo/create_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Creating a todo for the alert' do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let(:alert) { create(:alert_management_alert, project: project) }
+
+ let(:mutation) do
+ variables = {
+ project_path: project.full_path,
+ iid: alert.iid.to_s
+ }
+ graphql_mutation(:alert_todo_create, variables) do
+ <<~QL
+ clientMutationId
+ errors
+ todo {
+ author {
+ username
+ }
+ }
+ QL
+ end
+ end
+
+ let(:mutation_response) { graphql_mutation_response(:alert_todo_create) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ it 'creates a todo for the current user' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['todo']['author']['username']).to eq(user.username)
+ end
+
+ context 'todo already exists' do
+ before do
+ create(:todo, :pending, project: project, user: user, target: alert)
+ end
+
+ it 'surfaces an error' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['errors']).to eq(['You already have pending todo for this alert'])
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/alert_management/alerts/update_alert_status_spec.rb b/spec/requests/api/graphql/mutations/alert_management/alerts/update_alert_status_spec.rb
index 2a470bda689..ff55656a2ae 100644
--- a/spec/requests/api/graphql/mutations/alert_management/alerts/update_alert_status_spec.rb
+++ b/spec/requests/api/graphql/mutations/alert_management/alerts/update_alert_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting the status of an alert' do
+RSpec.describe 'Setting the status of an alert' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
index 83dec7dd3e2..1891300dace 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Adding an AwardEmoji' do
+RSpec.describe 'Adding an AwardEmoji' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
@@ -15,11 +15,11 @@ describe 'Adding an AwardEmoji' do
name: emoji_name
}
- graphql_mutation(:add_award_emoji, variables)
+ graphql_mutation(:award_emoji_add, variables)
end
def mutation_response
- graphql_mutation_response(:add_award_emoji)
+ graphql_mutation_response(:award_emoji_add)
end
shared_examples 'a mutation that does not create an AwardEmoji' do
diff --git a/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb
index a2997db6cae..665b511abb8 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Removing an AwardEmoji' do
+RSpec.describe 'Removing an AwardEmoji' do
include GraphqlHelpers
let(:current_user) { create(:user) }
@@ -12,11 +12,11 @@ describe 'Removing an AwardEmoji' do
let(:input) { { awardable_id: GitlabSchema.id_from_object(awardable).to_s, name: emoji_name } }
let(:mutation) do
- graphql_mutation(:remove_award_emoji, input)
+ graphql_mutation(:award_emoji_remove, input)
end
def mutation_response
- graphql_mutation_response(:remove_award_emoji)
+ graphql_mutation_response(:award_emoji_remove)
end
def create_award_emoji(user)
diff --git a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
index e1180c85c6b..ab4a213fde3 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Toggling an AwardEmoji' do
+RSpec.describe 'Toggling an AwardEmoji' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
@@ -15,11 +15,11 @@ describe 'Toggling an AwardEmoji' do
name: emoji_name
}
- graphql_mutation(:toggle_award_emoji, variables)
+ graphql_mutation(:award_emoji_toggle, variables)
end
def mutation_response
- graphql_mutation_response(:toggle_award_emoji)
+ graphql_mutation_response(:award_emoji_toggle)
end
shared_examples 'a mutation that does not create or destroy an AwardEmoji' do
diff --git a/spec/requests/api/graphql/mutations/branches/create_spec.rb b/spec/requests/api/graphql/mutations/branches/create_spec.rb
index b3c378ec2bc..082b445bf3e 100644
--- a/spec/requests/api/graphql/mutations/branches/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/branches/create_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Creation of a new branch' do
+RSpec.describe 'Creation of a new branch' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/commits/create_spec.rb b/spec/requests/api/graphql/mutations/commits/create_spec.rb
index 10a69932948..9e4a96700bb 100644
--- a/spec/requests/api/graphql/mutations/commits/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/commits/create_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Creation of a new commit' do
+RSpec.describe 'Creation of a new commit' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/container_expiration_policy/update_spec.rb b/spec/requests/api/graphql/mutations/container_expiration_policy/update_spec.rb
index bc256a08f00..bc1b42d68e6 100644
--- a/spec/requests/api/graphql/mutations/container_expiration_policy/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/container_expiration_policy/update_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Updating the container expiration policy' do
+RSpec.describe 'Updating the container expiration policy' do
include GraphqlHelpers
using RSpec::Parameterized::TableSyntax
@@ -48,13 +48,48 @@ describe 'Updating the container expiration policy' do
end
end
- RSpec.shared_examples 'updating the container expiration policy' do
+ RSpec.shared_examples 'rejecting invalid regex for' do |field_name|
+ context "for field #{field_name}" do
+ let_it_be(:invalid_regex) { '*production' }
+ let(:params) do
+ {
+ :project_path => project.full_path,
+ field_name => invalid_regex
+ }
+ end
+
+ it_behaves_like 'returning response status', :success
+
+ it_behaves_like 'not creating the container expiration policy'
+
+ it 'returns an error' do
+ subject
+
+ expect(graphql_errors.size).to eq(1)
+ expect(graphql_errors.first['message']).to include("#{invalid_regex} is an invalid regexp")
+ end
+ end
+ end
+
+ RSpec.shared_examples 'accepting the mutation request updating the container expiration policy' do
it_behaves_like 'updating the container expiration policy attributes', mode: :update, from: { cadence: '1d', keep_n: 10, older_than: '90d' }, to: { cadence: '3month', keep_n: 100, older_than: '14d' }
it_behaves_like 'returning a success'
+
+ it_behaves_like 'rejecting invalid regex for', :name_regex
+ it_behaves_like 'rejecting invalid regex for', :name_regex_keep
+ end
+
+ RSpec.shared_examples 'accepting the mutation request creating the container expiration policy' do
+ it_behaves_like 'creating the container expiration policy'
+
+ it_behaves_like 'returning a success'
+
+ it_behaves_like 'rejecting invalid regex for', :name_regex
+ it_behaves_like 'rejecting invalid regex for', :name_regex_keep
end
- RSpec.shared_examples 'denying access to container expiration policy' do
+ RSpec.shared_examples 'denying the mutation request' do
it_behaves_like 'not creating the container expiration policy'
it_behaves_like 'returning response status', :success
@@ -71,11 +106,11 @@ describe 'Updating the container expiration policy' do
context 'with existing container expiration policy' do
where(:user_role, :shared_examples_name) do
- :maintainer | 'updating the container expiration policy'
- :developer | 'updating the container expiration policy'
- :reporter | 'denying access to container expiration policy'
- :guest | 'denying access to container expiration policy'
- :anonymous | 'denying access to container expiration policy'
+ :maintainer | 'accepting the mutation request updating the container expiration policy'
+ :developer | 'accepting the mutation request updating the container expiration policy'
+ :reporter | 'denying the mutation request'
+ :guest | 'denying the mutation request'
+ :anonymous | 'denying the mutation request'
end
with_them do
@@ -91,11 +126,11 @@ describe 'Updating the container expiration policy' do
let_it_be(:project, reload: true) { create(:project, :without_container_expiration_policy) }
where(:user_role, :shared_examples_name) do
- :maintainer | 'creating the container expiration policy'
- :developer | 'creating the container expiration policy'
- :reporter | 'denying access to container expiration policy'
- :guest | 'denying access to container expiration policy'
- :anonymous | 'denying access to container expiration policy'
+ :maintainer | 'accepting the mutation request creating the container expiration policy'
+ :developer | 'accepting the mutation request creating the container expiration policy'
+ :reporter | 'denying the mutation request'
+ :guest | 'denying the mutation request'
+ :anonymous | 'denying the mutation request'
end
with_them do
diff --git a/spec/requests/api/graphql/mutations/design_management/delete_spec.rb b/spec/requests/api/graphql/mutations/design_management/delete_spec.rb
index 10376305b3e..e329416faee 100644
--- a/spec/requests/api/graphql/mutations/design_management/delete_spec.rb
+++ b/spec/requests/api/graphql/mutations/design_management/delete_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe "deleting designs" do
+RSpec.describe "deleting designs" do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/requests/api/graphql/mutations/design_management/upload_spec.rb b/spec/requests/api/graphql/mutations/design_management/upload_spec.rb
index 22adc064406..9a9c7107b20 100644
--- a/spec/requests/api/graphql/mutations/design_management/upload_spec.rb
+++ b/spec/requests/api/graphql/mutations/design_management/upload_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "spec_helper"
-describe "uploading designs" do
+RSpec.describe "uploading designs" do
include GraphqlHelpers
include DesignManagementTestHelpers
include WorkhorseHelpers
diff --git a/spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb b/spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb
index 95e967c039d..e83da830935 100644
--- a/spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb
+++ b/spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Toggling the resolve status of a discussion' do
+RSpec.describe 'Toggling the resolve status of a discussion' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :public, :repository) }
diff --git a/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb b/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb
index 4d0bb59b030..3f804a46992 100644
--- a/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting an issue as confidential' do
+RSpec.describe 'Setting an issue as confidential' do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb b/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb
index 1efa9e16233..3dd1225db5a 100644
--- a/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting Due Date of an issue' do
+RSpec.describe 'Setting Due Date of an issue' do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb b/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb
new file mode 100644
index 00000000000..f1d55430e02
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Setting an issue as locked' do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:issue) { create(:issue) }
+ let_it_be(:project) { issue.project }
+ let(:input) { { locked: true } }
+
+ let(:mutation) do
+ variables = {
+ project_path: project.full_path,
+ iid: issue.iid.to_s
+ }
+ graphql_mutation(:issue_set_locked, variables.merge(input),
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ issue {
+ iid
+ discussionLocked
+ }
+ QL
+ )
+ end
+
+ def mutation_response
+ graphql_mutation_response(:issue_set_locked)
+ end
+
+ context 'when the user is not allowed to update the issue' do
+ it 'returns an error' do
+ error = "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(graphql_errors).to include(a_hash_including('message' => error))
+ end
+ end
+
+ context 'when user is allowed to update the issue' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ it 'updates the issue locked status' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['issue']['discussionLocked']).to be_truthy
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/jira_import/import_users_spec.rb b/spec/requests/api/graphql/mutations/jira_import/import_users_spec.rb
index be0d843d5ff..4057aa4ba9e 100644
--- a/spec/requests/api/graphql/mutations/jira_import/import_users_spec.rb
+++ b/spec/requests/api/graphql/mutations/jira_import/import_users_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Importing Jira Users' do
+RSpec.describe 'Importing Jira Users' do
include JiraServiceHelper
include GraphqlHelpers
diff --git a/spec/requests/api/graphql/mutations/jira_import/start_spec.rb b/spec/requests/api/graphql/mutations/jira_import/start_spec.rb
index 296d33aec5d..e7124512ef1 100644
--- a/spec/requests/api/graphql/mutations/jira_import/start_spec.rb
+++ b/spec/requests/api/graphql/mutations/jira_import/start_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Starting a Jira Import' do
+RSpec.describe 'Starting a Jira Import' do
include JiraServiceHelper
include GraphqlHelpers
@@ -14,7 +14,8 @@ describe 'Starting a Jira Import' do
let(:mutation) do
variables = {
jira_project_key: jira_project_key,
- project_path: project_path
+ project_path: project_path,
+ users_mapping: [{ jiraAccountId: 'abc', gitlabId: 5 }]
}
graphql_mutation(:jira_import_start, variables)
diff --git a/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb
index 5c63f655f1d..d4ac639e226 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Creation of a new merge request' do
+RSpec.describe 'Creation of a new merge request' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
index 8f908b7bf88..97873b01338 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting assignees of a merge request' do
+RSpec.describe 'Setting assignees of a merge request' do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb
index 2112ff0dc74..34d347c76fd 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting labels of a merge request' do
+RSpec.describe 'Setting labels of a merge request' do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb
index c45da613591..a1a35bc1dcc 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting locked status of a merge request' do
+RSpec.describe 'Setting locked status of a merge request' do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb
index bd558edf9c5..d7e2602bd0a 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting milestone of a merge request' do
+RSpec.describe 'Setting milestone of a merge request' do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb
index 975735bf246..6b3035fbf48 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting subscribed status of a merge request' do
+RSpec.describe 'Setting subscribed status of a merge request' do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb
index 4492c51dbd7..2143abd3031 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting WIP status of a merge request' do
+RSpec.describe 'Setting Draft status of a merge request' do
include GraphqlHelpers
let(:current_user) { create(:user) }
@@ -41,39 +41,39 @@ describe 'Setting WIP status of a merge request' do
expect(graphql_errors).not_to be_empty
end
- it 'marks the merge request as WIP' do
+ it 'marks the merge request as Draft' do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
- expect(mutation_response['mergeRequest']['title']).to start_with('WIP:')
+ expect(mutation_response['mergeRequest']['title']).to start_with('Draft:')
end
- it 'does not do anything if the merge request was already marked `WIP`' do
- merge_request.update!(title: 'wip: hello world')
+ it 'does not do anything if the merge request was already marked `Draft`' do
+ merge_request.update!(title: 'draft: hello world')
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
- expect(mutation_response['mergeRequest']['title']).to start_with('wip:')
+ expect(mutation_response['mergeRequest']['title']).to start_with('draft:')
end
- context 'when passing WIP false as input' do
+ context 'when passing Draft false as input' do
let(:input) { { wip: false } }
- it 'does not do anything if the merge reqeust was not marked wip' do
+ it 'does not do anything if the merge reqeust was not marked draft' do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
- expect(mutation_response['mergeRequest']['title']).not_to start_with(/wip\:/)
+ expect(mutation_response['mergeRequest']['title']).not_to start_with(/draft\:/)
end
- it 'unmarks the merge request as `WIP`' do
- merge_request.update!(title: 'wip: hello world')
+ it 'unmarks the merge request as `Draft`' do
+ merge_request.update!(title: 'draft: hello world')
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
- expect(mutation_response['mergeRequest']['title']).not_to start_with('/wip\:/')
+ expect(mutation_response['mergeRequest']['title']).not_to start_with('/draft\:/')
end
end
end
diff --git a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb
index 8568dc8ffc0..0e2da94f0f9 100644
--- a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::Metrics::Dashboard::Annotations::Create do
+RSpec.describe Mutations::Metrics::Dashboard::Annotations::Create do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb
index 217f538c53e..2459a6f3828 100644
--- a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb
+++ b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::Metrics::Dashboard::Annotations::Delete do
+RSpec.describe Mutations::Metrics::Dashboard::Annotations::Delete do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb
index 4c535434faa..e847c46be1b 100644
--- a/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Adding a DiffNote' do
+RSpec.describe 'Adding a DiffNote' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb
index 0bba3e79434..896a398e308 100644
--- a/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Adding an image DiffNote' do
+RSpec.describe 'Adding an image DiffNote' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
index 9a78d44245e..391ced7dc98 100644
--- a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Adding a Note' do
+RSpec.describe 'Adding a Note' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
@@ -13,7 +13,8 @@ describe 'Adding a Note' do
variables = {
noteable_id: GitlabSchema.id_from_object(noteable).to_s,
discussion_id: (GitlabSchema.id_from_object(discussion).to_s if discussion),
- body: 'Body text'
+ body: 'Body text',
+ confidential: true
}
graphql_mutation(:create_note, variables)
@@ -40,6 +41,7 @@ describe 'Adding a Note' do
post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['note']['body']).to eq('Body text')
+ expect(mutation_response['note']['confidential']).to eq(true)
end
describe 'creating Notes in reply to a discussion' do
diff --git a/spec/requests/api/graphql/mutations/notes/destroy_spec.rb b/spec/requests/api/graphql/mutations/notes/destroy_spec.rb
index 337a6e6f6e6..6002a5b5b9d 100644
--- a/spec/requests/api/graphql/mutations/notes/destroy_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/destroy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Destroying a Note' do
+RSpec.describe 'Destroying a Note' do
include GraphqlHelpers
let!(:note) { create(:note) }
diff --git a/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb
index 0362fef2d2e..f7be671e5f3 100644
--- a/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Updating an image DiffNote' do
+RSpec.describe 'Updating an image DiffNote' do
include GraphqlHelpers
using RSpec::Parameterized::TableSyntax
diff --git a/spec/requests/api/graphql/mutations/notes/update/note_spec.rb b/spec/requests/api/graphql/mutations/notes/update/note_spec.rb
index a5c6b72005e..38378310d9f 100644
--- a/spec/requests/api/graphql/mutations/notes/update/note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/update/note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Updating a Note' do
+RSpec.describe 'Updating a Note' do
include GraphqlHelpers
let!(:note) { create(:note, note: original_body) }
diff --git a/spec/requests/api/graphql/mutations/snippets/create_spec.rb b/spec/requests/api/graphql/mutations/snippets/create_spec.rb
index 9052f54b171..e2474e1bcce 100644
--- a/spec/requests/api/graphql/mutations/snippets/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/create_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Creating a Snippet' do
+RSpec.describe 'Creating a Snippet' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
@@ -14,9 +14,8 @@ describe 'Creating a Snippet' do
let(:visibility_level) { 'public' }
let(:project_path) { nil }
let(:uploaded_files) { nil }
-
- let(:mutation) do
- variables = {
+ let(:mutation_vars) do
+ {
content: content,
description: description,
visibility_level: visibility_level,
@@ -25,8 +24,10 @@ describe 'Creating a Snippet' do
project_path: project_path,
uploaded_files: uploaded_files
}
+ end
- graphql_mutation(:create_snippet, variables)
+ let(:mutation) do
+ graphql_mutation(:create_snippet, mutation_vars)
end
def mutation_response
@@ -137,6 +138,47 @@ describe 'Creating a Snippet' do
end
end
+ context 'when snippet is created using the files param' do
+ let(:action) { :create }
+ let(:file_1) { { filePath: 'example_file1', content: 'This is the example file 1' }}
+ let(:file_2) { { filePath: 'example_file2', content: 'This is the example file 2' }}
+ let(:actions) { [{ action: action }.merge(file_1), { action: action }.merge(file_2)] }
+ let(:mutation_vars) do
+ {
+ description: description,
+ visibility_level: visibility_level,
+ project_path: project_path,
+ title: title,
+ files: actions
+ }
+ end
+
+ it 'creates the Snippet' do
+ expect do
+ subject
+ end.to change { Snippet.count }.by(1)
+ end
+
+ it 'returns the created Snippet' do
+ subject
+
+ expect(mutation_response['snippet']['title']).to eq(title)
+ expect(mutation_response['snippet']['description']).to eq(description)
+ expect(mutation_response['snippet']['visibilityLevel']).to eq(visibility_level)
+ expect(mutation_response['snippet']['blobs'][0]['plainData']).to match(file_1[:content])
+ expect(mutation_response['snippet']['blobs'][0]['fileName']).to match(file_1[:file_path])
+ expect(mutation_response['snippet']['blobs'][1]['plainData']).to match(file_2[:content])
+ expect(mutation_response['snippet']['blobs'][1]['fileName']).to match(file_2[:file_path])
+ end
+
+ context 'when action is invalid' do
+ let(:file_1) { { filePath: 'example_file1' }}
+
+ it_behaves_like 'a mutation that returns errors in the response', errors: ['Snippet actions have invalid data']
+ it_behaves_like 'does not create snippet'
+ end
+ end
+
context 'when there are ActiveRecord validation errors' do
let(:title) { '' }
diff --git a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
index cb9aeea74b2..8ade72635af 100644
--- a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Destroying a Snippet' do
+RSpec.describe 'Destroying a Snippet' do
include GraphqlHelpers
let(:current_user) { snippet.author }
diff --git a/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb b/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb
index 6d4dce3f6f1..97e6ae8fda8 100644
--- a/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Mark snippet as spam', :do_not_mock_admin_mode do
+RSpec.describe 'Mark snippet as spam', :do_not_mock_admin_mode do
include GraphqlHelpers
let_it_be(:admin) { create(:admin) }
diff --git a/spec/requests/api/graphql/mutations/snippets/update_spec.rb b/spec/requests/api/graphql/mutations/snippets/update_spec.rb
index 968ea5aed52..3b2f9dc0f19 100644
--- a/spec/requests/api/graphql/mutations/snippets/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/update_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Updating a Snippet' do
+RSpec.describe 'Updating a Snippet' do
include GraphqlHelpers
let_it_be(:original_content) { 'Initial content' }
@@ -16,8 +16,8 @@ describe 'Updating a Snippet' do
let(:current_user) { snippet.author }
let(:snippet_gid) { GitlabSchema.id_from_object(snippet).to_s }
- let(:mutation) do
- variables = {
+ let(:mutation_vars) do
+ {
id: snippet_gid,
content: updated_content,
description: updated_description,
@@ -25,8 +25,9 @@ describe 'Updating a Snippet' do
file_name: updated_file_name,
title: updated_title
}
-
- graphql_mutation(:update_snippet, variables)
+ end
+ let(:mutation) do
+ graphql_mutation(:update_snippet, mutation_vars)
end
def mutation_response
@@ -101,7 +102,6 @@ describe 'Updating a Snippet' do
end
it_behaves_like 'graphql update actions'
-
it_behaves_like 'when the snippet is not found'
end
@@ -148,4 +148,40 @@ describe 'Updating a Snippet' do
it_behaves_like 'when the snippet is not found'
end
+
+ context 'when using the files params' do
+ let!(:snippet) { create(:personal_snippet, :private, :repository) }
+ let(:updated_content) { 'updated_content' }
+ let(:updated_file) { 'CHANGELOG' }
+ let(:deleted_file) { 'README' }
+ let(:mutation_vars) do
+ {
+ id: snippet_gid,
+ files: [
+ { action: :update, filePath: updated_file, content: updated_content },
+ { action: :delete, filePath: deleted_file }
+ ]
+ }
+ end
+
+ it 'updates the Snippet' do
+ blob_to_update = blob_at(updated_file)
+ expect(blob_to_update.data).not_to eq updated_content
+
+ blob_to_delete = blob_at(deleted_file)
+ expect(blob_to_delete).to be_present
+
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ blob_to_update = blob_at(updated_file)
+ expect(blob_to_update.data).to eq updated_content
+
+ blob_to_delete = blob_at(deleted_file)
+ expect(blob_to_delete).to be_nil
+ end
+
+ def blob_at(filename)
+ snippet.repository.blob_at('HEAD', filename)
+ end
+ end
end
diff --git a/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb b/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb
index 40e085027d7..ed5552f3e30 100644
--- a/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb
+++ b/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Marking all todos done' do
+RSpec.describe 'Marking all todos done' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb b/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb
index fabbb3aeb49..9c4733f6769 100644
--- a/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb
+++ b/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Marking todos done' do
+RSpec.describe 'Marking todos done' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/todos/restore_spec.rb b/spec/requests/api/graphql/mutations/todos/restore_spec.rb
index faa36c8273a..6dedde56e13 100644
--- a/spec/requests/api/graphql/mutations/todos/restore_spec.rb
+++ b/spec/requests/api/graphql/mutations/todos/restore_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Restoring Todos' do
+RSpec.describe 'Restoring Todos' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/namespace/projects_spec.rb b/spec/requests/api/graphql/namespace/projects_spec.rb
index 2a95b99572f..0b634e6b689 100644
--- a/spec/requests/api/graphql/namespace/projects_spec.rb
+++ b/spec/requests/api/graphql/namespace/projects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting projects' do
+RSpec.describe 'getting projects' do
include GraphqlHelpers
let(:group) { create(:group) }
diff --git a/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb b/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb
index f7e28043930..44e68c59248 100644
--- a/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb
+++ b/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'rendering namespace statistics' do
+RSpec.describe 'rendering namespace statistics' do
include GraphqlHelpers
let(:namespace) { user.namespace }
diff --git a/spec/requests/api/graphql/project/alert_management/alert/assignees_spec.rb b/spec/requests/api/graphql/project/alert_management/alert/assignees_spec.rb
index 4c048caaeee..dd001a73349 100644
--- a/spec/requests/api/graphql/project/alert_management/alert/assignees_spec.rb
+++ b/spec/requests/api/graphql/project/alert_management/alert/assignees_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting Alert Management Alert Assignees' do
+RSpec.describe 'getting Alert Management Alert Assignees' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/project/alert_management/alert/metrics_dashboard_url_spec.rb b/spec/requests/api/graphql/project/alert_management/alert/metrics_dashboard_url_spec.rb
new file mode 100644
index 00000000000..352a94cfc1d
--- /dev/null
+++ b/spec/requests/api/graphql/project/alert_management/alert/metrics_dashboard_url_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'getting Alert Management Alert Assignees' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:current_user) { create(:user) }
+
+ let(:fields) do
+ <<~QUERY
+ nodes {
+ iid
+ metricsDashboardUrl
+ }
+ QUERY
+ end
+
+ let(:graphql_query) do
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => project.full_path },
+ query_graphql_field('alertManagementAlerts', {}, fields)
+ )
+ end
+
+ let(:alerts) { graphql_data.dig('project', 'alertManagementAlerts', 'nodes') }
+ let(:first_alert) { alerts.first }
+
+ before do
+ project.add_developer(current_user)
+ end
+
+ context 'with self-managed prometheus payload' do
+ include_context 'self-managed prometheus alert attributes'
+
+ before do
+ create(:alert_management_alert, :prometheus, project: project, payload: payload)
+ end
+
+ it 'includes the correct metrics dashboard url' do
+ post_graphql(graphql_query, current_user: current_user)
+
+ expect(first_alert).to include('metricsDashboardUrl' => dashboard_url_for_alert)
+ end
+ end
+
+ context 'with gitlab-managed prometheus payload' do
+ include_context 'gitlab-managed prometheus alert attributes'
+
+ before do
+ create(:alert_management_alert, :prometheus, project: project, payload: payload, prometheus_alert: prometheus_alert)
+ end
+
+ it 'includes the correct metrics dashboard url' do
+ post_graphql(graphql_query, current_user: current_user)
+
+ expect(first_alert).to include('metricsDashboardUrl' => dashboard_url_for_alert)
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb b/spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb
index df6bfa8c97b..1350cba119b 100644
--- a/spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb
+++ b/spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb
@@ -2,15 +2,15 @@
require 'spec_helper'
-describe 'getting Alert Management Alert Notes' do
+RSpec.describe 'getting Alert Management Alert Notes' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:current_user) { create(:user) }
let_it_be(:first_alert) { create(:alert_management_alert, project: project, assignees: [current_user]) }
let_it_be(:second_alert) { create(:alert_management_alert, project: project) }
- let_it_be(:first_system_note) { create(:note_on_alert, noteable: first_alert, project: project) }
- let_it_be(:second_system_note) { create(:note_on_alert, noteable: first_alert, project: project) }
+ let_it_be(:first_system_note) { create(:note_on_alert, :with_system_note_metadata, noteable: first_alert, project: project) }
+ let_it_be(:second_system_note) { create(:note_on_alert, :with_system_note_metadata, noteable: first_alert, project: project) }
let(:params) { {} }
@@ -21,6 +21,8 @@ describe 'getting Alert Management Alert Notes' do
notes {
nodes {
id
+ body
+ systemNoteIconName
}
}
}
@@ -44,7 +46,17 @@ describe 'getting Alert Management Alert Notes' do
project.add_developer(current_user)
end
- it 'returns the notes ordered by createdAt' do
+ it 'includes expected data' do
+ post_graphql(query, current_user: current_user)
+
+ expect(first_notes_result.first).to include(
+ 'id' => first_system_note.to_global_id.to_s,
+ 'systemNoteIconName' => 'git-merge',
+ 'body' => first_system_note.note
+ )
+ end
+
+ it 'returns the notes ordered by createdAt with sufficient content' do
post_graphql(query, current_user: current_user)
expect(first_notes_result.length).to eq(2)
@@ -64,4 +76,18 @@ describe 'getting Alert Management Alert Notes' do
expect { post_graphql(query, current_user: current_user) }.not_to exceed_query_limit(base_count)
expect(alerts_result.length).to eq(3)
end
+
+ context 'for non-system notes' do
+ let_it_be(:user_note) { create(:note_on_alert, noteable: second_alert, project: project) }
+
+ it 'includes expected data' do
+ post_graphql(query, current_user: current_user)
+
+ expect(second_notes_result.first).to include(
+ 'id' => user_note.to_global_id.to_s,
+ 'systemNoteIconName' => nil,
+ 'body' => user_note.note
+ )
+ end
+ end
end
diff --git a/spec/requests/api/graphql/project/alert_management/alert_status_counts_spec.rb b/spec/requests/api/graphql/project/alert_management/alert_status_counts_spec.rb
index a0d1ff7efc5..b62215f43fb 100644
--- a/spec/requests/api/graphql/project/alert_management/alert_status_counts_spec.rb
+++ b/spec/requests/api/graphql/project/alert_management/alert_status_counts_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'getting Alert Management Alert counts by status' do
+RSpec.describe 'getting Alert Management Alert counts by status' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/requests/api/graphql/project/alert_management/alerts_spec.rb b/spec/requests/api/graphql/project/alert_management/alerts_spec.rb
index c591895f295..f050c6873f3 100644
--- a/spec/requests/api/graphql/project/alert_management/alerts_spec.rb
+++ b/spec/requests/api/graphql/project/alert_management/alerts_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'getting Alert Management Alerts' do
+RSpec.describe 'getting Alert Management Alerts' do
include GraphqlHelpers
let_it_be(:payload) { { 'custom' => { 'alert' => 'payload' } } }
@@ -73,12 +73,13 @@ describe 'getting Alert Management Alerts' do
'endedAt' => nil,
'details' => { 'custom.alert' => 'payload' },
'createdAt' => triggered_alert.created_at.strftime('%Y-%m-%dT%H:%M:%SZ'),
- 'updatedAt' => triggered_alert.updated_at.strftime('%Y-%m-%dT%H:%M:%SZ')
+ 'updatedAt' => triggered_alert.updated_at.strftime('%Y-%m-%dT%H:%M:%SZ'),
+ 'metricsDashboardUrl' => nil
)
expect(second_alert).to include(
'iid' => resolved_alert.iid.to_s,
- 'issueIid' => nil,
+ 'issueIid' => resolved_alert.issue_iid.to_s,
'status' => 'RESOLVED',
'endedAt' => resolved_alert.ended_at.strftime('%Y-%m-%dT%H:%M:%SZ')
)
@@ -109,14 +110,14 @@ describe 'getting Alert Management Alerts' do
it_behaves_like 'a working graphql query'
it 'sorts in the correct order' do
- expect(iids).to eq [resolved_alert.iid.to_s, triggered_alert.iid.to_s]
+ expect(iids).to eq [triggered_alert.iid.to_s, resolved_alert.iid.to_s]
end
context 'ascending order' do
let(:params) { 'sort: SEVERITY_ASC' }
it 'sorts in the correct order' do
- expect(iids).to eq [triggered_alert.iid.to_s, resolved_alert.iid.to_s]
+ expect(iids).to eq [resolved_alert.iid.to_s, triggered_alert.iid.to_s]
end
end
end
diff --git a/spec/requests/api/graphql/project/base_service_spec.rb b/spec/requests/api/graphql/project/base_service_spec.rb
index 8199f331fbf..4dfc242da80 100644
--- a/spec/requests/api/graphql/project/base_service_spec.rb
+++ b/spec/requests/api/graphql/project/base_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'query Jira service' do
+RSpec.describe 'query Jira service' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/project/container_expiration_policy_spec.rb b/spec/requests/api/graphql/project/container_expiration_policy_spec.rb
index d0563f9ff05..b064e4d43e9 100644
--- a/spec/requests/api/graphql/project/container_expiration_policy_spec.rb
+++ b/spec/requests/api/graphql/project/container_expiration_policy_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'getting a repository in a project' do
+RSpec.describe 'getting a repository in a project' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb b/spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb
index a1f9fa1f10c..b2b42137acf 100644
--- a/spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb
+++ b/spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'getting a detailed sentry error' do
+RSpec.describe 'getting a detailed sentry error' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb b/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb
index 06a0bfc0d32..cd84ce9cb96 100644
--- a/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb
+++ b/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'sentry errors requests' do
+RSpec.describe 'sentry errors requests' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
let_it_be(:project_setting) { create(:project_error_tracking_setting, project: project) }
diff --git a/spec/requests/api/graphql/project/grafana_integration_spec.rb b/spec/requests/api/graphql/project/grafana_integration_spec.rb
index c9bc6c1a68e..688959e622d 100644
--- a/spec/requests/api/graphql/project/grafana_integration_spec.rb
+++ b/spec/requests/api/graphql/project/grafana_integration_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'Getting Grafana Integration' do
+RSpec.describe 'Getting Grafana Integration' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb b/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb
index 04f445b4318..1b654e660e3 100644
--- a/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb
+++ b/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Query.project(fullPath).issue(iid).designCollection.version(sha)' do
+RSpec.describe 'Query.project(fullPath).issue(iid).designCollection.version(sha)' do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb b/spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb
index 18787bf925d..640ac95cd86 100644
--- a/spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb
+++ b/spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Getting versions related to an issue' do
+RSpec.describe 'Getting versions related to an issue' do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/requests/api/graphql/project/issue/designs/designs_spec.rb b/spec/requests/api/graphql/project/issue/designs/designs_spec.rb
index b6fd0d91bda..e47c025f8b2 100644
--- a/spec/requests/api/graphql/project/issue/designs/designs_spec.rb
+++ b/spec/requests/api/graphql/project/issue/designs/designs_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Getting designs related to an issue' do
+RSpec.describe 'Getting designs related to an issue' do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/requests/api/graphql/project/issue/designs/notes_spec.rb b/spec/requests/api/graphql/project/issue/designs/notes_spec.rb
index 0207bb9123a..ae5c8363d0f 100644
--- a/spec/requests/api/graphql/project/issue/designs/notes_spec.rb
+++ b/spec/requests/api/graphql/project/issue/designs/notes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Getting designs related to an issue' do
+RSpec.describe 'Getting designs related to an issue' do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/requests/api/graphql/project/issue/notes_spec.rb b/spec/requests/api/graphql/project/issue/notes_spec.rb
index bfc89434370..97f5261ef1d 100644
--- a/spec/requests/api/graphql/project/issue/notes_spec.rb
+++ b/spec/requests/api/graphql/project/issue/notes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting notes for an issue' do
+RSpec.describe 'getting notes for an issue' do
include GraphqlHelpers
let(:noteable) { create(:issue) }
diff --git a/spec/requests/api/graphql/project/issue_spec.rb b/spec/requests/api/graphql/project/issue_spec.rb
index 92d2f9d0d31..5f368833181 100644
--- a/spec/requests/api/graphql/project/issue_spec.rb
+++ b/spec/requests/api/graphql/project/issue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Query.project(fullPath).issue(iid)' do
+RSpec.describe 'Query.project(fullPath).issue(iid)' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb
index 3128f527356..cdfff2f50d4 100644
--- a/spec/requests/api/graphql/project/issues_spec.rb
+++ b/spec/requests/api/graphql/project/issues_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting an issue list for a project' do
+RSpec.describe 'getting an issue list for a project' do
include GraphqlHelpers
let(:project) { create(:project, :repository, :public) }
diff --git a/spec/requests/api/graphql/project/jira_import_spec.rb b/spec/requests/api/graphql/project/jira_import_spec.rb
index 7be14696963..814965262b6 100644
--- a/spec/requests/api/graphql/project/jira_import_spec.rb
+++ b/spec/requests/api/graphql/project/jira_import_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'query Jira import data' do
+RSpec.describe 'query Jira import data' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/project/jira_projects_spec.rb b/spec/requests/api/graphql/project/jira_projects_spec.rb
index d67c89f18c9..d5f59711ab1 100644
--- a/spec/requests/api/graphql/project/jira_projects_spec.rb
+++ b/spec/requests/api/graphql/project/jira_projects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'query Jira projects' do
+RSpec.describe 'query Jira projects' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
@@ -80,34 +80,6 @@ describe 'query Jira projects' do
it_behaves_like 'fetches first project'
end
-
- context 'with before cursor' do
- let(:projects_query) { 'projects(before: "Mg==", first: 1)' }
-
- it_behaves_like 'fetches first project'
- end
-
- context 'with after cursor' do
- let(:projects_query) { 'projects(after: "MA==", first: 1)' }
-
- it_behaves_like 'fetches first project'
- end
- end
-
- context 'with valid but inexistent after cursor' do
- let(:projects_query) { 'projects(after: "MTk==")' }
-
- it 'retuns empty list of jira projects' do
- expect(jira_projects.size).to eq(0)
- end
- end
-
- context 'with invalid after cursor' do
- let(:projects_query) { 'projects(after: "invalid==")' }
-
- it 'treats the invalid cursor as no cursor and returns list of jira projects' do
- expect(jira_projects.size).to eq(2)
- end
end
end
end
diff --git a/spec/requests/api/graphql/project/jira_service_spec.rb b/spec/requests/api/graphql/project/jira_service_spec.rb
index 4ac598b789f..905a669bf0d 100644
--- a/spec/requests/api/graphql/project/jira_service_spec.rb
+++ b/spec/requests/api/graphql/project/jira_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'query Jira service' do
+RSpec.describe 'query Jira service' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/project/labels_query_spec.rb b/spec/requests/api/graphql/project/labels_query_spec.rb
index ecc43e0a3db..eeaaaaee575 100644
--- a/spec/requests/api/graphql/project/labels_query_spec.rb
+++ b/spec/requests/api/graphql/project/labels_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting project label information' do
+RSpec.describe 'getting project label information' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :public) }
diff --git a/spec/requests/api/graphql/project/merge_request/diff_notes_spec.rb b/spec/requests/api/graphql/project/merge_request/diff_notes_spec.rb
index c616310a72c..dd16b052e0e 100644
--- a/spec/requests/api/graphql/project/merge_request/diff_notes_spec.rb
+++ b/spec/requests/api/graphql/project/merge_request/diff_notes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting notes for a merge request' do
+RSpec.describe 'getting notes for a merge request' do
include GraphqlHelpers
let_it_be(:noteable) { create(:merge_request) }
diff --git a/spec/requests/api/graphql/project/merge_request_spec.rb b/spec/requests/api/graphql/project/merge_request_spec.rb
index 643532bf2e2..c39358a2db1 100644
--- a/spec/requests/api/graphql/project/merge_request_spec.rb
+++ b/spec/requests/api/graphql/project/merge_request_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting merge request information nested in a project' do
+RSpec.describe 'getting merge request information nested in a project' do
include GraphqlHelpers
let(:project) { create(:project, :repository, :public) }
@@ -43,6 +43,59 @@ describe 'getting merge request information nested in a project' do
expect(merge_request_graphql_data['author']['username']).to eq(merge_request.author.username)
end
+ it 'includes diff stats' do
+ be_natural = an_instance_of(Integer).and(be >= 0)
+
+ post_graphql(query, current_user: current_user)
+
+ sums = merge_request_graphql_data['diffStats'].reduce([0, 0, 0]) do |(a, d, c), node|
+ a_, d_ = node.values_at('additions', 'deletions')
+ [a + a_, d + d_, c + a_ + d_]
+ end
+
+ expect(merge_request_graphql_data).to include(
+ 'diffStats' => all(a_hash_including('path' => String, 'additions' => be_natural, 'deletions' => be_natural)),
+ 'diffStatsSummary' => a_hash_including(
+ 'fileCount' => merge_request.diff_stats.count,
+ 'additions' => be_natural,
+ 'deletions' => be_natural,
+ 'changes' => be_natural
+ )
+ )
+
+ # diff_stats is consistent with summary
+ expect(merge_request_graphql_data['diffStatsSummary']
+ .values_at('additions', 'deletions', 'changes')).to eq(sums)
+
+ # diff_stats_summary is internally consistent
+ expect(merge_request_graphql_data['diffStatsSummary']
+ .values_at('additions', 'deletions').sum)
+ .to eq(merge_request_graphql_data.dig('diffStatsSummary', 'changes'))
+ .and be_positive
+ end
+
+ context 'requesting a specific diff stat' do
+ let(:diff_stat) { merge_request.diff_stats.first }
+
+ let(:query) do
+ graphql_query_for(:project, { full_path: project.full_path },
+ query_graphql_field(:merge_request, { iid: merge_request.iid.to_s }, [
+ query_graphql_field(:diff_stats, { path: diff_stat.path }, all_graphql_fields_for('DiffStats'))
+ ])
+ )
+ end
+
+ it 'includes only the requested stats' do
+ post_graphql(query, current_user: current_user)
+
+ expect(merge_request_graphql_data).to include(
+ 'diffStats' => contain_exactly(
+ a_hash_including('path' => diff_stat.path, 'additions' => diff_stat.additions, 'deletions' => diff_stat.deletions)
+ )
+ )
+ end
+ end
+
it 'includes correct mergedAt value when merged' do
time = 1.week.ago
merge_request.mark_as_merged
diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb
index 49fdfe29874..e2255fdb048 100644
--- a/spec/requests/api/graphql/project/merge_requests_spec.rb
+++ b/spec/requests/api/graphql/project/merge_requests_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting merge request listings nested in a project' do
+RSpec.describe 'getting merge request listings nested in a project' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository, :public) }
diff --git a/spec/requests/api/graphql/project/packages_spec.rb b/spec/requests/api/graphql/project/packages_spec.rb
new file mode 100644
index 00000000000..88f97f9256b
--- /dev/null
+++ b/spec/requests/api/graphql/project/packages_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'getting a package list for a project' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:package) { create(:package, project: project) }
+ let(:packages_data) { graphql_data['project']['packages']['edges'] }
+
+ let(:fields) do
+ <<~QUERY
+ edges {
+ node {
+ #{all_graphql_fields_for('packages'.classify)}
+ }
+ }
+ QUERY
+ end
+
+ let(:query) do
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => project.full_path },
+ query_graphql_field('packages', {}, fields)
+ )
+ end
+
+ context 'without the need for a license' do
+ context 'when user has access to the project' do
+ before do
+ project.add_reporter(current_user)
+ post_graphql(query, current_user: current_user)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns packages successfully' do
+ expect(packages_data[0]['node']['name']).to eq package.name
+ end
+ end
+
+ context 'when the user does not have access to the project/packages' do
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns nil' do
+ expect(graphql_data['project']).to be_nil
+ end
+ end
+
+ context 'when the user is not autenthicated' do
+ before do
+ post_graphql(query)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns nil' do
+ expect(graphql_data['project']).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/pipeline_spec.rb b/spec/requests/api/graphql/project/pipeline_spec.rb
index bed9a18577f..57b9de25c3d 100644
--- a/spec/requests/api/graphql/project/pipeline_spec.rb
+++ b/spec/requests/api/graphql/project/pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting pipeline information nested in a project' do
+RSpec.describe 'getting pipeline information nested in a project' do
include GraphqlHelpers
let(:project) { create(:project, :repository, :public) }
diff --git a/spec/requests/api/graphql/project/project_statistics_spec.rb b/spec/requests/api/graphql/project/project_statistics_spec.rb
index 05dd5d36c26..c226b10ab51 100644
--- a/spec/requests/api/graphql/project/project_statistics_spec.rb
+++ b/spec/requests/api/graphql/project/project_statistics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'rendering project statistics' do
+RSpec.describe 'rendering project statistics' do
include GraphqlHelpers
let(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/project/release_spec.rb b/spec/requests/api/graphql/project/release_spec.rb
index f8624a97a2b..f9c19d9747d 100644
--- a/spec/requests/api/graphql/project/release_spec.rb
+++ b/spec/requests/api/graphql/project/release_spec.rb
@@ -1,206 +1,374 @@
# frozen_string_literal: true
require 'spec_helper'
-require 'pp'
-describe 'Query.project(fullPath).release(tagName)' do
+RSpec.describe 'Query.project(fullPath).release(tagName)' do
include GraphqlHelpers
include Presentable
- let_it_be(:project) { create(:project, :repository) }
- let_it_be(:milestone_1) { create(:milestone, project: project) }
- let_it_be(:milestone_2) { create(:milestone, project: project) }
- let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2]) }
- let_it_be(:release_link_1) { create(:release_link, release: release) }
- let_it_be(:release_link_2) { create(:release_link, release: release) }
let_it_be(:developer) { create(:user) }
+ let_it_be(:guest) { create(:user) }
+ let_it_be(:reporter) { create(:user) }
+ let_it_be(:stranger) { create(:user) }
- let(:current_user) { developer }
+ let(:params_for_issues_and_mrs) { { scope: 'all', state: 'opened', release_tag: release.tag } }
+ let(:post_query) { post_graphql(query, current_user: current_user) }
+ let(:path_prefix) { %w[project release] }
+ let(:data) { graphql_data.dig(*path) }
def query(rq = release_fields)
graphql_query_for(:project, { fullPath: project.full_path },
query_graphql_field(:release, { tagName: release.tag }, rq))
end
- let(:post_query) { post_graphql(query, current_user: current_user) }
- let(:path_prefix) { %w[project release] }
-
- let(:data) { graphql_data.dig(*path) }
-
before do
- project.add_developer(developer)
+ stub_default_url_options(host: 'www.example.com')
end
- describe 'scalar fields' do
- let(:path) { path_prefix }
- let(:release_fields) do
- query_graphql_field(%{
- tagName
- tagPath
- description
- descriptionHtml
- name
- createdAt
- releasedAt
- })
+ shared_examples 'full access to the release field' do
+ describe 'scalar fields' do
+ let(:path) { path_prefix }
+
+ let(:release_fields) do
+ query_graphql_field(%{
+ tagName
+ tagPath
+ description
+ descriptionHtml
+ name
+ createdAt
+ releasedAt
+ })
+ end
+
+ before do
+ post_query
+ end
+
+ it 'finds all release data' do
+ expect(data).to eq({
+ 'tagName' => release.tag,
+ 'tagPath' => project_tag_path(project, release.tag),
+ 'description' => release.description,
+ 'descriptionHtml' => release.description_html,
+ 'name' => release.name,
+ 'createdAt' => release.created_at.iso8601,
+ 'releasedAt' => release.released_at.iso8601
+ })
+ end
end
- before do
- post_query
+ describe 'milestones' do
+ let(:path) { path_prefix + %w[milestones nodes] }
+
+ let(:release_fields) do
+ query_graphql_field(:milestones, nil, 'nodes { id title }')
+ end
+
+ it 'finds all milestones associated to a release' do
+ post_query
+
+ expected = release.milestones.map do |milestone|
+ { 'id' => global_id_of(milestone), 'title' => milestone.title }
+ end
+
+ expect(data).to match_array(expected)
+ end
end
- it 'finds all release data' do
- expect(data).to eq({
- 'tagName' => release.tag,
- 'tagPath' => project_tag_path(project, release.tag),
- 'description' => release.description,
- 'descriptionHtml' => release.description_html,
- 'name' => release.name,
- 'createdAt' => release.created_at.iso8601,
- 'releasedAt' => release.released_at.iso8601
- })
+ describe 'author' do
+ let(:path) { path_prefix + %w[author] }
+
+ let(:release_fields) do
+ query_graphql_field(:author, nil, 'id username')
+ end
+
+ it 'finds the author of the release' do
+ post_query
+
+ expect(data).to eq(
+ 'id' => global_id_of(release.author),
+ 'username' => release.author.username
+ )
+ end
end
- end
- describe 'milestones' do
- let(:path) { path_prefix + %w[milestones nodes] }
- let(:release_fields) do
- query_graphql_field(:milestones, nil, 'nodes { id title }')
+ describe 'commit' do
+ let(:path) { path_prefix + %w[commit] }
+
+ let(:release_fields) do
+ query_graphql_field(:commit, nil, 'sha')
+ end
+
+ it 'finds the commit associated with the release' do
+ post_query
+
+ expect(data).to eq('sha' => release.commit.sha)
+ end
end
- it 'finds all milestones associated to a release' do
- post_query
+ describe 'assets' do
+ describe 'count' do
+ let(:path) { path_prefix + %w[assets] }
+
+ let(:release_fields) do
+ query_graphql_field(:assets, nil, 'count')
+ end
+
+ it 'returns the number of assets associated to the release' do
+ post_query
+
+ expect(data).to eq('count' => release.sources.size + release.links.size)
+ end
+ end
+
+ describe 'links' do
+ let(:path) { path_prefix + %w[assets links nodes] }
- expected = release.milestones.map do |milestone|
- { 'id' => global_id_of(milestone), 'title' => milestone.title }
+ let(:release_fields) do
+ query_graphql_field(:assets, nil,
+ query_graphql_field(:links, nil, 'nodes { id name url external }'))
+ end
+
+ it 'finds all release links' do
+ post_query
+
+ expected = release.links.map do |link|
+ {
+ 'id' => global_id_of(link),
+ 'name' => link.name,
+ 'url' => link.url,
+ 'external' => link.external?
+ }
+ end
+
+ expect(data).to match_array(expected)
+ end
end
- expect(data).to match_array(expected)
+ describe 'sources' do
+ let(:path) { path_prefix + %w[assets sources nodes] }
+
+ let(:release_fields) do
+ query_graphql_field(:assets, nil,
+ query_graphql_field(:sources, nil, 'nodes { format url }'))
+ end
+
+ it 'finds all release sources' do
+ post_query
+
+ expected = release.sources.map do |source|
+ {
+ 'format' => source.format,
+ 'url' => source.url
+ }
+ end
+
+ expect(data).to match_array(expected)
+ end
+ end
+ end
+
+ describe 'links' do
+ let(:path) { path_prefix + %w[links] }
+
+ let(:release_fields) do
+ query_graphql_field(:links, nil, %{
+ selfUrl
+ mergeRequestsUrl
+ issuesUrl
+ })
+ end
+
+ it 'finds all release links' do
+ post_query
+
+ expect(data).to eq(
+ 'selfUrl' => project_release_url(project, release),
+ 'mergeRequestsUrl' => project_merge_requests_url(project, params_for_issues_and_mrs),
+ 'issuesUrl' => project_issues_url(project, params_for_issues_and_mrs)
+ )
+ end
+ end
+
+ describe 'evidences' do
+ let(:path) { path_prefix + %w[evidences] }
+
+ let(:release_fields) do
+ query_graphql_field(:evidences, nil, 'nodes { id sha filepath collectedAt }')
+ end
+
+ it 'finds all evidence fields' do
+ post_query
+
+ evidence = release.evidences.first.present
+
+ expect(data["nodes"].first).to eq(
+ 'id' => global_id_of(evidence),
+ 'sha' => evidence.sha,
+ 'filepath' => evidence.filepath,
+ 'collectedAt' => evidence.collected_at.utc.iso8601
+ )
+ end
+ end
+ end
+
+ shared_examples 'no access to the release field' do
+ describe 'repository-related fields' do
+ let(:path) { path_prefix }
+
+ let(:release_fields) do
+ query_graphql_field('description')
+ end
+
+ before do
+ post_query
+ end
+
+ it 'returns nil' do
+ expect(data).to eq(nil)
+ end
end
end
- describe 'author' do
- let(:path) { path_prefix + %w[author] }
+ shared_examples 'access to editUrl' do
+ let(:path) { path_prefix + %w[links] }
+
let(:release_fields) do
- query_graphql_field(:author, nil, 'id username')
+ query_graphql_field(:links, nil, 'editUrl')
end
- it 'finds the author of the release' do
+ before do
post_query
+ end
- expect(data).to eq({
- 'id' => global_id_of(release.author),
- 'username' => release.author.username
- })
+ it 'returns editUrl' do
+ expect(data).to eq('editUrl' => edit_project_release_url(project, release))
end
end
- describe 'commit' do
- let(:path) { path_prefix + %w[commit] }
+ shared_examples 'no access to editUrl' do
+ let(:path) { path_prefix + %w[links] }
+
let(:release_fields) do
- query_graphql_field(:commit, nil, 'sha')
+ query_graphql_field(:links, nil, 'editUrl')
end
- it 'finds the commit associated with the release' do
+ before do
post_query
+ end
- expect(data).to eq({ 'sha' => release.commit.sha })
+ it 'does not return editUrl' do
+ expect(data).to eq('editUrl' => nil)
end
end
- describe 'assets' do
- describe 'assetsCount' do
- let(:path) { path_prefix + %w[assets] }
- let(:release_fields) do
- query_graphql_field(:assets, nil, 'assetsCount')
+ describe "ensures that the correct data is returned based on the project's visibility and the user's access level" do
+ context 'when the project is private' do
+ let_it_be(:project) { create(:project, :repository, :private) }
+ let_it_be(:milestone_1) { create(:milestone, project: project) }
+ let_it_be(:milestone_2) { create(:milestone, project: project) }
+ let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2]) }
+ let_it_be(:release_link_1) { create(:release_link, release: release) }
+ let_it_be(:release_link_2) { create(:release_link, release: release) }
+
+ before_all do
+ project.add_developer(developer)
+ project.add_guest(guest)
+ project.add_reporter(reporter)
end
- it 'returns the number of assets associated to the release' do
- post_query
+ context 'when the user is not logged in' do
+ let(:current_user) { stranger }
- expect(data).to eq({ 'assetsCount' => release.sources.size + release.links.size })
+ it_behaves_like 'no access to the release field'
end
- end
- describe 'links' do
- let(:path) { path_prefix + %w[assets links nodes] }
- let(:release_fields) do
- query_graphql_field(:assets, nil,
- query_graphql_field(:links, nil, 'nodes { id name url external }'))
+ context 'when the user has Guest permissions' do
+ let(:current_user) { guest }
+
+ it_behaves_like 'no access to the release field'
end
- it 'finds all release links' do
- post_query
+ context 'when the user has Reporter permissions' do
+ let(:current_user) { reporter }
- expected = release.links.map do |link|
- {
- 'id' => global_id_of(link),
- 'name' => link.name,
- 'url' => link.url,
- 'external' => link.external?
- }
- end
+ it_behaves_like 'full access to the release field'
+ it_behaves_like 'no access to editUrl'
+ end
- expect(data).to match_array(expected)
+ context 'when the user has Developer permissions' do
+ let(:current_user) { developer }
+
+ it_behaves_like 'full access to the release field'
+ it_behaves_like 'access to editUrl'
end
end
- describe 'sources' do
- let(:path) { path_prefix + %w[assets sources nodes] }
- let(:release_fields) do
- query_graphql_field(:assets, nil,
- query_graphql_field(:sources, nil, 'nodes { format url }'))
+ context 'when the project is public' do
+ let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:milestone_1) { create(:milestone, project: project) }
+ let_it_be(:milestone_2) { create(:milestone, project: project) }
+ let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2]) }
+ let_it_be(:release_link_1) { create(:release_link, release: release) }
+ let_it_be(:release_link_2) { create(:release_link, release: release) }
+
+ before_all do
+ project.add_developer(developer)
+ project.add_guest(guest)
+ project.add_reporter(reporter)
end
- it 'finds all release sources' do
- post_query
+ context 'when the user is not logged in' do
+ let(:current_user) { stranger }
- expected = release.sources.map do |source|
- {
- 'format' => source.format,
- 'url' => source.url
- }
- end
+ it_behaves_like 'full access to the release field'
+ it_behaves_like 'no access to editUrl'
+ end
- expect(data).to match_array(expected)
+ context 'when the user has Guest permissions' do
+ let(:current_user) { guest }
+
+ it_behaves_like 'full access to the release field'
+ it_behaves_like 'no access to editUrl'
end
- end
- describe 'evidences' do
- let(:path) { path_prefix + %w[evidences] }
- let(:release_fields) do
- query_graphql_field(:evidences, nil, 'nodes { id sha filepath collectedAt }')
+ context 'when the user has Reporter permissions' do
+ let(:current_user) { reporter }
+
+ it_behaves_like 'full access to the release field'
+ it_behaves_like 'no access to editUrl'
end
- context 'for a developer' do
- it 'finds all evidence fields' do
- post_query
+ context 'when the user has Reporter permissions' do
+ let(:current_user) { reporter }
- evidence = release.evidences.first.present
- expected = {
- 'id' => global_id_of(evidence),
- 'sha' => evidence.sha,
- 'filepath' => evidence.filepath,
- 'collectedAt' => evidence.collected_at.utc.iso8601
- }
+ it_behaves_like 'full access to the release field'
+ end
- expect(data["nodes"].first).to eq(expected)
- end
+ context 'when the user has Developer permissions' do
+ let(:current_user) { developer }
+
+ it_behaves_like 'full access to the release field'
+ it_behaves_like 'access to editUrl'
end
+ end
+ end
- context 'for a guest' do
- let(:current_user) { create :user }
+ describe 'ensures that the release data can be contolled by a feature flag' do
+ context 'when the graphql_release_data feature flag is disabled' do
+ let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:release) { create(:release, project: project) }
- before do
- project.add_guest(current_user)
- end
+ let(:current_user) { developer }
- it 'denies access' do
- post_query
+ before do
+ stub_feature_flags(graphql_release_data: false)
- expect(data['node']).to be_nil
- end
+ project.add_developer(developer)
end
+
+ it_behaves_like 'no access to the release field'
end
end
end
diff --git a/spec/requests/api/graphql/project/releases_spec.rb b/spec/requests/api/graphql/project/releases_spec.rb
new file mode 100644
index 00000000000..7e418bbaa5b
--- /dev/null
+++ b/spec/requests/api/graphql/project/releases_spec.rb
@@ -0,0 +1,284 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Query.project(fullPath).releases()' do
+ include GraphqlHelpers
+
+ let_it_be(:stranger) { create(:user) }
+ let_it_be(:guest) { create(:user) }
+ let_it_be(:reporter) { create(:user) }
+ let_it_be(:developer) { create(:user) }
+
+ let(:query) do
+ graphql_query_for(:project, { fullPath: project.full_path },
+ %{
+ releases {
+ nodes {
+ tagName
+ tagPath
+ name
+ commit {
+ sha
+ }
+ assets {
+ count
+ sources {
+ nodes {
+ url
+ }
+ }
+ }
+ evidences {
+ nodes {
+ sha
+ }
+ }
+ links {
+ selfUrl
+ mergeRequestsUrl
+ issuesUrl
+ }
+ }
+ }
+ })
+ end
+
+ let(:params_for_issues_and_mrs) { { scope: 'all', state: 'opened', release_tag: release.tag } }
+ let(:post_query) { post_graphql(query, current_user: current_user) }
+
+ let(:data) { graphql_data.dig('project', 'releases', 'nodes', 0) }
+
+ before do
+ stub_default_url_options(host: 'www.example.com')
+ end
+
+ shared_examples 'full access to all repository-related fields' do
+ describe 'repository-related fields' do
+ before do
+ post_query
+ end
+
+ it 'returns data for fields that are protected in private projects' do
+ expected_sources = release.sources.map do |s|
+ { 'url' => s.url }
+ end
+
+ expected_evidences = release.evidences.map do |e|
+ { 'sha' => e.sha }
+ end
+
+ expect(data).to eq(
+ 'tagName' => release.tag,
+ 'tagPath' => project_tag_path(project, release.tag),
+ 'name' => release.name,
+ 'commit' => {
+ 'sha' => release.commit.sha
+ },
+ 'assets' => {
+ 'count' => release.assets_count,
+ 'sources' => {
+ 'nodes' => expected_sources
+ }
+ },
+ 'evidences' => {
+ 'nodes' => expected_evidences
+ },
+ 'links' => {
+ 'selfUrl' => project_release_url(project, release),
+ 'mergeRequestsUrl' => project_merge_requests_url(project, params_for_issues_and_mrs),
+ 'issuesUrl' => project_issues_url(project, params_for_issues_and_mrs)
+ }
+ )
+ end
+ end
+ end
+
+ shared_examples 'no access to any repository-related fields' do
+ describe 'repository-related fields' do
+ before do
+ post_query
+ end
+
+ it 'does not return data for fields that expose repository information' do
+ expect(data).to eq(
+ 'tagName' => nil,
+ 'tagPath' => nil,
+ 'name' => "Release-#{release.id}",
+ 'commit' => nil,
+ 'assets' => {
+ 'count' => release.assets_count(except: [:sources]),
+ 'sources' => {
+ 'nodes' => []
+ }
+ },
+ 'evidences' => {
+ 'nodes' => []
+ },
+ 'links' => nil
+ )
+ end
+ end
+ end
+
+ # editUrl is tested separately becuase its permissions
+ # are slightly different than other release fields
+ shared_examples 'access to editUrl' do
+ let(:query) do
+ graphql_query_for(:project, { fullPath: project.full_path },
+ %{
+ releases {
+ nodes {
+ links {
+ editUrl
+ }
+ }
+ }
+ })
+ end
+
+ before do
+ post_query
+ end
+
+ it 'returns editUrl' do
+ expect(data).to eq(
+ 'links' => {
+ 'editUrl' => edit_project_release_url(project, release)
+ }
+ )
+ end
+ end
+
+ shared_examples 'no access to editUrl' do
+ let(:query) do
+ graphql_query_for(:project, { fullPath: project.full_path },
+ %{
+ releases {
+ nodes {
+ links {
+ editUrl
+ }
+ }
+ }
+ })
+ end
+
+ before do
+ post_query
+ end
+
+ it 'does not return editUrl' do
+ expect(data).to eq(
+ 'links' => {
+ 'editUrl' => nil
+ }
+ )
+ end
+ end
+
+ shared_examples 'no access to any release data' do
+ before do
+ post_query
+ end
+
+ it 'returns nil' do
+ expect(data).to eq(nil)
+ end
+ end
+
+ describe "ensures that the correct data is returned based on the project's visibility and the user's access level" do
+ context 'when the project is private' do
+ let_it_be(:project) { create(:project, :repository, :private) }
+ let_it_be(:release) { create(:release, :with_evidence, project: project) }
+
+ before_all do
+ project.add_guest(guest)
+ project.add_reporter(reporter)
+ project.add_developer(developer)
+ end
+
+ context 'when the user is not logged in' do
+ let(:current_user) { stranger }
+
+ it_behaves_like 'no access to any release data'
+ end
+
+ context 'when the user has Guest permissions' do
+ let(:current_user) { guest }
+
+ it_behaves_like 'no access to any repository-related fields'
+ end
+
+ context 'when the user has Reporter permissions' do
+ let(:current_user) { reporter }
+
+ it_behaves_like 'full access to all repository-related fields'
+ it_behaves_like 'no access to editUrl'
+ end
+
+ context 'when the user has Developer permissions' do
+ let(:current_user) { developer }
+
+ it_behaves_like 'full access to all repository-related fields'
+ it_behaves_like 'access to editUrl'
+ end
+ end
+
+ context 'when the project is public' do
+ let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:release) { create(:release, :with_evidence, project: project) }
+
+ before_all do
+ project.add_guest(guest)
+ project.add_reporter(reporter)
+ project.add_developer(developer)
+ end
+
+ context 'when the user is not logged in' do
+ let(:current_user) { stranger }
+
+ it_behaves_like 'full access to all repository-related fields'
+ it_behaves_like 'no access to editUrl'
+ end
+
+ context 'when the user has Guest permissions' do
+ let(:current_user) { guest }
+
+ it_behaves_like 'full access to all repository-related fields'
+ it_behaves_like 'no access to editUrl'
+ end
+
+ context 'when the user has Reporter permissions' do
+ let(:current_user) { reporter }
+
+ it_behaves_like 'full access to all repository-related fields'
+ it_behaves_like 'no access to editUrl'
+ end
+
+ context 'when the user has Developer permissions' do
+ let(:current_user) { developer }
+
+ it_behaves_like 'full access to all repository-related fields'
+ it_behaves_like 'access to editUrl'
+ end
+ end
+ end
+
+ describe 'ensures that the release data can be contolled by a feature flag' do
+ context 'when the graphql_release_data feature flag is disabled' do
+ let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:release) { create(:release, project: project) }
+
+ let(:current_user) { developer }
+
+ before do
+ stub_feature_flags(graphql_release_data: false)
+
+ project.add_developer(developer)
+ end
+
+ it_behaves_like 'no access to any release data'
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/repository_spec.rb b/spec/requests/api/graphql/project/repository_spec.rb
index 261433a3d6a..bd719a69647 100644
--- a/spec/requests/api/graphql/project/repository_spec.rb
+++ b/spec/requests/api/graphql/project/repository_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'getting a repository in a project' do
+RSpec.describe 'getting a repository in a project' do
include GraphqlHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/requests/api/graphql/project/tree/tree_spec.rb b/spec/requests/api/graphql/project/tree/tree_spec.rb
index 94128cc21ee..bce63d57c38 100644
--- a/spec/requests/api/graphql/project/tree/tree_spec.rb
+++ b/spec/requests/api/graphql/project/tree/tree_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'getting a tree in a project' do
+RSpec.describe 'getting a tree in a project' do
include GraphqlHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/requests/api/graphql/project_query_spec.rb b/spec/requests/api/graphql/project_query_spec.rb
index 9a88b47eea6..b115030afbc 100644
--- a/spec/requests/api/graphql/project_query_spec.rb
+++ b/spec/requests/api/graphql/project_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting project information' do
+RSpec.describe 'getting project information' do
include GraphqlHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/requests/api/graphql/query_spec.rb b/spec/requests/api/graphql/query_spec.rb
index 26b4c6eafd7..6bd0703c121 100644
--- a/spec/requests/api/graphql/query_spec.rb
+++ b/spec/requests/api/graphql/query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Query' do
+RSpec.describe 'Query' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/read_only_spec.rb b/spec/requests/api/graphql/read_only_spec.rb
index 1d28a71258d..ce8a3f6ef5c 100644
--- a/spec/requests/api/graphql/read_only_spec.rb
+++ b/spec/requests/api/graphql/read_only_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Requests on a read-only node' do
+RSpec.describe 'Requests on a read-only node' do
include GraphqlHelpers
before do
diff --git a/spec/requests/api/graphql/tasks/task_completion_status_spec.rb b/spec/requests/api/graphql/tasks/task_completion_status_spec.rb
index c47406ea534..5f4d2aec718 100644
--- a/spec/requests/api/graphql/tasks/task_completion_status_spec.rb
+++ b/spec/requests/api/graphql/tasks/task_completion_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting task completion status information' do
+RSpec.describe 'getting task completion status information' do
include GraphqlHelpers
description_0_done = '- [ ] task 1\n- [ ] task 2'
diff --git a/spec/requests/api/graphql/user/group_member_query_spec.rb b/spec/requests/api/graphql/user/group_member_query_spec.rb
index 022ee79297c..3a16d962214 100644
--- a/spec/requests/api/graphql/user/group_member_query_spec.rb
+++ b/spec/requests/api/graphql/user/group_member_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'GroupMember' do
+RSpec.describe 'GroupMember' do
include GraphqlHelpers
let_it_be(:member) { create(:group_member, :developer) }
diff --git a/spec/requests/api/graphql/user/project_member_query_spec.rb b/spec/requests/api/graphql/user/project_member_query_spec.rb
index 397d2872189..0790e148caf 100644
--- a/spec/requests/api/graphql/user/project_member_query_spec.rb
+++ b/spec/requests/api/graphql/user/project_member_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'ProjectMember' do
+RSpec.describe 'ProjectMember' do
include GraphqlHelpers
let_it_be(:member) { create(:project_member, :developer) }
diff --git a/spec/requests/api/graphql/user_query_spec.rb b/spec/requests/api/graphql/user_query_spec.rb
index 5ac94bc7323..7ba1788a9ef 100644
--- a/spec/requests/api/graphql/user_query_spec.rb
+++ b/spec/requests/api/graphql/user_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting user information' do
+RSpec.describe 'getting user information' do
include GraphqlHelpers
let(:query) do
diff --git a/spec/requests/api/graphql/user_spec.rb b/spec/requests/api/graphql/user_spec.rb
index 097c75b3541..d2d6b1fca66 100644
--- a/spec/requests/api/graphql/user_spec.rb
+++ b/spec/requests/api/graphql/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'User' do
+RSpec.describe 'User' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/users_spec.rb b/spec/requests/api/graphql/users_spec.rb
index 1e6d73cbd7d..91ac206676b 100644
--- a/spec/requests/api/graphql/users_spec.rb
+++ b/spec/requests/api/graphql/users_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Users' do
+RSpec.describe 'Users' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user, created_at: 1.day.ago) }
diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb
index 84be5ab0951..ff1a5aa1540 100644
--- a/spec/requests/api/graphql_spec.rb
+++ b/spec/requests/api/graphql_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'GraphQL' do
+RSpec.describe 'GraphQL' do
include GraphqlHelpers
let(:query) { graphql_query_for('echo', 'text' => 'Hello world' ) }
diff --git a/spec/requests/api/group_boards_spec.rb b/spec/requests/api/group_boards_spec.rb
index a9083f82f25..6ce8b766807 100644
--- a/spec/requests/api/group_boards_spec.rb
+++ b/spec/requests/api/group_boards_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupBoards do
+RSpec.describe API::GroupBoards do
let_it_be(:user) { create(:user) }
let_it_be(:non_member) { create(:user) }
let_it_be(:guest) { create(:user) }
diff --git a/spec/requests/api/group_clusters_spec.rb b/spec/requests/api/group_clusters_spec.rb
index fade54f6b11..068af1485e2 100644
--- a/spec/requests/api/group_clusters_spec.rb
+++ b/spec/requests/api/group_clusters_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupClusters do
+RSpec.describe API::GroupClusters do
include KubernetesHelpers
let(:current_user) { create(:user) }
@@ -266,29 +266,51 @@ describe API::GroupClusters do
end
end
- context 'when user tries to add multiple clusters' do
+ context 'non-authorized user' do
before do
- create(:cluster, :provided_by_gcp, :group,
- groups: [group])
-
- post api("/groups/#{group.id}/clusters/user", current_user), params: cluster_params
+ post api("/groups/#{group.id}/clusters/user", developer_user), params: cluster_params
end
- it 'responds with 400' do
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message']['base'].first).to eq(_('Instance does not support multiple Kubernetes clusters'))
+ it 'responds with 403' do
+ expect(response).to have_gitlab_http_status(:forbidden)
+
+ expect(json_response['message']).to eq('403 Forbidden')
end
end
+ end
- context 'non-authorized user' do
+ describe 'PUT /groups/:id/clusters/:cluster_id' do
+ let(:api_url) { 'https://kubernetes.example.com' }
+
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'sample-token'
+ }
+ end
+
+ let(:cluster_params) do
+ {
+ name: 'test-cluster',
+ environment_scope: 'test/*',
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ context 'when another cluster exists' do
before do
- post api("/groups/#{group.id}/clusters/user", developer_user), params: cluster_params
+ create(:cluster, :provided_by_gcp, :group,
+ groups: [group])
+
+ post api("/groups/#{group.id}/clusters/user", current_user), params: cluster_params
end
- it 'responds with 403' do
- expect(response).to have_gitlab_http_status(:forbidden)
+ it 'responds with 201' do
+ expect(response).to have_gitlab_http_status(:created)
+ end
- expect(json_response['message']).to eq('403 Forbidden')
+ it 'allows multiple clusters to be associated to group' do
+ expect(group.reload.clusters.count).to eq(2)
end
end
end
diff --git a/spec/requests/api/group_container_repositories_spec.rb b/spec/requests/api/group_container_repositories_spec.rb
index 9f439bb2167..3128becae6d 100644
--- a/spec/requests/api/group_container_repositories_spec.rb
+++ b/spec/requests/api/group_container_repositories_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupContainerRepositories do
+RSpec.describe API::GroupContainerRepositories do
let_it_be(:group) { create(:group, :private) }
let_it_be(:project) { create(:project, :private, group: group) }
let_it_be(:reporter) { create(:user) }
diff --git a/spec/requests/api/group_export_spec.rb b/spec/requests/api/group_export_spec.rb
index 9dd7797c768..50a1e9d0c3d 100644
--- a/spec/requests/api/group_export_spec.rb
+++ b/spec/requests/api/group_export_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupExport do
+RSpec.describe API::GroupExport do
let_it_be(:group) { create(:group) }
let_it_be(:user) { create(:user) }
@@ -33,6 +33,10 @@ describe API::GroupExport do
context 'group_import_export feature flag enabled' do
before do
stub_feature_flags(group_import_export: true)
+
+ allow(Gitlab::ApplicationRateLimiter)
+ .to receive(:increment)
+ .and_return(0)
end
context 'when export file exists' do
@@ -87,7 +91,7 @@ describe API::GroupExport do
before do
allow(Gitlab::ApplicationRateLimiter)
.to receive(:increment)
- .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_download_export][:threshold] + 1)
+ .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_download_export][:threshold].call + 1)
end
it 'throttles the endpoint' do
@@ -162,7 +166,7 @@ describe API::GroupExport do
allow(Gitlab::ApplicationRateLimiter)
.to receive(:increment)
- .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_export][:threshold] + 1)
+ .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_export][:threshold].call + 1)
end
it 'throttles the endpoint' do
diff --git a/spec/requests/api/group_import_spec.rb b/spec/requests/api/group_import_spec.rb
index b60a1b3f119..ad67f737725 100644
--- a/spec/requests/api/group_import_spec.rb
+++ b/spec/requests/api/group_import_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupImport do
+RSpec.describe API::GroupImport do
include WorkhorseHelpers
let_it_be(:user) { create(:user) }
@@ -122,6 +122,7 @@ describe API::GroupImport do
before do
allow_next_instance_of(Group) do |group|
allow(group).to receive(:persisted?).and_return(false)
+ allow(group).to receive(:save).and_return(false)
end
end
diff --git a/spec/requests/api/group_labels_spec.rb b/spec/requests/api/group_labels_spec.rb
index 715c1255cb3..f965a845bbe 100644
--- a/spec/requests/api/group_labels_spec.rb
+++ b/spec/requests/api/group_labels_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupLabels do
+RSpec.describe API::GroupLabels do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
diff --git a/spec/requests/api/group_milestones_spec.rb b/spec/requests/api/group_milestones_spec.rb
index 3e9b6246434..2b361f2b503 100644
--- a/spec/requests/api/group_milestones_spec.rb
+++ b/spec/requests/api/group_milestones_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupMilestones do
+RSpec.describe API::GroupMilestones do
let(:user) { create(:user) }
let(:group) { create(:group, :private) }
let(:project) { create(:project, namespace: group) }
diff --git a/spec/requests/api/group_packages_spec.rb b/spec/requests/api/group_packages_spec.rb
new file mode 100644
index 00000000000..7c7e8da3fb1
--- /dev/null
+++ b/spec/requests/api/group_packages_spec.rb
@@ -0,0 +1,147 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::GroupPackages do
+ let_it_be(:group) { create(:group, :public) }
+ let_it_be(:project) { create(:project, :public, namespace: group, name: 'project A') }
+ let_it_be(:user) { create(:user) }
+
+ subject { get api(url) }
+
+ describe 'GET /groups/:id/packages' do
+ let(:url) { "/groups/#{group.id}/packages" }
+ let(:package_schema) { 'public_api/v4/packages/group_packages' }
+
+ context 'without the need for a license' do
+ context 'with sorting' do
+ let_it_be(:package1) { create(:npm_package, project: project, version: '3.1.0', name: "@#{project.root_namespace.path}/foo1") }
+ let_it_be(:package2) { create(:nuget_package, project: project, version: '2.0.4') }
+ let(:package3) { create(:maven_package, project: project, version: '1.1.1', name: 'zzz') }
+
+ before do
+ travel_to(1.day.ago) do
+ package3
+ end
+ end
+
+ context 'without sorting params' do
+ let(:packages) { [package3, package1, package2] }
+
+ it 'sorts by created_at asc' do
+ subject
+
+ expect(json_response.map { |package| package['id'] }).to eq(packages.map(&:id))
+ end
+ end
+
+ it_behaves_like 'package sorting', 'name' do
+ let(:packages) { [package1, package2, package3] }
+ end
+
+ it_behaves_like 'package sorting', 'created_at' do
+ let(:packages) { [package3, package1, package2] }
+ end
+
+ it_behaves_like 'package sorting', 'version' do
+ let(:packages) { [package3, package2, package1] }
+ end
+
+ it_behaves_like 'package sorting', 'type' do
+ let(:packages) { [package3, package1, package2] }
+ end
+
+ it_behaves_like 'package sorting', 'project_path' do
+ let(:another_project) { create(:project, :public, namespace: group, name: 'project B') }
+ let!(:package4) { create(:npm_package, project: another_project, version: '3.1.0', name: "@#{project.root_namespace.path}/bar") }
+
+ let(:packages) { [package1, package2, package3, package4] }
+ end
+ end
+
+ context 'with private group' do
+ let!(:package1) { create(:package, project: project) }
+ let!(:package2) { create(:package, project: project) }
+
+ let(:group) { create(:group, :private) }
+ let(:subgroup) { create(:group, :private, parent: group) }
+ let(:project) { create(:project, :private, namespace: group) }
+ let(:subproject) { create(:project, :private, namespace: subgroup) }
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'rejects packages access', :group, :no_type, :not_found
+ end
+
+ context 'with authenticated user' do
+ subject { get api(url, user) }
+
+ it_behaves_like 'returns packages', :group, :owner
+ it_behaves_like 'returns packages', :group, :maintainer
+ it_behaves_like 'returns packages', :group, :developer
+ it_behaves_like 'rejects packages access', :group, :reporter, :forbidden
+ it_behaves_like 'rejects packages access', :group, :guest, :forbidden
+
+ context 'with subgroup' do
+ let(:subgroup) { create(:group, :private, parent: group) }
+ let(:subproject) { create(:project, :private, namespace: subgroup) }
+ let!(:package3) { create(:npm_package, project: subproject) }
+
+ it_behaves_like 'returns packages with subgroups', :group, :owner
+ it_behaves_like 'returns packages with subgroups', :group, :maintainer
+ it_behaves_like 'returns packages with subgroups', :group, :developer
+ it_behaves_like 'rejects packages access', :group, :reporter, :forbidden
+ it_behaves_like 'rejects packages access', :group, :guest, :forbidden
+
+ context 'excluding subgroup' do
+ let(:url) { "/groups/#{group.id}/packages?exclude_subgroups=true" }
+
+ it_behaves_like 'returns packages', :group, :owner
+ it_behaves_like 'returns packages', :group, :maintainer
+ it_behaves_like 'returns packages', :group, :developer
+ it_behaves_like 'rejects packages access', :group, :reporter, :forbidden
+ it_behaves_like 'rejects packages access', :group, :guest, :forbidden
+ end
+ end
+ end
+ end
+
+ context 'with public group' do
+ let_it_be(:package1) { create(:package, project: project) }
+ let_it_be(:package2) { create(:package, project: project) }
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'returns packages', :group, :no_type
+ end
+
+ context 'with authenticated user' do
+ subject { get api(url, user) }
+
+ it_behaves_like 'returns packages', :group, :owner
+ it_behaves_like 'returns packages', :group, :maintainer
+ it_behaves_like 'returns packages', :group, :developer
+ it_behaves_like 'returns packages', :group, :reporter
+ it_behaves_like 'returns packages', :group, :guest
+ end
+ end
+
+ context 'with pagination params' do
+ let_it_be(:package1) { create(:package, project: project) }
+ let_it_be(:package2) { create(:package, project: project) }
+ let_it_be(:package3) { create(:npm_package, project: project) }
+ let_it_be(:package4) { create(:npm_package, project: project) }
+
+ it_behaves_like 'returns paginated packages'
+ end
+
+ it_behaves_like 'filters on each package_type', is_project: false
+
+ context 'does not accept non supported package_type value' do
+ include_context 'package filter context'
+
+ let(:url) { group_filter_url(:type, 'foo') }
+
+ it_behaves_like 'returning response status', :bad_request
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/group_variables_spec.rb b/spec/requests/api/group_variables_spec.rb
index a5b48985df5..c6d6ae1615b 100644
--- a/spec/requests/api/group_variables_spec.rb
+++ b/spec/requests/api/group_variables_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupVariables do
+RSpec.describe API::GroupVariables do
let(:group) { create(:group) }
let(:user) { create(:user) }
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 9a449499576..fac9f4dfe00 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Groups do
+RSpec.describe API::Groups do
include GroupAPIHelpers
include UploadHelpers
@@ -15,6 +15,7 @@ describe API::Groups do
let_it_be(:project1) { create(:project, namespace: group1) }
let_it_be(:project2) { create(:project, namespace: group2) }
let_it_be(:project3) { create(:project, namespace: group1, path: 'test', visibility_level: Gitlab::VisibilityLevel::PRIVATE) }
+ let_it_be(:archived_project) { create(:project, namespace: group1, archived: true) }
before do
group1.add_owner(user1)
@@ -184,11 +185,12 @@ describe API::Groups do
it "includes statistics if requested" do
attributes = {
- storage_size: 1158,
+ storage_size: 2392,
repository_size: 123,
wiki_size: 456,
lfs_objects_size: 234,
- build_artifacts_size: 345
+ build_artifacts_size: 345,
+ snippets_size: 1234
}.stringify_keys
exposed_attributes = attributes.dup
exposed_attributes['job_artifacts_size'] = exposed_attributes.delete('build_artifacts_size')
@@ -470,7 +472,7 @@ describe API::Groups do
expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
expect(json_response['shared_with_groups'][0]).to have_key('expires_at')
expect(json_response['projects']).to be_an Array
- expect(json_response['projects'].length).to eq(2)
+ expect(json_response['projects'].length).to eq(3)
expect(json_response['shared_projects']).to be_an Array
expect(json_response['shared_projects'].length).to eq(1)
expect(json_response['shared_projects'][0]['id']).to eq(project.id)
@@ -695,7 +697,7 @@ describe API::Groups do
expect(json_response['parent_id']).to eq(nil)
expect(json_response['created_at']).to be_present
expect(json_response['projects']).to be_an Array
- expect(json_response['projects'].length).to eq(2)
+ expect(json_response['projects'].length).to eq(3)
expect(json_response['shared_projects']).to be_an Array
expect(json_response['shared_projects'].length).to eq(0)
expect(json_response['default_branch_protection']).to eq(::Gitlab::Access::MAINTAINER_PROJECT_ACCESS)
@@ -821,20 +823,51 @@ describe API::Groups do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
- expect(json_response.length).to eq(2)
+ expect(json_response.length).to eq(3)
project_names = json_response.map { |proj| proj['name'] }
- expect(project_names).to match_array([project1.name, project3.name])
+ expect(project_names).to match_array([project1.name, project3.name, archived_project.name])
expect(json_response.first['visibility']).to be_present
end
+ context 'and using archived' do
+ it "returns the group's archived projects" do
+ get api("/groups/#{group1.id}/projects?archived=true", user1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(Project.public_or_visible_to_user(user1).where(archived: true).size)
+ expect(json_response.map { |project| project['id'] }).to include(archived_project.id)
+ end
+
+ it "returns the group's non-archived projects" do
+ get api("/groups/#{group1.id}/projects?archived=false", user1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(Project.public_or_visible_to_user(user1).where(archived: false).size)
+ expect(json_response.map { |project| project['id'] }).not_to include(archived_project.id)
+ end
+
+ it "returns all of the group's projects" do
+ get api("/groups/#{group1.id}/projects", user1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |project| project['id'] }).to contain_exactly(*Project.public_or_visible_to_user(user1).pluck(:id))
+ end
+ end
+
it "returns the group's projects with simple representation" do
get api("/groups/#{group1.id}/projects", user1), params: { simple: true }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
- expect(json_response.length).to eq(2)
+ expect(json_response.length).to eq(3)
project_names = json_response.map { |proj| proj['name'] }
- expect(project_names).to match_array([project1.name, project3.name])
+ expect(project_names).to match_array([project1.name, project3.name, archived_project.name])
expect(json_response.first['visibility']).not_to be_present
end
@@ -860,7 +893,7 @@ describe API::Groups do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an(Array)
- expect(json_response.length).to eq(2)
+ expect(json_response.length).to eq(3)
end
it "returns projects including those in subgroups" do
@@ -873,7 +906,7 @@ describe API::Groups do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an(Array)
- expect(json_response.length).to eq(4)
+ expect(json_response.length).to eq(5)
end
it "does not return a non existing group" do
@@ -958,7 +991,7 @@ describe API::Groups do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
project_names = json_response.map { |proj| proj['name'] }
- expect(project_names).to match_array([project1.name, project3.name])
+ expect(project_names).to match_array([project1.name, project3.name, archived_project.name])
end
it 'does not return a non existing group' do
diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb
index d65c89f48ea..12cd5ace84e 100644
--- a/spec/requests/api/helpers_spec.rb
+++ b/spec/requests/api/helpers_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require 'raven/transports/dummy'
require_relative '../../../config/initializers/sentry'
-describe API::Helpers do
+RSpec.describe API::Helpers do
include API::APIGuard::HelperMethods
include described_class
include TermsHelper
diff --git a/spec/requests/api/import_bitbucket_server_spec.rb b/spec/requests/api/import_bitbucket_server_spec.rb
new file mode 100644
index 00000000000..5828dab3080
--- /dev/null
+++ b/spec/requests/api/import_bitbucket_server_spec.rb
@@ -0,0 +1,218 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::ImportBitbucketServer do
+ let(:base_uri) { "https://test:7990" }
+ let(:user) { create(:user) }
+ let(:token) { "asdasd12345" }
+ let(:secret) { "sekrettt" }
+ let(:project_key) { 'TES' }
+ let(:repo_slug) { 'vim' }
+ let(:repo) { { name: 'vim' } }
+
+ describe "POST /import/bitbucket_server" do
+ context 'with no optional parameters' do
+ let_it_be(:project) { create(:project) }
+ let(:client) { double(BitbucketServer::Client) }
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:client).and_return(client.as_null_object)
+ allow(client).to receive(:repo).with(project_key, repo_slug).and_return(double(name: repo_slug))
+ end
+ end
+
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
+ it 'returns 201 response when the project is imported successfully' do
+ allow(Gitlab::BitbucketServerImport::ProjectCreator)
+ .to receive(:new).with(project_key, repo_slug, anything, repo_slug, user.namespace, user, anything)
+ .and_return(double(execute: project))
+
+ post api("/import/bitbucket_server", user), params: {
+ bitbucket_server_url: base_uri,
+ bitbucket_server_username: user,
+ personal_access_token: token,
+ bitbucket_server_project: project_key,
+ bitbucket_server_repo: repo_slug
+ }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to be_a Hash
+ expect(json_response['name']).to eq(project.name)
+ end
+ end
+
+ context 'with a new project name' do
+ let_it_be(:project) { create(:project, name: 'new-name') }
+ let(:client) { instance_double(BitbucketServer::Client) }
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:client).and_return(client)
+ allow(client).to receive(:repo).with(project_key, repo_slug).and_return(double(name: repo_slug))
+ end
+ end
+
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
+ it 'returns 201 response when the project is imported successfully with a new project name' do
+ allow(Gitlab::BitbucketServerImport::ProjectCreator)
+ .to receive(:new).with(project_key, repo_slug, anything, project.name, user.namespace, user, anything)
+ .and_return(double(execute: project))
+
+ post api("/import/bitbucket_server", user), params: {
+ bitbucket_server_url: base_uri,
+ bitbucket_server_username: user,
+ personal_access_token: token,
+ bitbucket_server_project: project_key,
+ bitbucket_server_repo: repo_slug,
+ new_name: 'new-name'
+ }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to be_a Hash
+ expect(json_response['name']).to eq('new-name')
+ end
+ end
+
+ context 'with an invalid URL' do
+ let_it_be(:project) { create(:project, name: 'new-name') }
+ let(:client) { instance_double(BitbucketServer::Client) }
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:client).and_return(client)
+ allow(client).to receive(:repo).with(project_key, repo_slug).and_return(double(name: repo_slug))
+ end
+ end
+
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
+ it 'returns 400 response due to a blcoked URL' do
+ allow(Gitlab::BitbucketServerImport::ProjectCreator)
+ .to receive(:new).with(project_key, repo_slug, anything, project.name, user.namespace, user, anything)
+ .and_return(double(execute: project))
+
+ allow(Gitlab::UrlBlocker)
+ .to receive(:blocked_url?)
+ .and_return(true)
+ post api("/import/bitbucket_server", user), params: {
+ bitbucket_server_url: base_uri,
+ bitbucket_server_username: user,
+ personal_access_token: token,
+ bitbucket_server_project: project_key,
+ bitbucket_server_repo: repo_slug,
+ new_name: 'new-name'
+ }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'with a new namespace' do
+ let(:bitbucket_client) { instance_double(BitbucketServer::Client) }
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:client).and_return(bitbucket_client)
+ repo = double(name: repo_slug, full_path: "/other-namespace/#{repo_slug}")
+ allow(bitbucket_client).to receive(:repo).with(project_key, repo_slug).and_return(repo)
+ end
+ end
+
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
+ it 'returns 201 response when the project is imported successfully to a new namespace' do
+ allow(Gitlab::BitbucketServerImport::ProjectCreator)
+ .to receive(:new).with(project_key, repo_slug, anything, repo_slug, an_instance_of(Group), user, anything)
+ .and_return(double(execute: create(:project, name: repo_slug)))
+
+ post api("/import/bitbucket_server", user), params: {
+ bitbucket_server_url: base_uri,
+ bitbucket_server_username: user,
+ personal_access_token: token,
+ bitbucket_server_project: project_key,
+ bitbucket_server_repo: repo_slug,
+ new_namespace: 'new-namespace'
+ }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to be_a Hash
+ expect(json_response['full_path']).not_to eq("/#{user.namespace}/#{repo_slug}")
+ end
+ end
+
+ context 'with a private inaccessible namespace' do
+ let(:bitbucket_client) { instance_double(BitbucketServer::Client) }
+ let(:project) { create(:project, import_type: 'bitbucket', creator_id: user.id, import_source: 'asd/vim', namespace: 'private-group/vim') }
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:client).and_return(bitbucket_client)
+ repo = double(name: repo_slug, full_path: "/private-group/#{repo_slug}")
+ allow(bitbucket_client).to receive(:repo).with(project_key, repo_slug).and_return(repo)
+ end
+ end
+
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
+ it 'returns 401 response when user can not create projects in the chosen namespace' do
+ allow(Gitlab::BitbucketServerImport::ProjectCreator)
+ .to receive(:new).with(project_key, repo_slug, anything, repo_slug, an_instance_of(Group), user, anything)
+ .and_return(double(execute: build(:project)))
+
+ other_namespace = create(:group, :private, name: 'private-group')
+
+ post api("/import/bitbucket_server", user), params: {
+ bitbucket_server_url: base_uri,
+ bitbucket_server_username: user,
+ personal_access_token: token,
+ bitbucket_server_project: project_key,
+ bitbucket_server_repo: repo_slug,
+ new_namespace: other_namespace.name
+ }
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ context 'with an inaccessible bitbucket server instance' do
+ let(:bitbucket_client) { instance_double(BitbucketServer::Client) }
+ let(:project) { create(:project, import_type: 'bitbucket', creator_id: user.id, import_source: 'asd/vim', namespace: 'private-group/vim') }
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:client).and_return(bitbucket_client)
+ allow(bitbucket_client).to receive(:repo).with(project_key, repo_slug).and_raise(::BitbucketServer::Connection::ConnectionError)
+ end
+ end
+
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
+ it 'raises a connection error' do
+ post api("/import/bitbucket_server", user), params: {
+ bitbucket_server_url: base_uri,
+ bitbucket_server_username: user,
+ personal_access_token: token,
+ bitbucket_server_project: project_key,
+ bitbucket_server_repo: repo_slug,
+ new_namespace: 'new-namespace'
+ }
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/import_github_spec.rb b/spec/requests/api/import_github_spec.rb
index f33436df40e..f026314f7a8 100644
--- a/spec/requests/api/import_github_spec.rb
+++ b/spec/requests/api/import_github_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ImportGithub do
+RSpec.describe API::ImportGithub do
let(:token) { "asdasd12345" }
let(:provider) { :github }
let(:access_params) { { github_access_token: token } }
@@ -26,6 +26,10 @@ describe API::ImportGithub do
end
end
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
it 'rejects requests when Github Importer is disabled' do
stub_application_setting(import_sources: nil)
diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb
index aa5e2367a2b..7d219954e9d 100644
--- a/spec/requests/api/internal/base_spec.rb
+++ b/spec/requests/api/internal/base_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Internal::Base do
+RSpec.describe API::Internal::Base do
let_it_be(:user, reload: true) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :repository, :wiki_repo) }
let_it_be(:personal_snippet) { create(:personal_snippet, :repository, author: user) }
@@ -467,21 +467,6 @@ describe API::Internal::Base do
expect(json_response["git_config_options"]).to include("uploadpack.allowFilter=true")
expect(json_response["git_config_options"]).to include("uploadpack.allowAnySHA1InWant=true")
end
-
- context 'when gitaly_upload_pack_filter feature flag is disabled' do
- before do
- stub_feature_flags(gitaly_upload_pack_filter: false)
- end
-
- it 'returns only maxInputSize and not partial clone git config' do
- push(key, project)
-
- expect(json_response["git_config_options"]).to be_present
- expect(json_response["git_config_options"]).to include("receive.maxInputSize=1048576")
- expect(json_response["git_config_options"]).not_to include("uploadpack.allowFilter=true")
- expect(json_response["git_config_options"]).not_to include("uploadpack.allowAnySHA1InWant=true")
- end
- end
end
context 'when receive_max_input_size is empty' do
@@ -496,18 +481,6 @@ describe API::Internal::Base do
expect(json_response["git_config_options"]).to include("uploadpack.allowFilter=true")
expect(json_response["git_config_options"]).to include("uploadpack.allowAnySHA1InWant=true")
end
-
- context 'when gitaly_upload_pack_filter feature flag is disabled' do
- before do
- stub_feature_flags(gitaly_upload_pack_filter: false)
- end
-
- it 'returns an empty git config' do
- push(key, project)
-
- expect(json_response["git_config_options"]).to be_empty
- end
- end
end
end
diff --git a/spec/requests/api/internal/pages_spec.rb b/spec/requests/api/internal/pages_spec.rb
index fecf15c29c2..48fc95b6574 100644
--- a/spec/requests/api/internal/pages_spec.rb
+++ b/spec/requests/api/internal/pages_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Internal::Pages do
+RSpec.describe API::Internal::Pages do
let(:auth_headers) do
jwt_token = JWT.encode({ 'iss' => 'gitlab-pages' }, Gitlab::Pages.secret, 'HS256')
{ Gitlab::Pages::INTERNAL_API_REQUEST_HEADER => jwt_token }
diff --git a/spec/requests/api/issues/get_group_issues_spec.rb b/spec/requests/api/issues/get_group_issues_spec.rb
index 5c925d2a32e..b53fac3679d 100644
--- a/spec/requests/api/issues/get_group_issues_spec.rb
+++ b/spec/requests/api/issues/get_group_issues_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Issues do
+RSpec.describe API::Issues do
let_it_be(:user2) { create(:user) }
let_it_be(:admin) { create(:user, :admin) }
let_it_be(:non_member) { create(:user) }
diff --git a/spec/requests/api/issues/get_project_issues_spec.rb b/spec/requests/api/issues/get_project_issues_spec.rb
index 4a728c81215..7ff07bf580d 100644
--- a/spec/requests/api/issues/get_project_issues_spec.rb
+++ b/spec/requests/api/issues/get_project_issues_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Issues do
+RSpec.describe API::Issues do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :public, :repository, creator_id: user.id, namespace: user.namespace) }
let_it_be(:private_mrs_project) do
diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb
index 315396c89c3..519bea22501 100644
--- a/spec/requests/api/issues/issues_spec.rb
+++ b/spec/requests/api/issues/issues_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Issues do
+RSpec.describe API::Issues do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :public, :repository, creator_id: user.id, namespace: user.namespace) }
let_it_be(:private_mrs_project) do
@@ -886,4 +886,53 @@ describe API::Issues do
include_examples 'time tracking endpoints', 'issue'
end
+
+ describe 'PUT /projects/:id/issues/:issue_iid/reorder' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:issue1) { create(:issue, project: project, relative_position: 10) }
+ let_it_be(:issue2) { create(:issue, project: project, relative_position: 20) }
+ let_it_be(:issue3) { create(:issue, project: project, relative_position: 30) }
+
+ context 'when user has access' do
+ before do
+ project.add_developer(user)
+ end
+
+ context 'with valid params' do
+ it 'reorders issues and returns a successful 200 response' do
+ put api("/projects/#{project.id}/issues/#{issue1.iid}/reorder", user), params: { move_after_id: issue2.id, move_before_id: issue3.id }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(issue1.reload.relative_position)
+ .to be_between(issue2.reload.relative_position, issue3.reload.relative_position)
+ end
+ end
+
+ context 'with invalid params' do
+ it 'returns a unprocessable entity 422 response for invalid move ids' do
+ put api("/projects/#{project.id}/issues/#{issue1.iid}/reorder", user), params: { move_after_id: issue2.id, move_before_id: non_existing_record_id }
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+
+ it 'returns a not found 404 response for invalid issue id' do
+ put api("/projects/#{project.id}/issues/#{non_existing_record_iid}/reorder", user), params: { move_after_id: issue2.id, move_before_id: issue3.id }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'with unauthorized user' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'responds with 403 forbidden' do
+ put api("/projects/#{project.id}/issues/#{issue1.iid}/reorder", user), params: { move_after_id: issue2.id, move_before_id: issue3.id }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/issues/post_projects_issues_spec.rb b/spec/requests/api/issues/post_projects_issues_spec.rb
index 2e1e5d3204e..e2f1bb2cd1a 100644
--- a/spec/requests/api/issues/post_projects_issues_spec.rb
+++ b/spec/requests/api/issues/post_projects_issues_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Issues do
+RSpec.describe API::Issues do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) do
create(:project, :public, creator_id: user.id, namespace: user.namespace)
diff --git a/spec/requests/api/issues/put_projects_issues_spec.rb b/spec/requests/api/issues/put_projects_issues_spec.rb
index 62a4d3b48b2..dac721cbea0 100644
--- a/spec/requests/api/issues/put_projects_issues_spec.rb
+++ b/spec/requests/api/issues/put_projects_issues_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Issues do
+RSpec.describe API::Issues do
let_it_be(:user) { create(:user) }
let_it_be(:owner) { create(:owner) }
let(:user2) { create(:user) }
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
index 18b5c00d64f..53c57931d36 100644
--- a/spec/requests/api/jobs_spec.rb
+++ b/spec/requests/api/jobs_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Jobs do
+RSpec.describe API::Jobs do
include HttpIOHelpers
shared_examples 'a job with artifacts and trace' do |result_is_array: true|
@@ -36,9 +36,9 @@ describe API::Jobs do
end
let_it_be(:pipeline, reload: true) do
- create(:ci_empty_pipeline, project: project,
- sha: project.commit.id,
- ref: project.default_branch)
+ create(:ci_pipeline, project: project,
+ sha: project.commit.id,
+ ref: project.default_branch)
end
let!(:job) do
diff --git a/spec/requests/api/keys_spec.rb b/spec/requests/api/keys_spec.rb
index 089ee22982c..49b8f4a8520 100644
--- a/spec/requests/api/keys_spec.rb
+++ b/spec/requests/api/keys_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Keys do
+RSpec.describe API::Keys do
let(:user) { create(:user) }
let(:admin) { create(:admin) }
let(:key) { create(:key, user: user, expires_at: 1.day.from_now) }
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index 697f22e5f29..fc674fca9b2 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Labels do
+RSpec.describe API::Labels do
def put_labels_api(route_type, user, spec_params, request_params = {})
if route_type == :deprecated
put api("/projects/#{project.id}/labels", user),
diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb
index 71c2619d898..4c60c8bd2a3 100644
--- a/spec/requests/api/lint_spec.rb
+++ b/spec/requests/api/lint_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Lint do
+RSpec.describe API::Lint do
describe 'POST /ci/lint' do
context 'with valid .gitlab-ci.yaml content' do
let(:yaml_content) do
diff --git a/spec/requests/api/markdown_spec.rb b/spec/requests/api/markdown_spec.rb
index 53e43430b1f..35d91963ac9 100644
--- a/spec/requests/api/markdown_spec.rb
+++ b/spec/requests/api/markdown_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe API::Markdown do
+RSpec.describe API::Markdown do
describe "POST /markdown" do
let(:user) {} # No-op. It gets overwritten in the contexts below.
diff --git a/spec/requests/api/maven_packages_spec.rb b/spec/requests/api/maven_packages_spec.rb
new file mode 100644
index 00000000000..189d6a4c1a4
--- /dev/null
+++ b/spec/requests/api/maven_packages_spec.rb
@@ -0,0 +1,569 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe API::MavenPackages do
+ include WorkhorseHelpers
+
+ let_it_be(:group) { create(:group) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project, :public, namespace: group) }
+ let_it_be(:package, reload: true) { create(:maven_package, project: project, name: project.full_path) }
+ let_it_be(:maven_metadatum, reload: true) { package.maven_metadatum }
+ let_it_be(:package_file) { package.package_files.with_file_name_like('%.xml').first }
+ let_it_be(:jar_file) { package.package_files.with_file_name_like('%.jar').first }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:job) { create(:ci_build, user: user) }
+ let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
+ let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
+
+ let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let(:headers) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let(:headers_with_token) { headers.merge('Private-Token' => personal_access_token.token) }
+
+ let(:headers_with_deploy_token) do
+ headers.merge(
+ Gitlab::Auth::AuthFinders::DEPLOY_TOKEN_HEADER => deploy_token.token
+ )
+ end
+
+ let(:version) { '1.0-SNAPSHOT' }
+
+ before do
+ project.add_developer(user)
+ end
+
+ shared_examples 'tracking the file download event' do
+ context 'with jar file' do
+ let_it_be(:package_file) { jar_file }
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'pull_package'
+ end
+ end
+
+ shared_examples 'processing HEAD requests' do
+ subject { head api(url) }
+
+ before do
+ allow_any_instance_of(::Packages::PackageFileUploader).to receive(:fog_credentials).and_return(object_storage_credentials)
+ stub_package_file_object_storage(enabled: object_storage_enabled)
+ end
+
+ context 'with object storage enabled' do
+ let(:object_storage_enabled) { true }
+
+ before do
+ allow_any_instance_of(::Packages::PackageFileUploader).to receive(:file_storage?).and_return(false)
+ end
+
+ context 'non AWS provider' do
+ let(:object_storage_credentials) { { provider: 'Google' } }
+
+ it 'does not generated a signed url for head' do
+ expect_any_instance_of(Fog::AWS::Storage::Files).not_to receive(:head_url)
+
+ subject
+ end
+ end
+
+ context 'with AWS provider' do
+ let(:object_storage_credentials) { { provider: 'AWS', aws_access_key_id: 'test', aws_secret_access_key: 'test' } }
+
+ it 'generates a signed url for head' do
+ expect_any_instance_of(Fog::AWS::Storage::Files).to receive(:head_url).and_call_original
+
+ subject
+ end
+ end
+ end
+
+ context 'with object storage disabled' do
+ let(:object_storage_enabled) { false }
+ let(:object_storage_credentials) { {} }
+
+ it 'does not generate a signed url for head' do
+ expect_any_instance_of(Fog::AWS::Storage::Files).not_to receive(:head_url)
+
+ subject
+ end
+ end
+ end
+
+ shared_examples 'downloads with a deploy token' do
+ it 'allows download with deploy token' do
+ download_file(
+ package_file.file_name,
+ {},
+ Gitlab::Auth::AuthFinders::DEPLOY_TOKEN_HEADER => deploy_token.token
+ )
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+ end
+
+ shared_examples 'downloads with a job token' do
+ it 'allows download with job token' do
+ download_file(package_file.file_name, job_token: job.token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+ end
+
+ describe 'GET /api/v4/packages/maven/*path/:file_name' do
+ context 'a public project' do
+ subject { download_file(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'returns sha1 of the file' do
+ download_file(package_file.file_name + '.sha1')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('text/plain')
+ expect(response.body).to eq(package_file.file_sha1)
+ end
+ end
+
+ context 'internal project' do
+ before do
+ project.team.truncate
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ subject { download_file_with_token(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download when no private token' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it_behaves_like 'downloads with a job token'
+
+ it_behaves_like 'downloads with a deploy token'
+ end
+
+ context 'private project' do
+ subject { download_file_with_token(package_file.file_name) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download when not enough permissions' do
+ project.add_guest(user)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'denies download when no private token' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it_behaves_like 'downloads with a job token'
+
+ it_behaves_like 'downloads with a deploy token'
+ end
+
+ context 'project name is different from a package name' do
+ before do
+ maven_metadatum.update!(path: "wrong_name/#{package.version}")
+ end
+
+ it 'rejects request' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ def download_file(file_name, params = {}, request_headers = headers)
+ get api("/packages/maven/#{maven_metadatum.path}/#{file_name}"), params: params, headers: request_headers
+ end
+
+ def download_file_with_token(file_name, params = {}, request_headers = headers_with_token)
+ download_file(file_name, params, request_headers)
+ end
+ end
+
+ describe 'HEAD /api/v4/packages/maven/*path/:file_name' do
+ let(:url) { "/packages/maven/#{package.maven_metadatum.path}/#{package_file.file_name}" }
+
+ it_behaves_like 'processing HEAD requests'
+ end
+
+ describe 'GET /api/v4/groups/:id/-/packages/maven/*path/:file_name' do
+ before do
+ project.team.truncate
+ group.add_developer(user)
+ end
+
+ context 'a public project' do
+ subject { download_file(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'returns sha1 of the file' do
+ download_file(package_file.file_name + '.sha1')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('text/plain')
+ expect(response.body).to eq(package_file.file_sha1)
+ end
+ end
+
+ context 'internal project' do
+ before do
+ group.group_member(user).destroy
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ subject { download_file_with_token(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download when no private token' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it_behaves_like 'downloads with a job token'
+
+ it_behaves_like 'downloads with a deploy token'
+ end
+
+ context 'private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ subject { download_file_with_token(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download when not enough permissions' do
+ group.add_guest(user)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'denies download when no private token' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it_behaves_like 'downloads with a job token'
+
+ it_behaves_like 'downloads with a deploy token'
+ end
+
+ def download_file(file_name, params = {}, request_headers = headers)
+ get api("/groups/#{group.id}/-/packages/maven/#{maven_metadatum.path}/#{file_name}"), params: params, headers: request_headers
+ end
+
+ def download_file_with_token(file_name, params = {}, request_headers = headers_with_token)
+ download_file(file_name, params, request_headers)
+ end
+ end
+
+ describe 'HEAD /api/v4/groups/:id/-/packages/maven/*path/:file_name' do
+ let(:url) { "/groups/#{group.id}/-/packages/maven/#{package.maven_metadatum.path}/#{package_file.file_name}" }
+
+ it_behaves_like 'processing HEAD requests'
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/maven/*path/:file_name' do
+ context 'a public project' do
+ subject { download_file(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'returns sha1 of the file' do
+ download_file(package_file.file_name + '.sha1')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('text/plain')
+ expect(response.body).to eq(package_file.file_sha1)
+ end
+ end
+
+ context 'private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ subject { download_file_with_token(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download when not enough permissions' do
+ project.add_guest(user)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'denies download when no private token' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it_behaves_like 'downloads with a job token'
+
+ it_behaves_like 'downloads with a deploy token'
+ end
+
+ def download_file(file_name, params = {}, request_headers = headers)
+ get api("/projects/#{project.id}/packages/maven/" \
+ "#{maven_metadatum.path}/#{file_name}"), params: params, headers: request_headers
+ end
+
+ def download_file_with_token(file_name, params = {}, request_headers = headers_with_token)
+ download_file(file_name, params, request_headers)
+ end
+ end
+
+ describe 'HEAD /api/v4/projects/:id/packages/maven/*path/:file_name' do
+ let(:url) { "/projects/#{project.id}/packages/maven/#{package.maven_metadatum.path}/#{package_file.file_name}" }
+
+ it_behaves_like 'processing HEAD requests'
+ end
+
+ describe 'PUT /api/v4/projects/:id/packages/maven/*path/:file_name/authorize' do
+ it 'rejects a malicious request' do
+ put api("/projects/#{project.id}/packages/maven/com/example/my-app/#{version}/%2e%2e%2F.ssh%2Fauthorized_keys/authorize"), params: {}, headers: headers_with_token
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'authorizes posting package with a valid token' do
+ authorize_upload_with_token
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(json_response['TempPath']).not_to be_nil
+ end
+
+ it 'rejects request without a valid token' do
+ headers_with_token['Private-Token'] = 'foo'
+
+ authorize_upload_with_token
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'rejects request without a valid permission' do
+ project.add_guest(user)
+
+ authorize_upload_with_token
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'rejects requests that did not go through gitlab-workhorse' do
+ headers.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER)
+
+ authorize_upload_with_token
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'authorizes upload with job token' do
+ authorize_upload(job_token: job.token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'authorizes upload with deploy token' do
+ authorize_upload({}, headers_with_deploy_token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ def authorize_upload(params = {}, request_headers = headers)
+ put api("/projects/#{project.id}/packages/maven/com/example/my-app/#{version}/maven-metadata.xml/authorize"), params: params, headers: request_headers
+ end
+
+ def authorize_upload_with_token(params = {}, request_headers = headers_with_token)
+ authorize_upload(params, request_headers)
+ end
+ end
+
+ describe 'PUT /api/v4/projects/:id/packages/maven/*path/:file_name' do
+ let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let(:send_rewritten_field) { true }
+ let(:file_upload) { fixture_file_upload('spec/fixtures/packages/maven/my-app-1.0-20180724.124855-1.jar') }
+
+ before do
+ # by configuring this path we allow to pass temp file from any path
+ allow(Packages::PackageFileUploader).to receive(:workhorse_upload_path).and_return('/')
+ end
+
+ it 'rejects requests without a file from workhorse' do
+ upload_file_with_token
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'rejects request without a token' do
+ upload_file
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ context 'without workhorse rewritten field' do
+ let(:send_rewritten_field) { false }
+
+ it 'rejects the request' do
+ upload_file_with_token
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'when params from workhorse are correct' do
+ let(:params) { { file: file_upload } }
+
+ it 'rejects a malicious request' do
+ put api("/projects/#{project.id}/packages/maven/com/example/my-app/#{version}/%2e%2e%2f.ssh%2fauthorized_keys"), params: params, headers: headers_with_token
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ context 'without workhorse header' do
+ let(:workhorse_header) { {} }
+
+ subject { upload_file_with_token(params) }
+
+ it_behaves_like 'package workhorse uploads'
+ end
+
+ context 'event tracking' do
+ subject { upload_file_with_token(params) }
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'push_package'
+ end
+
+ it 'creates package and stores package file' do
+ expect { upload_file_with_token(params) }.to change { project.packages.count }.by(1)
+ .and change { Packages::Maven::Metadatum.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(jar_file.file_name).to eq(file_upload.original_filename)
+ end
+
+ it 'allows upload with job token' do
+ upload_file(params.merge(job_token: job.token))
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(project.reload.packages.last.build_info.pipeline).to eq job.pipeline
+ end
+
+ it 'allows upload with deploy token' do
+ upload_file(params, headers_with_deploy_token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ context 'version is not correct' do
+ let(:version) { '$%123' }
+
+ it 'rejects request' do
+ expect { upload_file_with_token(params) }.not_to change { project.packages.count }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to include('Validation failed')
+ end
+ end
+ end
+
+ def upload_file(params = {}, request_headers = headers)
+ url = "/projects/#{project.id}/packages/maven/com/example/my-app/#{version}/my-app-1.0-20180724.124855-1.jar"
+ workhorse_finalize(
+ api(url),
+ method: :put,
+ file_key: :file,
+ params: params,
+ headers: request_headers,
+ send_rewritten_field: send_rewritten_field
+ )
+ end
+
+ def upload_file_with_token(params = {}, request_headers = headers_with_token)
+ upload_file(params, request_headers)
+ end
+ end
+end
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 0ecef26c27a..23889912d7a 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Members do
+RSpec.describe API::Members do
let(:maintainer) { create(:user, username: 'maintainer_user') }
let(:developer) { create(:user) }
let(:access_requester) { create(:user) }
@@ -321,6 +321,26 @@ describe API::Members do
expect(response).to have_gitlab_http_status(:bad_request)
end
end
+
+ context 'adding project bot' do
+ let_it_be(:project_bot) { create(:user, :project_bot) }
+
+ before do
+ unrelated_project = create(:project)
+ unrelated_project.add_maintainer(project_bot)
+ end
+
+ it 'returns 400' do
+ expect do
+ post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
+ params: { user_id: project_bot.id, access_level: Member::DEVELOPER }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']['user_id']).to(
+ include('project bots cannot be added to other groups / projects'))
+ end.not_to change { project.members.count }
+ end
+ end
end
shared_examples 'PUT /:source_type/:id/members/:user_id' do |source_type|
@@ -461,8 +481,34 @@ describe API::Members do
end
end
- it_behaves_like 'POST /:source_type/:id/members', 'project' do
- let(:source) { project }
+ describe 'POST /projects/:id/members' do
+ it_behaves_like 'POST /:source_type/:id/members', 'project' do
+ let(:source) { project }
+ end
+
+ context 'adding owner to project' do
+ it 'returns 403' do
+ expect do
+ post api("/projects/#{project.id}/members", maintainer),
+ params: { user_id: stranger.id, access_level: Member::OWNER }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end.not_to change { project.members.count }
+ end
+ end
+
+ context 'remove bot from project' do
+ it 'returns a 403 forbidden' do
+ project_bot = create(:user, :project_bot)
+ create(:project_member, project: project, user: project_bot)
+
+ expect do
+ delete api("/projects/#{project.id}/members/#{project_bot.id}", maintainer)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end.not_to change { project.members.count }
+ end
+ end
end
it_behaves_like 'POST /:source_type/:id/members', 'group' do
@@ -484,15 +530,4 @@ describe API::Members do
it_behaves_like 'DELETE /:source_type/:id/members/:user_id', 'group' do
let(:source) { group }
end
-
- context 'Adding owner to project' do
- it 'returns 403' do
- expect do
- post api("/projects/#{project.id}/members", maintainer),
- params: { user_id: stranger.id, access_level: Member::OWNER }
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end.to change { project.members.count }.by(0)
- end
- end
end
diff --git a/spec/requests/api/merge_request_approvals_spec.rb b/spec/requests/api/merge_request_approvals_spec.rb
new file mode 100644
index 00000000000..fad5c3fb60e
--- /dev/null
+++ b/spec/requests/api/merge_request_approvals_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::MergeRequestApprovals do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace) }
+ let_it_be(:approver) { create :user }
+ let_it_be(:group) { create :group }
+
+ let(:merge_request) { create(:merge_request, :simple, author: user, source_project: project) }
+
+ describe 'GET :id/merge_requests/:merge_request_iid/approvals' do
+ it 'retrieves the approval status' do
+ project.add_developer(approver)
+ project.add_developer(create(:user))
+
+ create(:approval, user: approver, merge_request: merge_request)
+
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/approvals", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ describe 'POST :id/merge_requests/:merge_request_iid/approve' do
+ context 'as a valid approver' do
+ let_it_be(:approver) { create(:user) }
+
+ before do
+ project.add_developer(approver)
+ project.add_developer(create(:user))
+ end
+
+ def approve(extra_params = {})
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/approve", approver), params: extra_params
+ end
+
+ context 'when the sha param is not set' do
+ it 'approves the merge request' do
+ approve
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+ end
+
+ context 'when the sha param is correct' do
+ it 'approves the merge request' do
+ approve(sha: merge_request.diff_head_sha)
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+ end
+
+ context 'when the sha param is incorrect' do
+ it 'does not approve the merge request' do
+ approve(sha: merge_request.diff_head_sha.reverse)
+
+ expect(response).to have_gitlab_http_status(:conflict)
+ expect(merge_request.approvals).to be_empty
+ end
+ end
+ end
+ end
+
+ describe 'POST :id/merge_requests/:merge_request_iid/unapprove' do
+ context 'as a user who has approved the merge request' do
+ it 'unapproves the merge request' do
+ unapprover = create(:user)
+
+ project.add_developer(approver)
+ project.add_developer(unapprover)
+ project.add_developer(create(:user))
+
+ create(:approval, user: approver, merge_request: merge_request)
+ create(:approval, user: unapprover, merge_request: merge_request)
+
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unapprove", unapprover)
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/merge_request_diffs_spec.rb b/spec/requests/api/merge_request_diffs_spec.rb
index d00bc4a6dde..3f41a7a034d 100644
--- a/spec/requests/api/merge_request_diffs_spec.rb
+++ b/spec/requests/api/merge_request_diffs_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe API::MergeRequestDiffs, 'MergeRequestDiffs' do
+RSpec.describe API::MergeRequestDiffs, 'MergeRequestDiffs' do
let!(:user) { create(:user) }
let!(:merge_request) { create(:merge_request, importing: true) }
let!(:project) { merge_request.target_project }
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 7a0077f853a..68f1a0f1ba1 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe API::MergeRequests do
+RSpec.describe API::MergeRequests do
include ProjectForksHelper
let(:base_time) { Time.now }
@@ -425,6 +425,73 @@ describe API::MergeRequests do
end
end
+ context 'NOT params' do
+ let(:merge_request2) do
+ create(
+ :merge_request,
+ :simple,
+ milestone: milestone,
+ author: user,
+ assignees: [user],
+ merge_request_context_commits: [merge_request_context_commit],
+ source_project: project,
+ target_project: project,
+ source_branch: 'what',
+ title: "What",
+ created_at: base_time
+ )
+ end
+
+ before do
+ create(:label_link, label: label, target: merge_request)
+ create(:label_link, label: label2, target: merge_request2)
+ end
+
+ it 'returns merge requests without any of the labels given', :aggregate_failures do
+ get api(endpoint_path, user), params: { not: { labels: ["#{label.title}, #{label2.title}"] } }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(3)
+ json_response.each do |mr|
+ expect(mr['labels']).not_to include(label2.title, label.title)
+ end
+ end
+
+ it 'returns merge requests without any of the milestones given', :aggregate_failures do
+ get api(endpoint_path, user), params: { not: { milestone: milestone.title } }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(4)
+ json_response.each do |mr|
+ expect(mr['milestone']).not_to eq(milestone.title)
+ end
+ end
+
+ it 'returns merge requests without the author given', :aggregate_failures do
+ get api(endpoint_path, user), params: { not: { author_id: user2.id } }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(5)
+ json_response.each do |mr|
+ expect(mr['author']['id']).not_to eq(user2.id)
+ end
+ end
+
+ it 'returns merge requests without the assignee given', :aggregate_failures do
+ get api(endpoint_path, user), params: { not: { assignee_id: user2.id } }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(5)
+ json_response.each do |mr|
+ expect(mr['assignee']['id']).not_to eq(user2.id)
+ end
+ end
+ end
+
context 'source_branch param' do
it 'returns merge requests with the given source branch' do
get api(endpoint_path, user), params: { source_branch: merge_request_closed.source_branch, state: 'all' }
@@ -1930,7 +1997,7 @@ describe API::MergeRequests do
it "updates the MR's squash attribute" do
expect do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), params: { squash: true }
- end.to change { merge_request.reload.squash }
+ end.to change { merge_request.reload.squash_on_merge? }
expect(response).to have_gitlab_http_status(:ok)
end
diff --git a/spec/requests/api/metrics/dashboard/annotations_spec.rb b/spec/requests/api/metrics/dashboard/annotations_spec.rb
index 6377ef2435a..07de2925ee2 100644
--- a/spec/requests/api/metrics/dashboard/annotations_spec.rb
+++ b/spec/requests/api/metrics/dashboard/annotations_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Metrics::Dashboard::Annotations do
+RSpec.describe API::Metrics::Dashboard::Annotations do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :private, :repository, namespace: user.namespace) }
let_it_be(:environment) { create(:environment, project: project) }
diff --git a/spec/requests/api/metrics/user_starred_dashboards_spec.rb b/spec/requests/api/metrics/user_starred_dashboards_spec.rb
index 8f9394a0e20..533dff05f27 100644
--- a/spec/requests/api/metrics/user_starred_dashboards_spec.rb
+++ b/spec/requests/api/metrics/user_starred_dashboards_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Metrics::UserStarredDashboards do
+RSpec.describe API::Metrics::UserStarredDashboards do
let_it_be(:user) { create(:user) }
let_it_be(:dashboard_yml) { fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml') }
let_it_be(:dashboard) { '.gitlab/dashboards/find&seek.yml' }
diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb
index 68fffc638df..2ac76d469d5 100644
--- a/spec/requests/api/namespaces_spec.rb
+++ b/spec/requests/api/namespaces_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Namespaces do
+RSpec.describe API::Namespaces do
let(:admin) { create(:admin) }
let(:user) { create(:user) }
let!(:group1) { create(:group, name: 'group.one') }
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 797dd3bb4e2..1510d31a1a6 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Notes do
+RSpec.describe API::Notes do
let!(:user) { create(:user) }
let!(:project) { create(:project, :public) }
let(:private_user) { create(:user) }
diff --git a/spec/requests/api/notification_settings_spec.rb b/spec/requests/api/notification_settings_spec.rb
index 2dfde4c8ec9..73cb4948524 100644
--- a/spec/requests/api/notification_settings_spec.rb
+++ b/spec/requests/api/notification_settings_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::NotificationSettings do
+RSpec.describe API::NotificationSettings do
let(:user) { create(:user) }
let!(:group) { create(:group) }
let!(:project) { create(:project, :public, creator_id: user.id, namespace: group) }
diff --git a/spec/requests/api/npm_packages_spec.rb b/spec/requests/api/npm_packages_spec.rb
new file mode 100644
index 00000000000..98a1ca978a8
--- /dev/null
+++ b/spec/requests/api/npm_packages_spec.rb
@@ -0,0 +1,550 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::NpmPackages do
+ include PackagesManagerApiSpecHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project, reload: true) { create(:project, :public, namespace: group) }
+ let_it_be(:package, reload: true) { create(:npm_package, project: project) }
+ let_it_be(:token) { create(:oauth_access_token, scopes: 'api', resource_owner: user) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:job) { create(:ci_build, user: user) }
+ let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
+ let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ shared_examples 'a package that requires auth' do
+ it 'returns the package info with oauth token' do
+ get_package_with_token(package)
+
+ expect_a_valid_package_response
+ end
+
+ it 'returns the package info with job token' do
+ get_package_with_job_token(package)
+
+ expect_a_valid_package_response
+ end
+
+ it 'denies request without oauth token' do
+ get_package(package)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'returns the package info with deploy token' do
+ get_package_with_deploy_token(package)
+
+ expect_a_valid_package_response
+ end
+ end
+
+ describe 'GET /api/v4/packages/npm/*package_name' do
+ let_it_be(:package_dependency_link1) { create(:packages_dependency_link, package: package, dependency_type: :dependencies) }
+ let_it_be(:package_dependency_link2) { create(:packages_dependency_link, package: package, dependency_type: :devDependencies) }
+ let_it_be(:package_dependency_link3) { create(:packages_dependency_link, package: package, dependency_type: :bundleDependencies) }
+ let_it_be(:package_dependency_link4) { create(:packages_dependency_link, package: package, dependency_type: :peerDependencies) }
+
+ shared_examples 'returning the npm package info' do
+ it 'returns the package info' do
+ get_package(package)
+
+ expect_a_valid_package_response
+ end
+ end
+
+ shared_examples 'returning forbidden for unknown package' do
+ context 'with an unknown package' do
+ it 'returns forbidden' do
+ get api("/packages/npm/unknown")
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ context 'a public project' do
+ it_behaves_like 'returning the npm package info'
+
+ context 'with application setting enabled' do
+ before do
+ stub_application_setting(npm_package_requests_forwarding: true)
+ end
+
+ it_behaves_like 'returning the npm package info'
+
+ context 'with unknown package' do
+ it 'returns a redirect' do
+ get api("/packages/npm/unknown")
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(response.headers['Location']).to eq('https://registry.npmjs.org/unknown')
+ end
+ end
+ end
+
+ context 'with application setting disabled' do
+ before do
+ stub_application_setting(npm_package_requests_forwarding: false)
+ end
+
+ it_behaves_like 'returning the npm package info'
+
+ it_behaves_like 'returning forbidden for unknown package'
+ end
+
+ context 'project path with a dot' do
+ before do
+ project.update!(path: 'foo.bar')
+ end
+
+ it_behaves_like 'returning the npm package info'
+ end
+ end
+
+ context 'internal project' do
+ before do
+ project.team.truncate
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ it_behaves_like 'a package that requires auth'
+ end
+
+ context 'private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ it_behaves_like 'a package that requires auth'
+
+ it 'denies request when not enough permissions' do
+ project.add_guest(user)
+
+ get_package_with_token(package)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ def get_package(package, params = {}, headers = {})
+ get api("/packages/npm/#{package.name}"), params: params, headers: headers
+ end
+
+ def get_package_with_token(package, params = {})
+ get_package(package, params.merge(access_token: token.token))
+ end
+
+ def get_package_with_job_token(package, params = {})
+ get_package(package, params.merge(job_token: job.token))
+ end
+
+ def get_package_with_deploy_token(package, params = {})
+ get_package(package, {}, build_token_auth_header(deploy_token.token))
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/npm/*package_name/-/*file_name' do
+ let_it_be(:package_file) { package.package_files.first }
+
+ shared_examples 'a package file that requires auth' do
+ it 'returns the file with an access token' do
+ get_file_with_token(package_file)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'returns the file with a job token' do
+ get_file_with_job_token(package_file)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download with no token' do
+ get_file(package_file)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'a public project' do
+ subject { get_file(package_file) }
+
+ it 'returns the file with no token needed' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'pull_package'
+ end
+
+ context 'private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ it_behaves_like 'a package file that requires auth'
+
+ it 'denies download when not enough permissions' do
+ project.add_guest(user)
+
+ get_file_with_token(package_file)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'internal project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ it_behaves_like 'a package file that requires auth'
+ end
+
+ def get_file(package_file, params = {})
+ get api("/projects/#{project.id}/packages/npm/" \
+ "#{package_file.package.name}/-/#{package_file.file_name}"), params: params
+ end
+
+ def get_file_with_token(package_file, params = {})
+ get_file(package_file, params.merge(access_token: token.token))
+ end
+
+ def get_file_with_job_token(package_file, params = {})
+ get_file(package_file, params.merge(job_token: job.token))
+ end
+ end
+
+ describe 'PUT /api/v4/projects/:id/packages/npm/:package_name' do
+ RSpec.shared_examples 'handling invalid record with 400 error' do
+ it 'handles an ActiveRecord::RecordInvalid exception with 400 error' do
+ expect { upload_package_with_token(package_name, params) }
+ .not_to change { project.packages.count }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'when params are correct' do
+ context 'invalid package record' do
+ context 'unscoped package' do
+ let(:package_name) { 'my_unscoped_package' }
+ let(:params) { upload_params(package_name: package_name) }
+
+ it_behaves_like 'handling invalid record with 400 error'
+
+ context 'with empty versions' do
+ let(:params) { upload_params(package_name: package_name).merge!(versions: {}) }
+
+ it 'throws a 400 error' do
+ expect { upload_package_with_token(package_name, params) }
+ .not_to change { project.packages.count }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+
+ context 'invalid package name' do
+ let(:package_name) { "@#{group.path}/my_inv@@lid_package_name" }
+ let(:params) { upload_params(package_name: package_name) }
+
+ it_behaves_like 'handling invalid record with 400 error'
+ end
+
+ context 'invalid package version' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:package_name) { "@#{group.path}/my_package_name" }
+
+ where(:version) do
+ [
+ '1',
+ '1.2',
+ '1./2.3',
+ '../../../../../1.2.3',
+ '%2e%2e%2f1.2.3'
+ ]
+ end
+
+ with_them do
+ let(:params) { upload_params(package_name: package_name, package_version: version) }
+
+ it_behaves_like 'handling invalid record with 400 error'
+ end
+ end
+ end
+
+ context 'scoped package' do
+ let(:package_name) { "@#{group.path}/my_package_name" }
+ let(:params) { upload_params(package_name: package_name) }
+
+ context 'with access token' do
+ subject { upload_package_with_token(package_name, params) }
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'push_package'
+
+ it 'creates npm package with file' do
+ expect { subject }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+ .and change { Packages::Tag.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ it 'creates npm package with file with job token' do
+ expect { upload_package_with_job_token(package_name, params) }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ context 'with an authenticated job token' do
+ let!(:job) { create(:ci_build, user: user) }
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ expect(endpoint).to receive(:current_authenticated_job) { job }
+ end
+ end
+
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
+ it 'creates the package metadata' do
+ upload_package_with_token(package_name, params)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(project.reload.packages.find(json_response['id']).build_info.pipeline).to eq job.pipeline
+ end
+ end
+ end
+
+ context 'package creation fails' do
+ let(:package_name) { "@#{group.path}/my_package_name" }
+ let(:params) { upload_params(package_name: package_name) }
+
+ it 'returns an error if the package already exists' do
+ create(:npm_package, project: project, version: '1.0.1', name: "@#{group.path}/my_package_name")
+ expect { upload_package_with_token(package_name, params) }
+ .not_to change { project.packages.count }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'with dependencies' do
+ let(:package_name) { "@#{group.path}/my_package_name" }
+ let(:params) { upload_params(package_name: package_name, file: 'npm/payload_with_duplicated_packages.json') }
+
+ it 'creates npm package with file and dependencies' do
+ expect { upload_package_with_token(package_name, params) }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+ .and change { Packages::Dependency.count}.by(4)
+ .and change { Packages::DependencyLink.count}.by(6)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ context 'with existing dependencies' do
+ before do
+ name = "@#{group.path}/existing_package"
+ upload_package_with_token(name, upload_params(package_name: name, file: 'npm/payload_with_duplicated_packages.json'))
+ end
+
+ it 'reuses them' do
+ expect { upload_package_with_token(package_name, params) }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+ .and not_change { Packages::Dependency.count}
+ .and change { Packages::DependencyLink.count}.by(6)
+ end
+ end
+ end
+ end
+
+ def upload_package(package_name, params = {})
+ put api("/projects/#{project.id}/packages/npm/#{package_name.sub('/', '%2f')}"), params: params
+ end
+
+ def upload_package_with_token(package_name, params = {})
+ upload_package(package_name, params.merge(access_token: token.token))
+ end
+
+ def upload_package_with_job_token(package_name, params = {})
+ upload_package(package_name, params.merge(job_token: job.token))
+ end
+
+ def upload_params(package_name:, package_version: '1.0.1', file: 'npm/payload.json')
+ Gitlab::Json.parse(fixture_file("packages/#{file}")
+ .gsub('@root/npm-test', package_name)
+ .gsub('1.0.1', package_version))
+ end
+ end
+
+ describe 'GET /api/v4/packages/npm/-/package/*package_name/dist-tags' do
+ let_it_be(:package_tag1) { create(:packages_tag, package: package) }
+ let_it_be(:package_tag2) { create(:packages_tag, package: package) }
+
+ let(:package_name) { package.name }
+ let(:url) { "/packages/npm/-/package/#{package_name}/dist-tags" }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'with public project' do
+ context 'with authenticated user' do
+ subject { get api(url, personal_access_token: personal_access_token) }
+
+ it_behaves_like 'returns package tags', :maintainer
+ it_behaves_like 'returns package tags', :developer
+ it_behaves_like 'returns package tags', :reporter
+ it_behaves_like 'returns package tags', :guest
+ end
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'returns package tags', :no_type
+ end
+ end
+
+ context 'with private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ context 'with authenticated user' do
+ subject { get api(url, personal_access_token: personal_access_token) }
+
+ it_behaves_like 'returns package tags', :maintainer
+ it_behaves_like 'returns package tags', :developer
+ it_behaves_like 'returns package tags', :reporter
+ it_behaves_like 'rejects package tags access', :guest, :forbidden
+ end
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'rejects package tags access', :no_type, :forbidden
+ end
+ end
+ end
+ end
+
+ describe 'PUT /api/v4/packages/npm/-/package/*package_name/dist-tags/:tag' do
+ let_it_be(:tag_name) { 'test' }
+
+ let(:package_name) { package.name }
+ let(:version) { package.version }
+ let(:url) { "/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}" }
+
+ subject { put api(url), env: { 'api.request.body': version } }
+
+ context 'without the need for a license' do
+ context 'with public project' do
+ context 'with authenticated user' do
+ subject { put api(url, personal_access_token: personal_access_token), env: { 'api.request.body': version } }
+
+ it_behaves_like 'create package tag', :maintainer
+ it_behaves_like 'create package tag', :developer
+ it_behaves_like 'rejects package tags access', :reporter, :forbidden
+ it_behaves_like 'rejects package tags access', :guest, :forbidden
+ end
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'rejects package tags access', :no_type, :unauthorized
+ end
+ end
+
+ context 'with private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ context 'with authenticated user' do
+ subject { put api(url, personal_access_token: personal_access_token), env: { 'api.request.body': version } }
+
+ it_behaves_like 'create package tag', :maintainer
+ it_behaves_like 'create package tag', :developer
+ it_behaves_like 'rejects package tags access', :reporter, :forbidden
+ it_behaves_like 'rejects package tags access', :guest, :forbidden
+ end
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'rejects package tags access', :no_type, :unauthorized
+ end
+ end
+ end
+ end
+
+ describe 'DELETE /api/v4/packages/npm/-/package/*package_name/dist-tags/:tag' do
+ let_it_be(:package_tag) { create(:packages_tag, package: package) }
+
+ let(:package_name) { package.name }
+ let(:tag_name) { package_tag.name }
+ let(:url) { "/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}" }
+
+ subject { delete api(url) }
+
+ context 'without the need for a license' do
+ context 'with public project' do
+ context 'with authenticated user' do
+ subject { delete api(url, personal_access_token: personal_access_token) }
+
+ it_behaves_like 'delete package tag', :maintainer
+ it_behaves_like 'rejects package tags access', :developer, :forbidden
+ it_behaves_like 'rejects package tags access', :reporter, :forbidden
+ it_behaves_like 'rejects package tags access', :guest, :forbidden
+ end
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'rejects package tags access', :no_type, :unauthorized
+ end
+ end
+
+ context 'with private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ context 'with authenticated user' do
+ subject { delete api(url, personal_access_token: personal_access_token) }
+
+ it_behaves_like 'delete package tag', :maintainer
+ it_behaves_like 'rejects package tags access', :developer, :forbidden
+ it_behaves_like 'rejects package tags access', :reporter, :forbidden
+ it_behaves_like 'rejects package tags access', :guest, :forbidden
+ end
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'rejects package tags access', :no_type, :unauthorized
+ end
+ end
+ end
+ end
+
+ def expect_a_valid_package_response
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/json')
+ expect(response).to match_response_schema('public_api/v4/packages/npm_package')
+ expect(json_response['name']).to eq(package.name)
+ expect(json_response['versions'][package.version]).to match_schema('public_api/v4/packages/npm_package_version')
+ ::Packages::Npm::PackagePresenter::NPM_VALID_DEPENDENCY_TYPES.each do |dependency_type|
+ expect(json_response.dig('versions', package.version, dependency_type.to_s)).to be_any
+ end
+ expect(json_response['dist-tags']).to match_schema('public_api/v4/packages/npm_package_tags')
+ end
+end
diff --git a/spec/requests/api/nuget_packages_spec.rb b/spec/requests/api/nuget_packages_spec.rb
new file mode 100644
index 00000000000..43aa65d1f76
--- /dev/null
+++ b/spec/requests/api/nuget_packages_spec.rb
@@ -0,0 +1,482 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe API::NugetPackages do
+ include WorkhorseHelpers
+ include PackagesManagerApiSpecHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project, :public) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
+ let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
+
+ describe 'GET /api/v4/projects/:id/packages/nuget' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/index.json" }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget service index request' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget service index request' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget service index request' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget service index request' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget service index request' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget service index request' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget service index request' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget service index request' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget service index request' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget service index request' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+
+ describe 'PUT /api/v4/projects/:id/packages/nuget/authorize' do
+ let_it_be(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let_it_be(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let(:url) { "/projects/#{project.id}/packages/nuget/authorize" }
+ let(:headers) { {} }
+
+ subject { put api(url), headers: headers }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget workhorse authorization' | :success
+ 'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | true | true | 'process nuget workhorse authorization' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:user_headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+ let(:headers) { user_headers.merge(workhorse_header) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package uploads'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+
+ describe 'PUT /api/v4/projects/:id/packages/nuget' do
+ let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let_it_be(:file_name) { 'package.nupkg' }
+ let(:url) { "/projects/#{project.id}/packages/nuget" }
+ let(:headers) { {} }
+ let(:params) { { package: temp_file(file_name) } }
+ let(:file_key) { :package }
+ let(:send_rewritten_field) { true }
+
+ subject do
+ workhorse_finalize(
+ api(url),
+ method: :put,
+ file_key: file_key,
+ params: params,
+ headers: headers,
+ send_rewritten_field: send_rewritten_field
+ )
+ end
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget upload' | :created
+ 'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | true | true | 'process nuget upload' | :created
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:user_headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+ let(:headers) { user_headers.merge(workhorse_header) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package uploads'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/index' do
+ include_context 'with expected presenters dependency groups'
+
+ let_it_be(:package_name) { 'Dummy.Package' }
+ let_it_be(:packages) { create_list(:nuget_package, 5, :with_metadatum, name: package_name, project: project) }
+ let_it_be(:tags) { packages.each { |pkg| create(:packages_tag, package: pkg, name: 'test') } }
+ let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/index.json" }
+
+ subject { get api(url) }
+
+ before do
+ packages.each { |pkg| create_dependencies_for(pkg) }
+ end
+
+ context 'without the need for license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget metadata request at package name level' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name level' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/*package_version' do
+ include_context 'with expected presenters dependency groups'
+
+ let_it_be(:package_name) { 'Dummy.Package' }
+ let_it_be(:package) { create(:nuget_package, :with_metadatum, name: 'Dummy.Package', project: project) }
+ let_it_be(:tag) { create(:packages_tag, package: package, name: 'test') }
+ let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/#{package.version}.json" }
+
+ subject { get api(url) }
+
+ before do
+ create_dependencies_for(package)
+ end
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ context 'with invalid package name' do
+ let_it_be(:package_name) { 'Unkown' }
+
+ it_behaves_like 'rejects nuget packages access', :developer, :not_found
+ end
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/nuget/download/*package_name/index' do
+ let_it_be(:package_name) { 'Dummy.Package' }
+ let_it_be(:packages) { create_list(:nuget_package, 5, name: package_name, project: project) }
+ let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package_name}/index.json" }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget download versions request' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget download versions request' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget download versions request' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget download versions request' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget download versions request' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget download versions request' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget download versions request' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget download versions request' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget download versions request' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget download versions request' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/nuget/download/*package_name/*package_version/*package_filename' do
+ let_it_be(:package_name) { 'Dummy.Package' }
+ let_it_be(:package) { create(:nuget_package, project: project, name: package_name) }
+
+ let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package.name}/#{package.version}/#{package.name}.#{package.version}.nupkg" }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget download content request' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget download content request' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget download content request' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget download content request' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget download content request' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget download content request' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget download content request' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget download content request' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget download content request' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget download content request' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/nuget/query' do
+ let_it_be(:package_a) { create(:nuget_package, :with_metadatum, name: 'Dummy.PackageA', project: project) }
+ let_it_be(:tag) { create(:packages_tag, package: package_a, name: 'test') }
+ let_it_be(:packages_b) { create_list(:nuget_package, 5, name: 'Dummy.PackageB', project: project) }
+ let_it_be(:packages_c) { create_list(:nuget_package, 5, name: 'Dummy.PackageC', project: project) }
+ let_it_be(:package_d) { create(:nuget_package, name: 'Dummy.PackageD', version: '5.0.5-alpha', project: project) }
+ let_it_be(:package_e) { create(:nuget_package, name: 'Foo.BarE', project: project) }
+ let(:search_term) { 'uMmy' }
+ let(:take) { 26 }
+ let(:skip) { 0 }
+ let(:include_prereleases) { true }
+ let(:query_parameters) { { q: search_term, take: take, skip: skip, prerelease: include_prereleases } }
+ let(:url) { "/projects/#{project.id}/packages/nuget/query?#{query_parameters.to_query}" }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget search request' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget search request' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget search request' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget search request' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget search request' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget search request' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget search request' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget search request' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget search request' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget search request' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+end
diff --git a/spec/requests/api/oauth_tokens_spec.rb b/spec/requests/api/oauth_tokens_spec.rb
index 5e775841f12..f5971054b3c 100644
--- a/spec/requests/api/oauth_tokens_spec.rb
+++ b/spec/requests/api/oauth_tokens_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'OAuth tokens' do
+RSpec.describe 'OAuth tokens' do
include HttpBasicAuthHelpers
context 'Resource Owner Password Credentials' do
diff --git a/spec/requests/api/package_files_spec.rb b/spec/requests/api/package_files_spec.rb
new file mode 100644
index 00000000000..11170066d6e
--- /dev/null
+++ b/spec/requests/api/package_files_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::PackageFiles do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+ let(:package) { create(:maven_package, project: project) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ describe 'GET /projects/:id/packages/:package_id/package_files' do
+ let(:url) { "/projects/#{project.id}/packages/#{package.id}/package_files" }
+
+ context 'without the need for a license' do
+ context 'project is public' do
+ it 'returns 200' do
+ get api(url)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns 404 if package does not exist' do
+ get api("/projects/#{project.id}/packages/0/package_files")
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'project is private' do
+ let(:project) { create(:project, :private) }
+
+ it 'returns 404 for non authenticated user' do
+ get api(url)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 404 for a user without access to the project' do
+ project.team.truncate
+
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 200 and valid response schema' do
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/packages/package_files')
+ end
+ end
+
+ context 'with pagination params' do
+ let(:per_page) { 2 }
+ let!(:package_file_1) { package.package_files[0] }
+ let!(:package_file_2) { package.package_files[1] }
+ let!(:package_file_3) { package.package_files[2] }
+
+ context 'when viewing the first page' do
+ it 'returns first 2 packages' do
+ get api(url, user), params: { page: 1, per_page: per_page }
+
+ expect_paginated_array_response([package_file_1.id, package_file_2.id])
+ end
+ end
+
+ context 'viewing the second page' do
+ it 'returns the last package' do
+ get api(url, user), params: { page: 2, per_page: per_page }
+
+ expect_paginated_array_response([package_file_3.id])
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/pages/internal_access_spec.rb b/spec/requests/api/pages/internal_access_spec.rb
index ee55d1c54b7..c894a2d3ca4 100644
--- a/spec/requests/api/pages/internal_access_spec.rb
+++ b/spec/requests/api/pages/internal_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe "Internal Project Pages Access" do
+RSpec.describe "Internal Project Pages Access" do
using RSpec::Parameterized::TableSyntax
include AccessMatchers
diff --git a/spec/requests/api/pages/pages_spec.rb b/spec/requests/api/pages/pages_spec.rb
index 62d43ecff16..53e732928ff 100644
--- a/spec/requests/api/pages/pages_spec.rb
+++ b/spec/requests/api/pages/pages_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Pages do
+RSpec.describe API::Pages do
let_it_be(:project) { create(:project, path: 'my.project', pages_https_only: false) }
let_it_be(:admin) { create(:admin) }
let_it_be(:user) { create(:user) }
diff --git a/spec/requests/api/pages/private_access_spec.rb b/spec/requests/api/pages/private_access_spec.rb
index 146c6a389f3..ea5db691b14 100644
--- a/spec/requests/api/pages/private_access_spec.rb
+++ b/spec/requests/api/pages/private_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe "Private Project Pages Access" do
+RSpec.describe "Private Project Pages Access" do
using RSpec::Parameterized::TableSyntax
include AccessMatchers
diff --git a/spec/requests/api/pages/public_access_spec.rb b/spec/requests/api/pages/public_access_spec.rb
index 7d929e2a287..ae73cee91d5 100644
--- a/spec/requests/api/pages/public_access_spec.rb
+++ b/spec/requests/api/pages/public_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe "Public Project Pages Access" do
+RSpec.describe "Public Project Pages Access" do
using RSpec::Parameterized::TableSyntax
include AccessMatchers
diff --git a/spec/requests/api/pages_domains_spec.rb b/spec/requests/api/pages_domains_spec.rb
index 8c411233b27..b6838a39257 100644
--- a/spec/requests/api/pages_domains_spec.rb
+++ b/spec/requests/api/pages_domains_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::PagesDomains do
+RSpec.describe API::PagesDomains do
let_it_be(:project) { create(:project, path: 'my.project', pages_https_only: false) }
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb
index ed899e830e1..ff35e380476 100644
--- a/spec/requests/api/project_clusters_spec.rb
+++ b/spec/requests/api/project_clusters_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectClusters do
+RSpec.describe API::ProjectClusters do
include KubernetesHelpers
let_it_be(:current_user) { create(:user) }
@@ -40,7 +40,7 @@ describe API::ProjectClusters do
expect(response).to include_pagination_headers
end
- it 'onlies include authorized clusters' do
+ it 'only includes authorized clusters' do
cluster_ids = json_response.map { |cluster| cluster['id'] }
expect(response).to have_gitlab_http_status(:ok)
@@ -258,29 +258,52 @@ describe API::ProjectClusters do
end
end
- context 'when user tries to add multiple clusters' do
+ context 'non-authorized user' do
before do
- create(:cluster, :provided_by_gcp, :project,
- projects: [project])
-
- post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params
+ post api("/projects/#{project.id}/clusters/user", developer_user), params: cluster_params
end
- it 'responds with 400' do
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message']['base'].first)
- .to eq(_('Instance does not support multiple Kubernetes clusters'))
+ it 'responds with 403' do
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(json_response['message']).to eq('403 Forbidden')
end
end
+ end
- context 'non-authorized user' do
+ describe 'POST /projects/:id/clusters/user with multiple clusters' do
+ let(:api_url) { 'https://kubernetes.example.com' }
+ let(:namespace) { project.path }
+
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'sample-token',
+ namespace: namespace
+ }
+ end
+
+ let(:cluster_params) do
+ {
+ name: 'test-cluster',
+ environment_scope: 'production/*',
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ context 'when another cluster exists' do
before do
- post api("/projects/#{project.id}/clusters/user", developer_user), params: cluster_params
+ create(:cluster, :provided_by_gcp, :project,
+ projects: [project])
+
+ post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params
end
- it 'responds with 403' do
- expect(response).to have_gitlab_http_status(:forbidden)
- expect(json_response['message']).to eq('403 Forbidden')
+ it 'responds with 201' do
+ expect(response).to have_gitlab_http_status(:created)
+ end
+
+ it 'allows multiple clusters to be associated to project' do
+ expect(project.reload.clusters.count).to eq(2)
end
end
end
diff --git a/spec/requests/api/project_container_repositories_spec.rb b/spec/requests/api/project_container_repositories_spec.rb
index 471fc99117b..6cf0619cde4 100644
--- a/spec/requests/api/project_container_repositories_spec.rb
+++ b/spec/requests/api/project_container_repositories_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectContainerRepositories do
+RSpec.describe API::ProjectContainerRepositories do
include ExclusiveLeaseHelpers
let_it_be(:project) { create(:project, :private) }
diff --git a/spec/requests/api/project_events_spec.rb b/spec/requests/api/project_events_spec.rb
index f65c62f9402..f3e592f9796 100644
--- a/spec/requests/api/project_events_spec.rb
+++ b/spec/requests/api/project_events_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectEvents do
+RSpec.describe API::ProjectEvents do
let(:user) { create(:user) }
let(:non_member) { create(:user) }
let(:private_project) { create(:project, :private, creator_id: user.id, namespace: user.namespace) }
diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb
index 58034322a13..d7ba3b4e158 100644
--- a/spec/requests/api/project_export_spec.rb
+++ b/spec/requests/api/project_export_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectExport, :clean_gitlab_redis_cache do
+RSpec.describe API::ProjectExport, :clean_gitlab_redis_cache do
let_it_be(:project) { create(:project) }
let_it_be(:project_none) { create(:project) }
let_it_be(:project_started) { create(:project) }
@@ -237,7 +237,7 @@ describe API::ProjectExport, :clean_gitlab_redis_cache do
before do
allow(Gitlab::ApplicationRateLimiter)
.to receive(:increment)
- .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:project_download_export][:threshold] + 1)
+ .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:project_download_export][:threshold].call + 1)
end
it 'prevents requesting project export' do
@@ -362,7 +362,7 @@ describe API::ProjectExport, :clean_gitlab_redis_cache do
before do
allow(Gitlab::ApplicationRateLimiter)
.to receive(:increment)
- .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:project_export][:threshold] + 1)
+ .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:project_export][:threshold].call + 1)
end
it 'prevents requesting project export' do
diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb
index 4474f2f0577..8ab90e26a51 100644
--- a/spec/requests/api/project_hooks_spec.rb
+++ b/spec/requests/api/project_hooks_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectHooks, 'ProjectHooks' do
+RSpec.describe API::ProjectHooks, 'ProjectHooks' do
let(:user) { create(:user) }
let(:user3) { create(:user) }
let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb
index 563acd0ece4..a6ae636996e 100644
--- a/spec/requests/api/project_import_spec.rb
+++ b/spec/requests/api/project_import_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectImport do
+RSpec.describe API::ProjectImport do
include WorkhorseHelpers
let(:user) { create(:user) }
diff --git a/spec/requests/api/project_milestones_spec.rb b/spec/requests/api/project_milestones_spec.rb
index c5911d51706..b238949ce47 100644
--- a/spec/requests/api/project_milestones_spec.rb
+++ b/spec/requests/api/project_milestones_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectMilestones do
+RSpec.describe API::ProjectMilestones do
let(:user) { create(:user) }
let!(:project) { create(:project, namespace: user.namespace ) }
let!(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') }
diff --git a/spec/requests/api/project_packages_spec.rb b/spec/requests/api/project_packages_spec.rb
new file mode 100644
index 00000000000..0ece3bff8f9
--- /dev/null
+++ b/spec/requests/api/project_packages_spec.rb
@@ -0,0 +1,272 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::ProjectPackages do
+ let(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public) }
+ let!(:package1) { create(:npm_package, project: project, version: '3.1.0', name: "@#{project.root_namespace.path}/foo1") }
+ let(:package_url) { "/projects/#{project.id}/packages/#{package1.id}" }
+ let!(:package2) { create(:nuget_package, project: project, version: '2.0.4') }
+ let!(:another_package) { create(:npm_package) }
+ let(:no_package_url) { "/projects/#{project.id}/packages/0" }
+ let(:wrong_package_url) { "/projects/#{project.id}/packages/#{another_package.id}" }
+
+ describe 'GET /projects/:id/packages' do
+ let(:url) { "/projects/#{project.id}/packages" }
+ let(:package_schema) { 'public_api/v4/packages/packages' }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'project is public' do
+ it_behaves_like 'returns packages', :project, :no_type
+ end
+
+ context 'project is private' do
+ let(:project) { create(:project, :private) }
+
+ context 'for unauthenticated user' do
+ it_behaves_like 'rejects packages access', :project, :no_type, :not_found
+ end
+
+ context 'for authenticated user' do
+ subject { get api(url, user) }
+
+ it_behaves_like 'returns packages', :project, :maintainer
+ it_behaves_like 'returns packages', :project, :developer
+ it_behaves_like 'returns packages', :project, :reporter
+ it_behaves_like 'rejects packages access', :project, :no_type, :not_found
+ it_behaves_like 'rejects packages access', :project, :guest, :forbidden
+
+ context 'user is a maintainer' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ it 'returns the destroy url' do
+ subject
+
+ expect(json_response.first['_links']).to include('delete_api_path')
+ end
+ end
+ end
+ end
+
+ context 'with pagination params' do
+ let!(:package3) { create(:maven_package, project: project) }
+ let!(:package4) { create(:maven_package, project: project) }
+
+ context 'with pagination params' do
+ let!(:package3) { create(:npm_package, project: project) }
+ let!(:package4) { create(:npm_package, project: project) }
+
+ it_behaves_like 'returns paginated packages'
+ end
+ end
+
+ context 'with sorting' do
+ let(:package3) { create(:maven_package, project: project, version: '1.1.1', name: 'zzz') }
+
+ before do
+ travel_to(1.day.ago) do
+ package3
+ end
+ end
+
+ it_behaves_like 'package sorting', 'name' do
+ let(:packages) { [package1, package2, package3] }
+ end
+
+ it_behaves_like 'package sorting', 'created_at' do
+ let(:packages) { [package3, package1, package2] }
+ end
+
+ it_behaves_like 'package sorting', 'version' do
+ let(:packages) { [package3, package2, package1] }
+ end
+
+ it_behaves_like 'package sorting', 'type' do
+ let(:packages) { [package3, package1, package2] }
+ end
+ end
+
+ it_behaves_like 'filters on each package_type', is_project: true
+
+ context 'filtering on package_name' do
+ include_context 'package filter context'
+
+ it 'returns the named package' do
+ url = package_filter_url(:name, 'nuget')
+ get api(url, user)
+
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['name']).to include(package2.name)
+ end
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/packages/:package_id' do
+ subject { get api(package_url, user) }
+
+ shared_examples 'no destroy url' do
+ it 'returns no destroy url' do
+ subject
+
+ expect(json_response['_links']).not_to include('delete_api_path')
+ end
+ end
+
+ shared_examples 'destroy url' do
+ it 'returns destroy url' do
+ subject
+
+ expect(json_response['_links']['delete_api_path']).to be_present
+ end
+ end
+
+ context 'without the need for a license' do
+ context 'project is public' do
+ it 'returns 200 and the package information' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/packages/package')
+ end
+
+ it 'returns 404 when the package does not exist' do
+ get api(no_package_url, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 404 for the package from a different project' do
+ get api(wrong_package_url, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it_behaves_like 'no destroy url'
+ end
+
+ context 'project is private' do
+ let(:project) { create(:project, :private) }
+
+ it 'returns 404 for non authenticated user' do
+ get api(package_url)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 404 for a user without access to the project' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ context 'user is a developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'returns 200 and the package information' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/packages/package')
+ end
+
+ it_behaves_like 'no destroy url'
+ end
+
+ context 'user is a maintainer' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ it_behaves_like 'destroy url'
+ end
+
+ context 'with pipeline' do
+ let!(:package1) { create(:npm_package, :with_build, project: project) }
+
+ it 'returns the pipeline info' do
+ project.add_developer(user)
+
+ get api(package_url, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/packages/package_with_build')
+ end
+ end
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/packages/:package_id' do
+ context 'without the need for a license' do
+ context 'project is public' do
+ it 'returns 403 for non authenticated user' do
+ delete api(package_url)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'returns 403 for a user without access to the project' do
+ delete api(package_url, user)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'project is private' do
+ let(:project) { create(:project, :private) }
+
+ it 'returns 404 for non authenticated user' do
+ delete api(package_url)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 404 for a user without access to the project' do
+ delete api(package_url, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 404 when the package does not exist' do
+ project.add_maintainer(user)
+
+ delete api(no_package_url, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 404 for the package from a different project' do
+ project.add_maintainer(user)
+
+ delete api(wrong_package_url, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 403 for a user without enough permissions' do
+ project.add_developer(user)
+
+ delete api(package_url, user)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'returns 204' do
+ project.add_maintainer(user)
+
+ delete api(package_url, user)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/project_repository_storage_moves_spec.rb b/spec/requests/api/project_repository_storage_moves_spec.rb
index 40966e31d0d..4c9e058ef13 100644
--- a/spec/requests/api/project_repository_storage_moves_spec.rb
+++ b/spec/requests/api/project_repository_storage_moves_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectRepositoryStorageMoves do
+RSpec.describe API::ProjectRepositoryStorageMoves do
include AccessMatchersForRequest
let_it_be(:user) { create(:admin) }
diff --git a/spec/requests/api/project_snapshots_spec.rb b/spec/requests/api/project_snapshots_spec.rb
index a54f317782b..f23e374407b 100644
--- a/spec/requests/api/project_snapshots_spec.rb
+++ b/spec/requests/api/project_snapshots_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectSnapshots do
+RSpec.describe API::ProjectSnapshots do
include WorkhorseHelpers
let(:project) { create(:project) }
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 22189dc3299..fbb0e3e109f 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -2,7 +2,9 @@
require 'spec_helper'
-describe API::ProjectSnippets do
+RSpec.describe API::ProjectSnippets do
+ include SnippetHelpers
+
let_it_be(:project) { create(:project, :public) }
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
@@ -84,19 +86,22 @@ describe API::ProjectSnippets do
end
describe 'GET /projects/:project_id/snippets/:id' do
- let(:user) { create(:user) }
- let(:snippet) { create(:project_snippet, :public, :repository, project: project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:snippet) { create(:project_snippet, :public, :repository, project: project) }
it 'returns snippet json' do
get api("/projects/#{project.id}/snippets/#{snippet.id}", user)
- expect(response).to have_gitlab_http_status(:ok)
+ aggregate_failures do
+ expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['title']).to eq(snippet.title)
- expect(json_response['description']).to eq(snippet.description)
- expect(json_response['file_name']).to eq(snippet.file_name_on_repo)
- expect(json_response['ssh_url_to_repo']).to eq(snippet.ssh_url_to_repo)
- expect(json_response['http_url_to_repo']).to eq(snippet.http_url_to_repo)
+ expect(json_response['title']).to eq(snippet.title)
+ expect(json_response['description']).to eq(snippet.description)
+ expect(json_response['file_name']).to eq(snippet.file_name_on_repo)
+ expect(json_response['files']).to eq(snippet.blobs.map { |blob| snippet_blob_file(blob) } )
+ expect(json_response['ssh_url_to_repo']).to eq(snippet.ssh_url_to_repo)
+ expect(json_response['http_url_to_repo']).to eq(snippet.http_url_to_repo)
+ end
end
it 'returns 404 for invalid snippet id' do
@@ -111,6 +116,10 @@ describe API::ProjectSnippets do
let(:request) { get api("/projects/#{project_no_snippets.id}/snippets/123", user) }
end
end
+
+ it_behaves_like 'snippet_multiple_files feature disabled' do
+ subject { get api("/projects/#{project.id}/snippets/#{snippet.id}", user) }
+ end
end
describe 'POST /projects/:project_id/snippets/' do
@@ -443,7 +452,7 @@ describe API::ProjectSnippets do
get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw", admin)
expect(response).to have_gitlab_http_status(:ok)
- expect(response.content_type).to eq 'text/plain'
+ expect(response.media_type).to eq 'text/plain'
end
it 'returns 404 for invalid snippet id' do
@@ -465,4 +474,12 @@ describe API::ProjectSnippets do
subject { get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw", snippet.author) }
end
end
+
+ describe 'GET /projects/:project_id/snippets/:id/files/:ref/:file_path/raw' do
+ let_it_be(:snippet) { create(:project_snippet, :repository, author: admin, project: project) }
+
+ it_behaves_like 'raw snippet files' do
+ let(:api_path) { "/projects/#{snippet.project.id}/snippets/#{snippet_id}/files/#{ref}/#{file_path}/raw" }
+ end
+ end
end
diff --git a/spec/requests/api/project_statistics_spec.rb b/spec/requests/api/project_statistics_spec.rb
index 89809a97b96..5f0cac403aa 100644
--- a/spec/requests/api/project_statistics_spec.rb
+++ b/spec/requests/api/project_statistics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectStatistics do
+RSpec.describe API::ProjectStatistics do
let_it_be(:developer) { create(:user) }
let_it_be(:public_project) { create(:project, :public) }
diff --git a/spec/requests/api/project_templates_spec.rb b/spec/requests/api/project_templates_spec.rb
index caeb465080e..59b2b09f0bf 100644
--- a/spec/requests/api/project_templates_spec.rb
+++ b/spec/requests/api/project_templates_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectTemplates do
+RSpec.describe API::ProjectTemplates do
let_it_be(:public_project) { create(:project, :public, path: 'path.with.dot') }
let_it_be(:private_project) { create(:project, :private) }
let_it_be(:developer) { create(:user) }
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index c3f29ec47a9..76b0c04e32d 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-shared_examples 'languages and percentages JSON response' do
+RSpec.shared_examples 'languages and percentages JSON response' do
let(:expected_languages) { project.repository.languages.map { |language| language.values_at(:label, :value)}.to_h }
before do
@@ -46,7 +46,7 @@ shared_examples 'languages and percentages JSON response' do
end
end
-describe API::Projects do
+RSpec.describe API::Projects do
include ProjectForksHelper
let(:user) { create(:user) }
@@ -254,7 +254,10 @@ describe API::Projects do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.first).to include 'statistics'
+
+ statistics = json_response.first['statistics']
+ expect(statistics).to be_present
+ expect(statistics).to include('commit_count', 'storage_size', 'repository_size', 'wiki_size', 'lfs_objects_size', 'job_artifacts_size', 'snippets_size')
end
it "does not include license by default" do
@@ -584,6 +587,85 @@ describe API::Projects do
end
end
+ context 'sorting by project statistics' do
+ %w(repository_size storage_size wiki_size).each do |order_by|
+ context "sorting by #{order_by}" do
+ before do
+ ProjectStatistics.update_all(order_by => 100)
+ project4.statistics.update_columns(order_by => 10)
+ project.statistics.update_columns(order_by => 200)
+ end
+
+ context 'admin user' do
+ let(:current_user) { admin }
+
+ context "when sorting by #{order_by} ascendingly" do
+ it 'returns a properly sorted list of projects' do
+ get api('/projects', current_user), params: { order_by: order_by, sort: :asc }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['id']).to eq(project4.id)
+ end
+ end
+
+ context "when sorting by #{order_by} descendingly" do
+ it 'returns a properly sorted list of projects' do
+ get api('/projects', current_user), params: { order_by: order_by, sort: :desc }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['id']).to eq(project.id)
+ end
+ end
+ end
+
+ context 'non-admin user' do
+ let(:current_user) { user }
+ let(:projects) { [public_project, project, project2, project3] }
+
+ it 'returns projects ordered normally' do
+ get api('/projects', current_user), params: { order_by: order_by }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |project| project['id'] }).to eq(projects.map(&:id).reverse)
+ end
+ end
+ end
+ end
+ end
+
+ context 'filtering by repository_storage' do
+ before do
+ [project, project3].each { |proj| proj.update_columns(repository_storage: 'nfs-11') }
+ # Since we don't actually have Gitaly configured with an nfs-11 storage, an error would be raised
+ # when we present the projects in a response, as we ask Gitaly for stuff like default branch and Gitaly
+ # is not configured for a nfs-11 storage. So we trick Rails into thinking the storage for these projects
+ # is still default (in reality, it is).
+ allow_any_instance_of(Project).to receive(:repository_storage).and_return('default')
+ end
+
+ context 'admin user' do
+ it_behaves_like 'projects response' do
+ let(:filter) { { repository_storage: 'nfs-11' } }
+ let(:current_user) { admin }
+ let(:projects) { [project, project3] }
+ end
+ end
+
+ context 'non-admin user' do
+ it_behaves_like 'projects response' do
+ let(:filter) { { repository_storage: 'nfs-11' } }
+ let(:current_user) { user }
+ let(:projects) { [public_project, project, project2, project3] }
+ end
+ end
+ end
+
context 'with keyset pagination' do
let(:current_user) { user }
let(:projects) { [public_project, project, project2, project3] }
@@ -1846,6 +1928,13 @@ describe API::Projects do
end
end
end
+
+ it 'exposes service desk attributes' do
+ get api("/projects/#{project.id}", user)
+
+ expect(json_response).to have_key 'service_desk_enabled'
+ expect(json_response).to have_key 'service_desk_address'
+ end
end
describe 'GET /projects/:id/users' do
@@ -2133,7 +2222,7 @@ describe API::Projects do
expect(json_response['expires_at']).to eq(expires_at.to_s)
end
- it 'updates project authorization' do
+ it 'updates project authorization', :sidekiq_inline do
expect do
post api("/projects/#{project.id}/share", user), params: { group_id: group.id, group_access: Gitlab::Access::DEVELOPER }
end.to(
@@ -2590,6 +2679,26 @@ describe API::Projects do
end
end
end
+
+ context 'when updating service desk' do
+ subject { put(api("/projects/#{project.id}", user), params: { service_desk_enabled: true }) }
+
+ before do
+ project.update!(service_desk_enabled: false)
+
+ allow(::Gitlab::IncomingEmail).to receive(:enabled?).and_return(true)
+ end
+
+ it 'returns 200' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'enables the service_desk' do
+ expect { subject }.to change { project.reload.service_desk_enabled }.to(true)
+ end
+ end
end
describe 'POST /projects/:id/archive' do
diff --git a/spec/requests/api/protected_branches_spec.rb b/spec/requests/api/protected_branches_spec.rb
index 9203e0ec819..8bcd493eb1f 100644
--- a/spec/requests/api/protected_branches_spec.rb
+++ b/spec/requests/api/protected_branches_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProtectedBranches do
+RSpec.describe API::ProtectedBranches do
let(:user) { create(:user) }
let!(:project) { create(:project, :repository) }
let(:protected_name) { 'feature' }
diff --git a/spec/requests/api/protected_tags_spec.rb b/spec/requests/api/protected_tags_spec.rb
index 3bc8ecbee73..cc7261dafc9 100644
--- a/spec/requests/api/protected_tags_spec.rb
+++ b/spec/requests/api/protected_tags_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProtectedTags do
+RSpec.describe API::ProtectedTags do
let(:user) { create(:user) }
let!(:project) { create(:project, :repository) }
let(:project2) { create(:project, path: 'project2', namespace: user.namespace) }
diff --git a/spec/requests/api/pypi_packages_spec.rb b/spec/requests/api/pypi_packages_spec.rb
new file mode 100644
index 00000000000..b4e83c8caab
--- /dev/null
+++ b/spec/requests/api/pypi_packages_spec.rb
@@ -0,0 +1,259 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe API::PypiPackages do
+ include WorkhorseHelpers
+ include PackagesManagerApiSpecHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project, :public) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
+ let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
+
+ describe 'GET /api/v4/projects/:id/packages/pypi/simple/:package_name' do
+ let_it_be(:package) { create(:pypi_package, project: project) }
+ let(:url) { "/projects/#{project.id}/packages/pypi/simple/#{package.name}" }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'PyPi package versions' | :success
+ 'PUBLIC' | :guest | true | true | 'PyPi package versions' | :success
+ 'PUBLIC' | :developer | true | false | 'PyPi package versions' | :success
+ 'PUBLIC' | :guest | true | false | 'PyPi package versions' | :success
+ 'PUBLIC' | :developer | false | true | 'PyPi package versions' | :success
+ 'PUBLIC' | :guest | false | true | 'PyPi package versions' | :success
+ 'PUBLIC' | :developer | false | false | 'PyPi package versions' | :success
+ 'PUBLIC' | :guest | false | false | 'PyPi package versions' | :success
+ 'PUBLIC' | :anonymous | false | true | 'PyPi package versions' | :success
+ 'PRIVATE' | :developer | true | true | 'PyPi package versions' | :success
+ 'PRIVATE' | :guest | true | true | 'process PyPi api request' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'process PyPi api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process PyPi api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'process PyPi api request' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects PyPI access with unknown project id'
+ end
+ end
+
+ describe 'POST /api/v4/projects/:id/packages/pypi/authorize' do
+ let_it_be(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let_it_be(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let(:url) { "/projects/#{project.id}/packages/pypi/authorize" }
+ let(:headers) { {} }
+
+ subject { post api(url), headers: headers }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process PyPi api request' | :success
+ 'PUBLIC' | :guest | true | true | 'process PyPi api request' | :forbidden
+ 'PUBLIC' | :developer | true | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :guest | true | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :developer | false | true | 'process PyPi api request' | :forbidden
+ 'PUBLIC' | :guest | false | true | 'process PyPi api request' | :forbidden
+ 'PUBLIC' | :developer | false | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :guest | false | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :anonymous | false | true | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :developer | true | true | 'process PyPi api request' | :success
+ 'PRIVATE' | :guest | true | true | 'process PyPi api request' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'process PyPi api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process PyPi api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'process PyPi api request' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:user_headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+ let(:headers) { user_headers.merge(workhorse_header) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package uploads'
+
+ it_behaves_like 'rejects PyPI access with unknown project id'
+ end
+ end
+
+ describe 'POST /api/v4/projects/:id/packages/pypi' do
+ let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let_it_be(:file_name) { 'package.whl' }
+ let(:url) { "/projects/#{project.id}/packages/pypi" }
+ let(:headers) { {} }
+ let(:base_params) { { requires_python: '>=3.7', version: '1.0.0', name: 'sample-project', sha256_digest: '123' } }
+ let(:params) { base_params.merge(content: temp_file(file_name)) }
+ let(:send_rewritten_field) { true }
+
+ subject do
+ workhorse_finalize(
+ api(url),
+ method: :post,
+ file_key: :content,
+ params: params,
+ headers: headers,
+ send_rewritten_field: send_rewritten_field
+ )
+ end
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'PyPi package creation' | :created
+ 'PUBLIC' | :guest | true | true | 'process PyPi api request' | :forbidden
+ 'PUBLIC' | :developer | true | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :guest | true | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :developer | false | true | 'process PyPi api request' | :forbidden
+ 'PUBLIC' | :guest | false | true | 'process PyPi api request' | :forbidden
+ 'PUBLIC' | :developer | false | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :guest | false | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :anonymous | false | true | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :developer | true | true | 'process PyPi api request' | :created
+ 'PRIVATE' | :guest | true | true | 'process PyPi api request' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'process PyPi api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process PyPi api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'process PyPi api request' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:user_headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+ let(:headers) { user_headers.merge(workhorse_header) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ context 'with an invalid package' do
+ let(:token) { personal_access_token.token }
+ let(:user_headers) { build_basic_auth_header(user.username, token) }
+ let(:headers) { user_headers.merge(workhorse_header) }
+
+ before do
+ params[:name] = '.$/@!^*'
+ project.add_developer(user)
+ end
+
+ it_behaves_like 'returning response status', :bad_request
+ end
+
+ it_behaves_like 'deploy token for package uploads'
+
+ it_behaves_like 'rejects PyPI access with unknown project id'
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/pypi/files/:sha256/*file_identifier' do
+ let_it_be(:package_name) { 'Dummy-Package' }
+ let_it_be(:package) { create(:pypi_package, project: project, name: package_name, version: '1.0.0') }
+
+ let(:url) { "/projects/#{project.id}/packages/pypi/files/#{package.package_files.first.file_sha256}/#{package_name}-1.0.0.tar.gz" }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'PyPi package download' | :success
+ 'PUBLIC' | :guest | true | true | 'PyPi package download' | :success
+ 'PUBLIC' | :developer | true | false | 'PyPi package download' | :success
+ 'PUBLIC' | :guest | true | false | 'PyPi package download' | :success
+ 'PUBLIC' | :developer | false | true | 'PyPi package download' | :success
+ 'PUBLIC' | :guest | false | true | 'PyPi package download' | :success
+ 'PUBLIC' | :developer | false | false | 'PyPi package download' | :success
+ 'PUBLIC' | :guest | false | false | 'PyPi package download' | :success
+ 'PUBLIC' | :anonymous | false | true | 'PyPi package download' | :success
+ 'PRIVATE' | :developer | true | true | 'PyPi package download' | :success
+ 'PRIVATE' | :guest | true | true | 'PyPi package download' | :success
+ 'PRIVATE' | :developer | true | false | 'PyPi package download' | :success
+ 'PRIVATE' | :guest | true | false | 'PyPi package download' | :success
+ 'PRIVATE' | :developer | false | true | 'PyPi package download' | :success
+ 'PRIVATE' | :guest | false | true | 'PyPi package download' | :success
+ 'PRIVATE' | :developer | false | false | 'PyPi package download' | :success
+ 'PRIVATE' | :guest | false | false | 'PyPi package download' | :success
+ 'PRIVATE' | :anonymous | false | true | 'PyPi package download' | :success
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ context 'with deploy token headers' do
+ let(:headers) { build_basic_auth_header(deploy_token.username, deploy_token.token) }
+
+ context 'valid token' do
+ it_behaves_like 'returning response status', :success
+ end
+
+ context 'invalid token' do
+ let(:headers) { build_basic_auth_header('foo', 'bar') }
+
+ it_behaves_like 'returning response status', :success
+ end
+ end
+
+ it_behaves_like 'rejects PyPI access with unknown project id'
+ end
+ end
+end
diff --git a/spec/requests/api/release/links_spec.rb b/spec/requests/api/release/links_spec.rb
index cf2043ecc74..82d0d64eba4 100644
--- a/spec/requests/api/release/links_spec.rb
+++ b/spec/requests/api/release/links_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Release::Links do
+RSpec.describe API::Release::Links do
let(:project) { create(:project, :repository, :private) }
let(:maintainer) { create(:user) }
let(:reporter) { create(:user) }
diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb
index f4cb7f25990..5e8353d74c3 100644
--- a/spec/requests/api/releases_spec.rb
+++ b/spec/requests/api/releases_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Releases do
+RSpec.describe API::Releases do
let(:project) { create(:project, :repository, :private) }
let(:maintainer) { create(:user) }
let(:reporter) { create(:user) }
diff --git a/spec/requests/api/remote_mirrors_spec.rb b/spec/requests/api/remote_mirrors_spec.rb
index 3029b8443b0..436efb708fd 100644
--- a/spec/requests/api/remote_mirrors_spec.rb
+++ b/spec/requests/api/remote_mirrors_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::RemoteMirrors do
+RSpec.describe API::RemoteMirrors do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, :remote_mirror) }
let_it_be(:developer) { create(:user) { |u| project.add_developer(u) } }
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 55243e83017..36707f32d04 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'mime/types'
-describe API::Repositories do
+RSpec.describe API::Repositories do
include RepoHelpers
include WorkhorseHelpers
@@ -227,7 +227,8 @@ describe API::Repositories do
end
describe "GET /projects/:id/repository/archive(.:format)?:sha" do
- let(:route) { "/projects/#{project.id}/repository/archive" }
+ let(:project_id) { CGI.escape(project.full_path) }
+ let(:route) { "/projects/#{project_id}/repository/archive" }
before do
allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(false)
@@ -246,7 +247,7 @@ describe API::Repositories do
end
it 'returns the repository archive archive.zip' do
- get api("/projects/#{project.id}/repository/archive.zip", user)
+ get api("/projects/#{project_id}/repository/archive.zip", user)
expect(response).to have_gitlab_http_status(:ok)
@@ -257,7 +258,7 @@ describe API::Repositories do
end
it 'returns the repository archive archive.tar.bz2' do
- get api("/projects/#{project.id}/repository/archive.tar.bz2", user)
+ get api("/projects/#{project_id}/repository/archive.tar.bz2", user)
expect(response).to have_gitlab_http_status(:ok)
@@ -277,7 +278,7 @@ describe API::Repositories do
it 'rate limits user when thresholds hit' do
allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(true)
- get api("/projects/#{project.id}/repository/archive.tar.bz2", user)
+ get api("/projects/#{project_id}/repository/archive.tar.bz2", user)
expect(response).to have_gitlab_http_status(:too_many_requests)
end
@@ -302,6 +303,13 @@ describe API::Repositories do
end
end
+ context 'when unauthenticated and project path has dots' do
+ it_behaves_like 'repository archive' do
+ let(:project) { create(:project, :public, :repository, path: 'path.with.dot') }
+ let(:current_user) { nil }
+ end
+ end
+
context 'when unauthenticated', 'and project is private' do
it_behaves_like '404 response' do
let(:request) { get api(route) }
diff --git a/spec/requests/api/resource_label_events_spec.rb b/spec/requests/api/resource_label_events_spec.rb
index 7619399458a..a4a70d89812 100644
--- a/spec/requests/api/resource_label_events_spec.rb
+++ b/spec/requests/api/resource_label_events_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ResourceLabelEvents do
+RSpec.describe API::ResourceLabelEvents do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :public, namespace: user.namespace) }
let_it_be(:label) { create(:label, project: project) }
diff --git a/spec/requests/api/resource_milestone_events_spec.rb b/spec/requests/api/resource_milestone_events_spec.rb
index b2e92fde5ee..5c81c2180d7 100644
--- a/spec/requests/api/resource_milestone_events_spec.rb
+++ b/spec/requests/api/resource_milestone_events_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ResourceMilestoneEvents do
+RSpec.describe API::ResourceMilestoneEvents do
let!(:user) { create(:user) }
let!(:project) { create(:project, :public, namespace: user.namespace) }
let!(:milestone) { create(:milestone, project: project) }
diff --git a/spec/requests/api/resource_state_events_spec.rb b/spec/requests/api/resource_state_events_spec.rb
new file mode 100644
index 00000000000..46ca9874395
--- /dev/null
+++ b/spec/requests/api/resource_state_events_spec.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::ResourceStateEvents do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project, :public, namespace: user.namespace) }
+
+ before_all do
+ project.add_developer(user)
+ end
+
+ shared_examples 'resource_state_events API' do |parent_type, eventable_type, id_name|
+ describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_state_events" do
+ let!(:event) { create_event }
+
+ it "returns an array of resource state events" do
+ url = "/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_state_events"
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['id']).to eq(event.id)
+ expect(json_response.first['state']).to eq(event.state.to_s)
+ end
+
+ it "returns a 404 error when eventable id not found" do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{non_existing_record_id}/resource_state_events", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it "returns 404 when not authorized" do
+ parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ private_user = create(:user)
+
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_state_events", private_user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_state_events/:event_id" do
+ let!(:event) { create_event }
+
+ it "returns a resource state event by id" do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_state_events/#{event.id}", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['id']).to eq(event.id)
+ expect(json_response['state']).to eq(event.state.to_s)
+ end
+
+ it "returns 404 when not authorized" do
+ parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ private_user = create(:user)
+
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_state_events/#{event.id}", private_user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it "returns a 404 error if resource state event not found" do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_state_events/#{non_existing_record_id}", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ describe 'pagination' do
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/220192
+ it 'returns the second page' do
+ create_event
+ event2 = create_event
+
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_state_events?page=2&per_page=1", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(response.headers['X-Total']).to eq '2'
+ expect(json_response.count).to eq(1)
+ expect(json_response.first['id']).to eq(event2.id)
+ end
+ end
+
+ def create_event(state: :opened)
+ create(:resource_state_event, eventable.class.name.underscore => eventable, state: state)
+ end
+ end
+
+ context 'when eventable is an Issue' do
+ it_behaves_like 'resource_state_events API', 'projects', 'issues', 'iid' do
+ let(:parent) { project }
+ let(:eventable) { create(:issue, project: project, author: user) }
+ end
+ end
+
+ context 'when eventable is a Merge Request' do
+ it_behaves_like 'resource_state_events API', 'projects', 'merge_requests', 'iid' do
+ let(:parent) { project }
+ let(:eventable) { create(:merge_request, source_project: project, target_project: project, author: user) }
+ end
+ end
+end
diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb
index a02d804ee9b..1a93be98a67 100644
--- a/spec/requests/api/search_spec.rb
+++ b/spec/requests/api/search_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Search do
+RSpec.describe API::Search do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project, reload: true) { create(:project, :wiki_repo, :public, name: 'awesome project', group: group) }
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 53265574e6a..5528a0c094f 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe API::Services do
+RSpec.describe API::Services do
let_it_be(:user) { create(:user) }
let_it_be(:user2) { create(:user) }
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index e6dd1fecb69..602aacb6ced 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Settings, 'Settings' do
+RSpec.describe API::Settings, 'Settings' do
let(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
@@ -62,14 +62,14 @@ describe API::Settings, 'Settings' do
default_projects_limit: 3,
default_project_creation: 2,
password_authentication_enabled_for_web: false,
- repository_storages: ['custom'],
+ repository_storages: 'custom',
plantuml_enabled: true,
plantuml_url: 'http://plantuml.example.com',
sourcegraph_enabled: true,
sourcegraph_url: 'https://sourcegraph.com',
sourcegraph_public_only: false,
default_snippet_visibility: 'internal',
- restricted_visibility_levels: ['public'],
+ restricted_visibility_levels: 'public',
default_artifacts_expire_in: '2 days',
help_page_text: 'custom help text',
help_page_hide_commercial_content: true,
@@ -94,7 +94,9 @@ describe API::Settings, 'Settings' do
issues_create_limit: 300,
raw_blob_request_limit: 300,
spam_check_endpoint_enabled: true,
- spam_check_endpoint_url: 'https://example.com/spam_check'
+ spam_check_endpoint_url: 'https://example.com/spam_check',
+ disabled_oauth_sign_in_sources: 'unknown',
+ import_sources: 'github,bitbucket'
}
expect(response).to have_gitlab_http_status(:ok)
@@ -135,6 +137,8 @@ describe API::Settings, 'Settings' do
expect(json_response['raw_blob_request_limit']).to eq(300)
expect(json_response['spam_check_endpoint_enabled']).to be_truthy
expect(json_response['spam_check_endpoint_url']).to eq('https://example.com/spam_check')
+ expect(json_response['disabled_oauth_sign_in_sources']).to eq([])
+ expect(json_response['import_sources']).to match_array(%w(github bitbucket))
end
end
diff --git a/spec/requests/api/sidekiq_metrics_spec.rb b/spec/requests/api/sidekiq_metrics_spec.rb
index 705ae29d5d8..23ac2ea5c0b 100644
--- a/spec/requests/api/sidekiq_metrics_spec.rb
+++ b/spec/requests/api/sidekiq_metrics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::SidekiqMetrics do
+RSpec.describe API::SidekiqMetrics do
let(:admin) { create(:user, :admin) }
describe 'GET sidekiq/*' do
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index c12c95ae2e0..e676eb94337 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -2,14 +2,16 @@
require 'spec_helper'
-describe API::Snippets do
+RSpec.describe API::Snippets do
+ include SnippetHelpers
+
let_it_be(:user) { create(:user) }
describe 'GET /snippets/' do
it 'returns snippets available' do
- public_snippet = create(:personal_snippet, :public, author: user)
- private_snippet = create(:personal_snippet, :private, author: user)
- internal_snippet = create(:personal_snippet, :internal, author: user)
+ public_snippet = create(:personal_snippet, :repository, :public, author: user)
+ private_snippet = create(:personal_snippet, :repository, :private, author: user)
+ internal_snippet = create(:personal_snippet, :repository, :internal, author: user)
get api("/snippets/", user)
@@ -22,6 +24,7 @@ describe API::Snippets do
private_snippet.id)
expect(json_response.last).to have_key('web_url')
expect(json_response.last).to have_key('raw_url')
+ expect(json_response.last).to have_key('files')
expect(json_response.last).to have_key('visibility')
end
@@ -59,32 +62,33 @@ describe API::Snippets do
end
describe 'GET /snippets/public' do
- let!(:other_user) { create(:user) }
- let!(:public_snippet) { create(:personal_snippet, :public, author: user) }
- let!(:private_snippet) { create(:personal_snippet, :private, author: user) }
- let!(:internal_snippet) { create(:personal_snippet, :internal, author: user) }
- let!(:public_snippet_other) { create(:personal_snippet, :public, author: other_user) }
- let!(:private_snippet_other) { create(:personal_snippet, :private, author: other_user) }
- let!(:internal_snippet_other) { create(:personal_snippet, :internal, author: other_user) }
- let!(:public_snippet_project) { create(:project_snippet, :public, author: user) }
- let!(:private_snippet_project) { create(:project_snippet, :private, author: user) }
- let!(:internal_snippet_project) { create(:project_snippet, :internal, author: user) }
+ let_it_be(:other_user) { create(:user) }
+ let_it_be(:public_snippet) { create(:personal_snippet, :repository, :public, author: user) }
+ let_it_be(:private_snippet) { create(:personal_snippet, :repository, :private, author: user) }
+ let_it_be(:internal_snippet) { create(:personal_snippet, :repository, :internal, author: user) }
+ let_it_be(:public_snippet_other) { create(:personal_snippet, :repository, :public, author: other_user) }
+ let_it_be(:private_snippet_other) { create(:personal_snippet, :repository, :private, author: other_user) }
+ let_it_be(:internal_snippet_other) { create(:personal_snippet, :repository, :internal, author: other_user) }
+ let_it_be(:public_snippet_project) { create(:project_snippet, :repository, :public, author: user) }
+ let_it_be(:private_snippet_project) { create(:project_snippet, :repository, :private, author: user) }
+ let_it_be(:internal_snippet_project) { create(:project_snippet, :repository, :internal, author: user) }
it 'returns all snippets with public visibility from all users' do
get api("/snippets/public", user)
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly(
- public_snippet.id,
- public_snippet_other.id)
- expect(json_response.map { |snippet| snippet['web_url']} ).to contain_exactly(
- "http://localhost/snippets/#{public_snippet.id}",
- "http://localhost/snippets/#{public_snippet_other.id}")
- expect(json_response.map { |snippet| snippet['raw_url']} ).to contain_exactly(
- "http://localhost/snippets/#{public_snippet.id}/raw",
- "http://localhost/snippets/#{public_snippet_other.id}/raw")
+ aggregate_failures do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly(
+ public_snippet.id,
+ public_snippet_other.id)
+ expect(json_response.map { |snippet| snippet['web_url']} ).to contain_exactly(
+ "http://localhost/snippets/#{public_snippet.id}",
+ "http://localhost/snippets/#{public_snippet_other.id}")
+ expect(json_response[0]['files'].first).to eq snippet_blob_file(public_snippet_other.blobs.first)
+ expect(json_response[1]['files'].first).to eq snippet_blob_file(public_snippet.blobs.first)
+ end
end
end
@@ -102,13 +106,8 @@ describe API::Snippets do
get api("/snippets/#{snippet.id}/raw", author)
expect(response).to have_gitlab_http_status(:ok)
- expect(response.content_type).to eq 'text/plain'
- end
-
- it 'forces attachment content disposition' do
- get api("/snippets/#{snippet.id}/raw", author)
-
- expect(headers['Content-Disposition']).to match(/^attachment/)
+ expect(response.media_type).to eq 'text/plain'
+ expect(headers['Content-Disposition']).to match(/^inline/)
end
it 'returns 404 for invalid snippet id' do
@@ -141,56 +140,88 @@ describe API::Snippets do
end
end
+ describe 'GET /snippets/:id/files/:ref/:file_path/raw' do
+ let_it_be(:snippet) { create(:personal_snippet, :repository, :private) }
+
+ it_behaves_like 'raw snippet files' do
+ let(:api_path) { "/snippets/#{snippet_id}/files/#{ref}/#{file_path}/raw" }
+ end
+ end
+
describe 'GET /snippets/:id' do
let_it_be(:admin) { create(:user, :admin) }
let_it_be(:author) { create(:user) }
let_it_be(:private_snippet) { create(:personal_snippet, :repository, :private, author: author) }
let_it_be(:internal_snippet) { create(:personal_snippet, :repository, :internal, author: author) }
+ let(:snippet) { private_snippet }
- it 'requires authentication' do
- get api("/snippets/#{private_snippet.id}", nil)
+ subject { get api("/snippets/#{snippet.id}", user) }
- expect(response).to have_gitlab_http_status(:unauthorized)
+ it 'hides private snippets from an ordinary user' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
end
- it 'returns snippet json' do
- get api("/snippets/#{private_snippet.id}", author)
+ context 'without a user' do
+ let(:user) { nil }
- expect(response).to have_gitlab_http_status(:ok)
+ it 'requires authentication' do
+ subject
- expect(json_response['title']).to eq(private_snippet.title)
- expect(json_response['description']).to eq(private_snippet.description)
- expect(json_response['file_name']).to eq(private_snippet.file_name_on_repo)
- expect(json_response['visibility']).to eq(private_snippet.visibility)
- expect(json_response['ssh_url_to_repo']).to eq(private_snippet.ssh_url_to_repo)
- expect(json_response['http_url_to_repo']).to eq(private_snippet.http_url_to_repo)
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
end
- it 'shows private snippets to an admin' do
- get api("/snippets/#{private_snippet.id}", admin)
+ context 'with the author' do
+ let(:user) { author }
- expect(response).to have_gitlab_http_status(:ok)
- end
+ it 'returns snippet json' do
+ subject
- it 'hides private snippets from an ordinary user' do
- get api("/snippets/#{private_snippet.id}", user)
+ expect(response).to have_gitlab_http_status(:ok)
- expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['title']).to eq(private_snippet.title)
+ expect(json_response['description']).to eq(private_snippet.description)
+ expect(json_response['file_name']).to eq(private_snippet.file_name_on_repo)
+ expect(json_response['files']).to eq(private_snippet.blobs.map { |blob| snippet_blob_file(blob) })
+ expect(json_response['visibility']).to eq(private_snippet.visibility)
+ expect(json_response['ssh_url_to_repo']).to eq(private_snippet.ssh_url_to_repo)
+ expect(json_response['http_url_to_repo']).to eq(private_snippet.http_url_to_repo)
+ end
end
- it 'shows internal snippets to an ordinary user' do
- get api("/snippets/#{internal_snippet.id}", user)
+ context 'with an admin' do
+ let(:user) { admin }
- expect(response).to have_gitlab_http_status(:ok)
+ it 'shows private snippets to an admin' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns 404 for invalid snippet id' do
+ private_snippet.destroy
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('404 Snippet Not Found')
+ end
end
- it 'returns 404 for invalid snippet id' do
- private_snippet.destroy
+ context 'with an internal snippet' do
+ let(:snippet) { internal_snippet }
- get api("/snippets/#{private_snippet.id}", admin)
+ it 'shows internal snippets to an ordinary user' do
+ subject
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response['message']).to eq('404 Snippet Not Found')
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ it_behaves_like 'snippet_multiple_files feature disabled' do
+ let(:user) { author }
end
end
@@ -221,6 +252,7 @@ describe API::Snippets do
expect(json_response['title']).to eq(params[:title])
expect(json_response['description']).to eq(params[:description])
expect(json_response['file_name']).to eq(params[:file_name])
+ expect(json_response['files']).to eq(snippet.blobs.map { |blob| snippet_blob_file(blob) })
expect(json_response['visibility']).to eq(params[:visibility])
end
@@ -251,6 +283,10 @@ describe API::Snippets do
it_behaves_like 'snippet creation'
+ it_behaves_like 'snippet_multiple_files feature disabled' do
+ let(:snippet) { Snippet.find(json_response["id"]) }
+ end
+
context 'with an external user' do
let(:user) { create(:user, :external) }
diff --git a/spec/requests/api/statistics_spec.rb b/spec/requests/api/statistics_spec.rb
index 5aea5c225a0..eab97b6916e 100644
--- a/spec/requests/api/statistics_spec.rb
+++ b/spec/requests/api/statistics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Statistics, 'Statistics' do
+RSpec.describe API::Statistics, 'Statistics' do
include ProjectForksHelper
tables_to_analyze = %w[
projects
diff --git a/spec/requests/api/submodules_spec.rb b/spec/requests/api/submodules_spec.rb
index 2604dc18005..6b141d6d036 100644
--- a/spec/requests/api/submodules_spec.rb
+++ b/spec/requests/api/submodules_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Submodules do
+RSpec.describe API::Submodules do
let(:user) { create(:user) }
let!(:project) { create(:project, :repository, namespace: user.namespace ) }
let(:guest) { create(:user) { |u| project.add_guest(u) } }
diff --git a/spec/requests/api/suggestions_spec.rb b/spec/requests/api/suggestions_spec.rb
index ffb8c811622..34d3c54d700 100644
--- a/spec/requests/api/suggestions_spec.rb
+++ b/spec/requests/api/suggestions_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Suggestions do
+RSpec.describe API::Suggestions do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
index 609aa615d33..01b46053d52 100644
--- a/spec/requests/api/system_hooks_spec.rb
+++ b/spec/requests/api/system_hooks_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::SystemHooks do
+RSpec.describe API::SystemHooks do
include StubRequests
let(:user) { create(:user) }
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index 694802ce1b8..b029c0f5793 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Tags do
+RSpec.describe API::Tags do
let(:user) { create(:user) }
let(:guest) { create(:user).tap { |u| project.add_guest(u) } }
let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
diff --git a/spec/requests/api/task_completion_status_spec.rb b/spec/requests/api/task_completion_status_spec.rb
index 4dd1e27bd4b..97ce858ba12 100644
--- a/spec/requests/api/task_completion_status_spec.rb
+++ b/spec/requests/api/task_completion_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'task completion status response' do
+RSpec.describe 'task completion status response' do
let_it_be(:user) { create(:user) }
let_it_be(:project) do
create(:project, :public, creator_id: user.id, namespace: user.namespace)
diff --git a/spec/requests/api/templates_spec.rb b/spec/requests/api/templates_spec.rb
index fae338b4ca3..e1c5bfd82c4 100644
--- a/spec/requests/api/templates_spec.rb
+++ b/spec/requests/api/templates_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Templates do
+RSpec.describe API::Templates do
context 'the Template Entity' do
before do
get api('/templates/gitignores/Ruby')
diff --git a/spec/requests/api/terraform/state_spec.rb b/spec/requests/api/terraform/state_spec.rb
index ec9db5566e3..c6cba39314b 100644
--- a/spec/requests/api/terraform/state_spec.rb
+++ b/spec/requests/api/terraform/state_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Terraform::State do
+RSpec.describe API::Terraform::State do
include HttpBasicAuthHelpers
let_it_be(:project) { create(:project) }
@@ -59,10 +59,11 @@ describe API::Terraform::State do
context 'with developer permissions' do
let(:current_user) { developer }
- it 'returns forbidden if the user cannot access the state' do
+ it 'returns terraform state belonging to a project of given state name' do
request
- expect(response).to have_gitlab_http_status(:forbidden)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body).to eq(state.file.read)
end
end
end
@@ -94,10 +95,11 @@ describe API::Terraform::State do
context 'with developer permissions' do
let(:job) { create(:ci_build, project: project, user: developer) }
- it 'returns forbidden if the user cannot access the state' do
+ it 'returns terraform state belonging to a project of given state name' do
request
- expect(response).to have_gitlab_http_status(:forbidden)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body).to eq(state.file.read)
end
end
end
@@ -235,9 +237,43 @@ describe API::Terraform::State do
expect(response).to have_gitlab_http_status(:ok)
end
+
+ context 'state is already locked' do
+ before do
+ state.update!(lock_xid: 'locked', locked_by_user: current_user)
+ end
+
+ it 'returns an error' do
+ request
+
+ expect(response).to have_gitlab_http_status(:conflict)
+ end
+ end
+
+ context 'user does not have permission to lock the state' do
+ let(:current_user) { developer }
+
+ it 'returns an error' do
+ request
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
end
describe 'DELETE /projects/:id/terraform/state/:name/lock' do
+ let(:params) do
+ {
+ ID: lock_id,
+ Version: '0.1',
+ Operation: 'OperationTypePlan',
+ Info: '',
+ Who: "#{current_user.username}",
+ Created: Time.now.utc.iso8601(6),
+ Path: ''
+ }
+ end
+
before do
state.lock_xid = '123-456'
state.save!
@@ -246,7 +282,7 @@ describe API::Terraform::State do
subject(:request) { delete api("#{state_path}/lock"), headers: auth_header, params: params }
context 'with the correct lock id' do
- let(:params) { { ID: '123-456' } }
+ let(:lock_id) { '123-456' }
it 'removes the terraform state lock' do
request
@@ -266,7 +302,7 @@ describe API::Terraform::State do
end
context 'with an incorrect lock id' do
- let(:params) { { ID: '456-789' } }
+ let(:lock_id) { '456-789' }
it 'returns an error' do
request
@@ -276,7 +312,7 @@ describe API::Terraform::State do
end
context 'with a longer than 255 character lock id' do
- let(:params) { { ID: '0' * 256 } }
+ let(:lock_id) { '0' * 256 }
it 'returns an error' do
request
@@ -284,5 +320,16 @@ describe API::Terraform::State do
expect(response).to have_gitlab_http_status(:bad_request)
end
end
+
+ context 'user does not have permission to unlock the state' do
+ let(:lock_id) { '123-456' }
+ let(:current_user) { developer }
+
+ it 'returns an error' do
+ request
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
end
end
diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb
index 0bdc71a30e9..dfd0e13d84c 100644
--- a/spec/requests/api/todos_spec.rb
+++ b/spec/requests/api/todos_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Todos do
+RSpec.describe API::Todos do
let_it_be(:group) { create(:group) }
let_it_be(:project_1) { create(:project, :repository, group: group) }
let_it_be(:project_2) { create(:project) }
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index 19b01cb7913..c51358bf659 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Triggers do
+RSpec.describe API::Triggers do
let_it_be(:user) { create(:user) }
let_it_be(:user2) { create(:user) }
diff --git a/spec/requests/api/user_counts_spec.rb b/spec/requests/api/user_counts_spec.rb
index 688dfe11115..94e25d647fc 100644
--- a/spec/requests/api/user_counts_spec.rb
+++ b/spec/requests/api/user_counts_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::UserCounts do
+RSpec.describe API::UserCounts do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index e780f67bcab..17f9112c1d5 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Users, :do_not_mock_admin_mode do
+RSpec.describe API::Users, :do_not_mock_admin_mode do
let_it_be(:admin) { create(:admin) }
let_it_be(:user, reload: true) { create(:user, username: 'user.with.dot') }
let_it_be(:key) { create(:key, user: user) }
@@ -910,6 +910,14 @@ describe API::Users, :do_not_mock_admin_mode do
expect(user.reload.bio).to eq('')
end
+ it 'updates user with nil bio' do
+ put api("/users/#{user.id}", admin), params: { bio: nil }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['bio']).to eq('')
+ expect(user.reload.bio).to eq('')
+ end
+
it "updates user with new password and forces reset on next login" do
put api("/users/#{user.id}", admin), params: { password: '12345678' }
diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb
index f209a1d2e6e..7bb73e9664b 100644
--- a/spec/requests/api/variables_spec.rb
+++ b/spec/requests/api/variables_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Variables do
+RSpec.describe API::Variables do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
@@ -54,6 +54,59 @@ describe API::Variables do
expect(response).to have_gitlab_http_status(:not_found)
end
+
+ context 'when there are two variables with the same key on different env' do
+ let!(:var1) { create(:ci_variable, project: project, key: 'key1', environment_scope: 'staging') }
+ let!(:var2) { create(:ci_variable, project: project, key: 'key1', environment_scope: 'production') }
+
+ context 'when filter[environment_scope] is not passed' do
+ context 'FF ci_variables_api_filter_environment_scope is enabled' do
+ it 'returns 409' do
+ get api("/projects/#{project.id}/variables/key1", user)
+
+ expect(response).to have_gitlab_http_status(:conflict)
+ end
+ end
+
+ context 'FF ci_variables_api_filter_environment_scope is disabled' do
+ before do
+ stub_feature_flags(ci_variables_api_filter_environment_scope: false)
+ end
+
+ it 'returns random one' do
+ get api("/projects/#{project.id}/variables/key1", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['key']).to eq('key1')
+ end
+ end
+ end
+
+ context 'when filter[environment_scope] is passed' do
+ it 'returns the variable' do
+ get api("/projects/#{project.id}/variables/key1", user), params: { 'filter[environment_scope]': 'production' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['value']).to eq(var2.value)
+ end
+ end
+
+ context 'when wrong filter[environment_scope] is passed' do
+ it 'returns not_found' do
+ get api("/projects/#{project.id}/variables/key1", user), params: { 'filter[environment_scope]': 'invalid' }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ context 'when there is only one variable with provided key' do
+ it 'returns not_found' do
+ get api("/projects/#{project.id}/variables/#{variable.key}", user), params: { 'filter[environment_scope]': 'invalid' }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+ end
end
context 'authorized user with invalid permissions' do
@@ -173,6 +226,52 @@ describe API::Variables do
expect(response).to have_gitlab_http_status(:not_found)
end
+
+ context 'when there are two variables with the same key on different env' do
+ let!(:var1) { create(:ci_variable, project: project, key: 'key1', environment_scope: 'staging') }
+ let!(:var2) { create(:ci_variable, project: project, key: 'key1', environment_scope: 'production') }
+
+ context 'when filter[environment_scope] is not passed' do
+ context 'FF ci_variables_api_filter_environment_scope is enabled' do
+ it 'returns 409' do
+ get api("/projects/#{project.id}/variables/key1", user)
+
+ expect(response).to have_gitlab_http_status(:conflict)
+ end
+ end
+
+ context 'FF ci_variables_api_filter_environment_scope is disabled' do
+ before do
+ stub_feature_flags(ci_variables_api_filter_environment_scope: false)
+ end
+
+ it 'updates random one' do
+ put api("/projects/#{project.id}/variables/key1", user), params: { value: 'new_val' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['value']).to eq('new_val')
+ end
+ end
+ end
+
+ context 'when filter[environment_scope] is passed' do
+ it 'updates the variable' do
+ put api("/projects/#{project.id}/variables/key1", user), params: { value: 'new_val', 'filter[environment_scope]': 'production' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(var1.reload.value).not_to eq('new_val')
+ expect(var2.reload.value).to eq('new_val')
+ end
+ end
+
+ context 'when wrong filter[environment_scope] is passed' do
+ it 'returns not_found' do
+ put api("/projects/#{project.id}/variables/key1", user), params: { value: 'new_val', 'filter[environment_scope]': 'invalid' }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
end
context 'authorized user with invalid permissions' do
@@ -207,6 +306,56 @@ describe API::Variables do
expect(response).to have_gitlab_http_status(:not_found)
end
+
+ context 'when there are two variables with the same key on different env' do
+ let!(:var1) { create(:ci_variable, project: project, key: 'key1', environment_scope: 'staging') }
+ let!(:var2) { create(:ci_variable, project: project, key: 'key1', environment_scope: 'production') }
+
+ context 'when filter[environment_scope] is not passed' do
+ context 'FF ci_variables_api_filter_environment_scope is enabled' do
+ it 'returns 409' do
+ get api("/projects/#{project.id}/variables/key1", user)
+
+ expect(response).to have_gitlab_http_status(:conflict)
+ end
+ end
+
+ context 'FF ci_variables_api_filter_environment_scope is disabled' do
+ before do
+ stub_feature_flags(ci_variables_api_filter_environment_scope: false)
+ end
+
+ it 'deletes random one' do
+ expect do
+ delete api("/projects/#{project.id}/variables/key1", user), params: { 'filter[environment_scope]': 'production' }
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end.to change {project.variables.count}.by(-1)
+ end
+ end
+ end
+
+ context 'when filter[environment_scope] is passed' do
+ it 'deletes the variable' do
+ expect do
+ delete api("/projects/#{project.id}/variables/key1", user), params: { 'filter[environment_scope]': 'production' }
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end.to change {project.variables.count}.by(-1)
+
+ expect(var1.reload).to be_present
+ expect { var2.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ context 'when wrong filter[environment_scope] is passed' do
+ it 'returns not_found' do
+ delete api("/projects/#{project.id}/variables/key1", user), params: { 'filter[environment_scope]': 'invalid' }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
end
context 'authorized user with invalid permissions' do
diff --git a/spec/requests/api/version_spec.rb b/spec/requests/api/version_spec.rb
index 9eb8c03e273..a0a0f66c8d1 100644
--- a/spec/requests/api/version_spec.rb
+++ b/spec/requests/api/version_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Version do
+RSpec.describe API::Version do
shared_examples_for 'GET /version' do
context 'when unauthenticated' do
it 'returns authentication error' do
diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb
index 43a5cb446bb..f271f8aa853 100644
--- a/spec/requests/api/wikis_spec.rb
+++ b/spec/requests/api/wikis_spec.rb
@@ -12,7 +12,7 @@ require 'spec_helper'
# - maintainer
# because they are 3 edge cases of using wiki pages.
-describe API::Wikis do
+RSpec.describe API::Wikis do
include WorkhorseHelpers
let(:user) { create(:user) }
@@ -21,178 +21,10 @@ describe API::Wikis do
let(:payload) { { content: 'content', format: 'rdoc', title: 'title' } }
let(:expected_keys_with_content) { %w(content format slug title) }
let(:expected_keys_without_content) { %w(format slug title) }
+ let(:wiki) { project_wiki }
- shared_examples_for 'returns list of wiki pages' do
- context 'when wiki has pages' do
- let!(:pages) do
- [create(:wiki_page, wiki: project_wiki, title: 'page1', content: 'content of page1'),
- create(:wiki_page, wiki: project_wiki, title: 'page2.with.dot', content: 'content of page2')]
- end
-
- it 'returns the list of wiki pages without content' do
- get api(url, user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.size).to eq(2)
-
- json_response.each_with_index do |page, index|
- expect(page.keys).to match_array(expected_keys_without_content)
- expect(page['slug']).to eq(pages[index].slug)
- expect(page['title']).to eq(pages[index].title)
- end
- end
-
- it 'returns the list of wiki pages with content' do
- get api(url, user), params: { with_content: 1 }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.size).to eq(2)
-
- json_response.each_with_index do |page, index|
- expect(page.keys).to match_array(expected_keys_with_content)
- expect(page['content']).to eq(pages[index].content)
- expect(page['slug']).to eq(pages[index].slug)
- expect(page['title']).to eq(pages[index].title)
- end
- end
- end
-
- it 'return the empty list of wiki pages' do
- get api(url, user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.size).to eq(0)
- end
- end
-
- shared_examples_for 'returns wiki page' do
- it 'returns the wiki page' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.size).to eq(4)
- expect(json_response.keys).to match_array(expected_keys_with_content)
- expect(json_response['content']).to eq(page.content)
- expect(json_response['slug']).to eq(page.slug)
- expect(json_response['title']).to eq(page.title)
- end
- end
-
- shared_examples_for 'creates wiki page' do
- it 'creates the wiki page' do
- post(api(url, user), params: payload)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response.size).to eq(4)
- expect(json_response.keys).to match_array(expected_keys_with_content)
- expect(json_response['content']).to eq(payload[:content])
- expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
- expect(json_response['title']).to eq(payload[:title])
- expect(json_response['rdoc']).to eq(payload[:rdoc])
- end
-
- [:title, :content].each do |part|
- it "responds with validation error on empty #{part}" do
- payload.delete(part)
-
- post(api(url, user), params: payload)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response.size).to eq(1)
- expect(json_response['error']).to eq("#{part} is missing")
- end
- end
- end
-
- shared_examples_for 'updates wiki page' do
- it 'updates the wiki page' do
- put(api(url, user), params: payload)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.size).to eq(4)
- expect(json_response.keys).to match_array(expected_keys_with_content)
- expect(json_response['content']).to eq(payload[:content])
- expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
- expect(json_response['title']).to eq(payload[:title])
- end
-
- [:title, :content, :format].each do |part|
- it "updates with wiki with missing #{part}" do
- payload.delete(part)
-
- put(api(url, user), params: payload)
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
- end
-
- shared_examples_for '403 Forbidden' do
- it 'returns 403 Forbidden' do
- expect(response).to have_gitlab_http_status(:forbidden)
- expect(json_response.size).to eq(1)
- expect(json_response['message']).to eq('403 Forbidden')
- end
- end
-
- shared_examples_for '404 Wiki Page Not Found' do
- it 'returns 404 Wiki Page Not Found' do
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response.size).to eq(1)
- expect(json_response['message']).to eq('404 Wiki Page Not Found')
- end
- end
-
- shared_examples_for '404 Project Not Found' do
- it 'returns 404 Project Not Found' do
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response.size).to eq(1)
- expect(json_response['message']).to eq('404 Project Not Found')
- end
- end
-
- shared_examples_for '204 No Content' do
- it 'returns 204 No Content' do
- expect(response).to have_gitlab_http_status(:no_content)
- end
- end
-
- shared_examples_for 'uploads wiki attachment' do
- it 'pushes attachment to the wiki repository' do
- allow(SecureRandom).to receive(:hex).and_return('fixed_hex')
-
- workhorse_post_with_file(api(url, user), file_key: :file, params: payload)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to eq result_hash.deep_stringify_keys
- end
-
- it 'responds with validation error on empty file' do
- payload.delete(:file)
-
- post(api(url, user), params: payload)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response.size).to eq(1)
- expect(json_response['error']).to eq('file is missing')
- end
-
- it 'responds with validation error on invalid temp file' do
- payload[:file] = { tempfile: '/etc/hosts' }
-
- post(api(url, user), params: payload)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response.size).to eq(1)
- expect(json_response['error']).to eq('file is invalid')
- end
-
- it 'is backward compatible with regular multipart uploads' do
- allow(SecureRandom).to receive(:hex).and_return('fixed_hex')
-
- post(api(url, user), params: payload)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to eq result_hash.deep_stringify_keys
- end
+ shared_examples_for 'wiki API 404 Project Not Found' do
+ include_examples 'wiki API 404 Not Found', 'Project'
end
describe 'GET /projects/:id/wikis' do
@@ -206,7 +38,7 @@ describe API::Wikis do
get api(url)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -216,7 +48,7 @@ describe API::Wikis do
get api(url, user)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -226,7 +58,7 @@ describe API::Wikis do
get api(url, user)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
end
@@ -238,7 +70,7 @@ describe API::Wikis do
get api(url)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -246,7 +78,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'returns list of wiki pages'
+ include_examples 'wikis API returns list of wiki pages'
end
context 'when user is maintainer' do
@@ -254,7 +86,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'returns list of wiki pages'
+ include_examples 'wikis API returns list of wiki pages'
end
end
@@ -266,7 +98,7 @@ describe API::Wikis do
get api(url)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -274,7 +106,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'returns list of wiki pages'
+ include_examples 'wikis API returns list of wiki pages'
end
context 'when user is maintainer' do
@@ -282,7 +114,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'returns list of wiki pages'
+ include_examples 'wikis API returns list of wiki pages'
end
end
end
@@ -299,7 +131,7 @@ describe API::Wikis do
get api(url)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -309,7 +141,7 @@ describe API::Wikis do
get api(url, user)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -319,7 +151,7 @@ describe API::Wikis do
get api(url, user)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
end
@@ -331,7 +163,7 @@ describe API::Wikis do
get api(url)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -340,12 +172,12 @@ describe API::Wikis do
get api(url, user)
end
- include_examples 'returns wiki page'
+ include_examples 'wikis API returns wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
@@ -356,12 +188,12 @@ describe API::Wikis do
get api(url, user)
end
- include_examples 'returns wiki page'
+ include_examples 'wikis API returns wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
end
@@ -374,7 +206,7 @@ describe API::Wikis do
get api(url)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -384,12 +216,12 @@ describe API::Wikis do
get api(url, user)
end
- include_examples 'returns wiki page'
+ include_examples 'wikis API returns wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
@@ -400,12 +232,12 @@ describe API::Wikis do
get api(url, user)
end
- include_examples 'returns wiki page'
+ include_examples 'wikis API returns wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
end
@@ -423,7 +255,7 @@ describe API::Wikis do
post(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -432,7 +264,7 @@ describe API::Wikis do
post(api(url, user), params: payload)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -441,7 +273,7 @@ describe API::Wikis do
post(api(url, user), params: payload)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
end
@@ -453,7 +285,7 @@ describe API::Wikis do
post(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -461,7 +293,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'creates wiki page'
+ include_examples 'wikis API creates wiki page'
end
context 'when user is maintainer' do
@@ -469,7 +301,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'creates wiki page'
+ include_examples 'wikis API creates wiki page'
end
end
@@ -481,7 +313,7 @@ describe API::Wikis do
post(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -489,7 +321,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'creates wiki page'
+ include_examples 'wikis API creates wiki page'
end
context 'when user is maintainer' do
@@ -497,7 +329,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'creates wiki page'
+ include_examples 'wikis API creates wiki page'
end
end
end
@@ -515,7 +347,7 @@ describe API::Wikis do
put(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -525,7 +357,7 @@ describe API::Wikis do
put(api(url, user), params: payload)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -535,7 +367,7 @@ describe API::Wikis do
put(api(url, user), params: payload)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
end
@@ -547,7 +379,7 @@ describe API::Wikis do
put(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -555,7 +387,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'updates wiki page'
+ include_examples 'wikis API updates wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
@@ -564,7 +396,7 @@ describe API::Wikis do
put(api(url, user), params: payload)
end
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
@@ -573,7 +405,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'updates wiki page'
+ include_examples 'wikis API updates wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
@@ -582,7 +414,7 @@ describe API::Wikis do
put(api(url, user), params: payload)
end
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
end
@@ -595,7 +427,7 @@ describe API::Wikis do
put(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -603,7 +435,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'updates wiki page'
+ include_examples 'wikis API updates wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
@@ -612,7 +444,7 @@ describe API::Wikis do
put(api(url, user), params: payload)
end
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
@@ -621,7 +453,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'updates wiki page'
+ include_examples 'wikis API updates wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
@@ -630,7 +462,7 @@ describe API::Wikis do
put(api(url, user), params: payload)
end
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
end
@@ -638,7 +470,7 @@ describe API::Wikis do
context 'when wiki belongs to a group project' do
let(:project) { create(:project, :wiki_repo, namespace: group) }
- include_examples 'updates wiki page'
+ include_examples 'wikis API updates wiki page'
end
end
@@ -654,7 +486,7 @@ describe API::Wikis do
delete(api(url))
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -664,7 +496,7 @@ describe API::Wikis do
delete(api(url, user))
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -674,7 +506,7 @@ describe API::Wikis do
delete(api(url, user))
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
end
@@ -686,7 +518,7 @@ describe API::Wikis do
delete(api(url))
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -696,7 +528,7 @@ describe API::Wikis do
delete(api(url, user))
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -706,7 +538,7 @@ describe API::Wikis do
delete(api(url, user))
end
- include_examples '204 No Content'
+ include_examples 'wiki API 204 No Content'
end
end
@@ -718,7 +550,7 @@ describe API::Wikis do
delete(api(url))
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -728,7 +560,7 @@ describe API::Wikis do
delete(api(url, user))
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -738,12 +570,12 @@ describe API::Wikis do
delete(api(url, user))
end
- include_examples '204 No Content'
+ include_examples 'wiki API 204 No Content'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
end
@@ -755,7 +587,7 @@ describe API::Wikis do
delete(api(url, user))
end
- include_examples '204 No Content'
+ include_examples 'wiki API 204 No Content'
end
end
@@ -783,7 +615,7 @@ describe API::Wikis do
post(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -792,7 +624,7 @@ describe API::Wikis do
post(api(url, user), params: payload)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -801,7 +633,7 @@ describe API::Wikis do
post(api(url, user), params: payload)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
end
@@ -813,7 +645,7 @@ describe API::Wikis do
post(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -821,7 +653,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'uploads wiki attachment'
+ include_examples 'wiki API uploads wiki attachment'
end
context 'when user is maintainer' do
@@ -829,7 +661,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'uploads wiki attachment'
+ include_examples 'wiki API uploads wiki attachment'
end
end
@@ -841,7 +673,7 @@ describe API::Wikis do
post(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -849,7 +681,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'uploads wiki attachment'
+ include_examples 'wiki API uploads wiki attachment'
end
context 'when user is maintainer' do
@@ -857,7 +689,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'uploads wiki attachment'
+ include_examples 'wiki API uploads wiki attachment'
end
end
end
diff --git a/spec/requests/boards/lists_controller_spec.rb b/spec/requests/boards/lists_controller_spec.rb
index 7451ad93efd..4d9f1dace4d 100644
--- a/spec/requests/boards/lists_controller_spec.rb
+++ b/spec/requests/boards/lists_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::ListsController do
+RSpec.describe Boards::ListsController do
describe '#index' do
let(:board) { create(:board) }
let(:user) { board.project.owner }
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index 183a3545cf2..d387c6df4cf 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Git HTTP requests' do
+RSpec.describe 'Git HTTP requests' do
include ProjectForksHelper
include TermsHelper
include GitHttpHelpers
diff --git a/spec/requests/groups/milestones_controller_spec.rb b/spec/requests/groups/milestones_controller_spec.rb
index 1c6743dc678..2c4d97ec4c3 100644
--- a/spec/requests/groups/milestones_controller_spec.rb
+++ b/spec/requests/groups/milestones_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::MilestonesController do
+RSpec.describe Groups::MilestonesController do
context 'N+1 DB queries' do
let(:user) { create(:user) }
let!(:public_group) { create(:group, :public) }
diff --git a/spec/requests/groups/registry/repositories_controller_spec.rb b/spec/requests/groups/registry/repositories_controller_spec.rb
index ab59b006be7..89cbd3e4100 100644
--- a/spec/requests/groups/registry/repositories_controller_spec.rb
+++ b/spec/requests/groups/registry/repositories_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::Registry::RepositoriesController do
+RSpec.describe Groups::Registry::RepositoriesController do
let_it_be(:group, reload: true) { create(:group) }
let_it_be(:user) { create(:user) }
diff --git a/spec/requests/health_controller_spec.rb b/spec/requests/health_controller_spec.rb
index 6ee716e0e89..592a57bc637 100644
--- a/spec/requests/health_controller_spec.rb
+++ b/spec/requests/health_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe HealthController do
+RSpec.describe HealthController do
include StubENV
let(:token) { Gitlab::CurrentSettings.health_check_access_token }
@@ -129,6 +129,40 @@ describe HealthController do
expect(response).to have_gitlab_http_status(:service_unavailable)
expect(response.headers['X-GitLab-Custom-Error']).to eq(1)
end
+
+ context 'when DB is not accessible and connection raises an exception' do
+ before do
+ expect(Gitlab::HealthChecks::DbCheck)
+ .to receive(:readiness)
+ .and_raise(PG::ConnectionBad, 'could not connect to server')
+ end
+
+ it 'responds with 500 including the exception info' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:internal_server_error)
+ expect(response.headers['X-GitLab-Custom-Error']).to eq(1)
+ expect(json_response).to eq(
+ { 'status' => 'failed', 'message' => 'PG::ConnectionBad : could not connect to server' })
+ end
+ end
+
+ context 'when any exception happens during the probing' do
+ before do
+ expect(Gitlab::HealthChecks::Redis::RedisCheck)
+ .to receive(:readiness)
+ .and_raise(::Redis::CannotConnectError, 'Redis down')
+ end
+
+ it 'responds with 500 including the exception info' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:internal_server_error)
+ expect(response.headers['X-GitLab-Custom-Error']).to eq(1)
+ expect(json_response).to eq(
+ { 'status' => 'failed', 'message' => 'Redis::CannotConnectError : Redis down' })
+ end
+ end
end
end
diff --git a/spec/requests/import/gitlab_groups_controller_spec.rb b/spec/requests/import/gitlab_groups_controller_spec.rb
index 35f2bf0c2f7..4125c5c7c7a 100644
--- a/spec/requests/import/gitlab_groups_controller_spec.rb
+++ b/spec/requests/import/gitlab_groups_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Import::GitlabGroupsController do
+RSpec.describe Import::GitlabGroupsController do
include WorkhorseHelpers
let(:import_path) { "#{Dir.tmpdir}/gitlab_groups_controller_spec" }
@@ -16,6 +16,8 @@ describe Import::GitlabGroupsController do
expect(import_export).to receive(:storage_path).and_return(import_path)
end
+ allow(Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(false)
+
stub_uploads_object_storage(ImportExportUploader)
end
diff --git a/spec/requests/import/gitlab_projects_controller_spec.rb b/spec/requests/import/gitlab_projects_controller_spec.rb
index f16755e9766..c1ac5a9f2c8 100644
--- a/spec/requests/import/gitlab_projects_controller_spec.rb
+++ b/spec/requests/import/gitlab_projects_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Import::GitlabProjectsController do
+RSpec.describe Import::GitlabProjectsController do
include WorkhorseHelpers
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
index 617587e2fa6..fe6c0f0a556 100644
--- a/spec/requests/jwt_controller_spec.rb
+++ b/spec/requests/jwt_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe JwtController do
+RSpec.describe JwtController do
include_context 'parsed logs'
let(:service) { double(execute: {}) }
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index f3fa5e36fec..f7771c7b0f9 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'Git LFS API and storage' do
+RSpec.describe 'Git LFS API and storage' do
include LfsHttpHelpers
include ProjectForksHelper
include WorkhorseHelpers
diff --git a/spec/requests/lfs_locks_api_spec.rb b/spec/requests/lfs_locks_api_spec.rb
index 41cf1a80205..34e345cb1cf 100644
--- a/spec/requests/lfs_locks_api_spec.rb
+++ b/spec/requests/lfs_locks_api_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Git LFS File Locking API' do
+RSpec.describe 'Git LFS File Locking API' do
include WorkhorseHelpers
let(:project) { create(:project) }
diff --git a/spec/requests/oauth_tokens_spec.rb b/spec/requests/oauth_tokens_spec.rb
index bb1c25d686e..6d944bbc783 100644
--- a/spec/requests/oauth_tokens_spec.rb
+++ b/spec/requests/oauth_tokens_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'OAuth Tokens requests' do
+RSpec.describe 'OAuth Tokens requests' do
let(:user) { create :user }
let(:application) { create :oauth_application, scopes: 'api' }
diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb
index 785ab98a3d0..7b682d76150 100644
--- a/spec/requests/openid_connect_spec.rb
+++ b/spec/requests/openid_connect_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'OpenID Connect requests' do
+RSpec.describe 'OpenID Connect requests' do
let(:user) do
create(
:user,
@@ -146,8 +146,16 @@ describe 'OpenID Connect requests' do
expect(@payload['auth_time']).to eq user.current_sign_in_at.to_i
end
+ it 'has public email in email claim' do
+ expect(@payload['email']).to eq(user.public_email)
+ end
+
+ it 'has true in email_verified claim' do
+ expect(@payload['email_verified']).to eq(true)
+ end
+
it 'does not include any unknown properties' do
- expect(@payload.keys).to eq %w[iss sub aud exp iat auth_time sub_legacy]
+ expect(@payload.keys).to eq %w[iss sub aud exp iat auth_time sub_legacy email email_verified]
end
end
@@ -211,5 +219,20 @@ describe 'OpenID Connect requests' do
expect(json_response['email_verified']).to eq(true)
end
end
+
+ context 'ID token payload' do
+ before do
+ request_access_token!
+ @payload = JSON::JWT.decode(json_response['id_token'], :skip_verification)
+ end
+
+ it 'has private email in email claim' do
+ expect(@payload['email']).to eq(user.email)
+ end
+
+ it 'has true in email_verified claim' do
+ expect(@payload['email_verified']).to eq(true)
+ end
+ end
end
end
diff --git a/spec/requests/product_analytics/collector_app_attack_spec.rb b/spec/requests/product_analytics/collector_app_attack_spec.rb
new file mode 100644
index 00000000000..6f86e39c295
--- /dev/null
+++ b/spec/requests/product_analytics/collector_app_attack_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'ProductAnalytics::CollectorApp throttle' do
+ include RackAttackSpecHelpers
+
+ include_context 'rack attack cache store'
+
+ let(:project1) { create(:project) }
+ let(:project2) { create(:project) }
+
+ before do
+ allow(ProductAnalyticsEvent).to receive(:create).and_return(true)
+ end
+
+ context 'per application id' do
+ let(:params) do
+ {
+ aid: project1.id,
+ eid: SecureRandom.uuid
+ }
+ end
+
+ it 'throttles the endpoint' do
+ # Allow requests under the rate limit.
+ 100.times do
+ expect_ok { get '/-/collector/i', params: params }
+ end
+
+ # Ensure its not related to ip address
+ random_next_ip
+
+ # Reject request over the limit
+ expect_rejection { get '/-/collector/i', params: params }
+
+ # But allows request for different aid
+ expect_ok { get '/-/collector/i', params: params.merge(aid: project2.id) }
+ end
+ end
+end
diff --git a/spec/requests/product_analytics/collector_app_spec.rb b/spec/requests/product_analytics/collector_app_spec.rb
new file mode 100644
index 00000000000..0491c2564f0
--- /dev/null
+++ b/spec/requests/product_analytics/collector_app_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'ProductAnalytics::CollectorApp' do
+ let_it_be(:project) { create(:project) }
+ let(:params) { {} }
+
+ subject { get '/-/collector/i', params: params }
+
+ RSpec.shared_examples 'not found' do
+ it 'repond with 404' do
+ expect { subject }.not_to change { ProductAnalyticsEvent.count }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'correct event params' do
+ let(:params) do
+ {
+ aid: project.id,
+ p: 'web',
+ tna: 'sp',
+ tv: 'js-2.14.0',
+ eid: SecureRandom.uuid,
+ duid: SecureRandom.uuid,
+ sid: SecureRandom.uuid,
+ vid: 4,
+ url: 'http://example.com/products/1',
+ refr: 'http://example.com/products/1',
+ lang: 'en-US',
+ cookie: true,
+ tz: 'America/Los_Angeles',
+ cs: 'UTF-8'
+ }
+ end
+
+ it 'repond with 200' do
+ expect { subject }.to change { ProductAnalyticsEvent.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ context 'feature disabled' do
+ before do
+ stub_feature_flags(product_analytics: false)
+ end
+
+ it_behaves_like 'not found'
+ end
+ end
+
+ context 'empty event params' do
+ it_behaves_like 'not found'
+ end
+
+ context 'invalid project id in params' do
+ let(:params) do
+ {
+ aid: '-1',
+ p: 'web',
+ tna: 'sp',
+ tv: 'js-2.14.0',
+ eid: SecureRandom.uuid,
+ duid: SecureRandom.uuid,
+ sid: SecureRandom.uuid
+ }
+ end
+
+ it_behaves_like 'not found'
+ end
+end
diff --git a/spec/requests/profiles/notifications_controller_spec.rb b/spec/requests/profiles/notifications_controller_spec.rb
index 0b2741677ab..d60cee00aef 100644
--- a/spec/requests/profiles/notifications_controller_spec.rb
+++ b/spec/requests/profiles/notifications_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'view user notifications' do
+RSpec.describe 'view user notifications' do
let(:user) do
create(:user) do |user|
user.emails.create(email: 'original@example.com', confirmed_at: Time.current)
diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb
index 773f243e733..8c3058d405c 100644
--- a/spec/requests/projects/cycle_analytics_events_spec.rb
+++ b/spec/requests/projects/cycle_analytics_events_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'value stream analytics events' do
+RSpec.describe 'value stream analytics events' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository, public_builds: false) }
let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
diff --git a/spec/requests/projects/incident_management/pagerduty_incidents_spec.rb b/spec/requests/projects/incident_management/pagerduty_incidents_spec.rb
new file mode 100644
index 00000000000..c246aacb4c7
--- /dev/null
+++ b/spec/requests/projects/incident_management/pagerduty_incidents_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'PagerDuty webhook' do
+ let_it_be(:project) { create(:project) }
+
+ describe 'POST /incidents/pagerduty' do
+ let(:payload) { Gitlab::Json.parse(fixture_file('pager_duty/webhook_incident_trigger.json')) }
+ let(:webhook_processor_class) { ::IncidentManagement::PagerDuty::ProcessWebhookService }
+ let(:webhook_processor) { instance_double(webhook_processor_class) }
+
+ def make_request
+ headers = { 'Content-Type' => 'application/json' }
+ post project_incidents_pagerduty_url(project, token: 'VALID-TOKEN'), params: payload.to_json, headers: headers
+ end
+
+ before do
+ allow(webhook_processor_class).to receive(:new).and_return(webhook_processor)
+ allow(webhook_processor).to receive(:execute).and_return(ServiceResponse.success(http_status: :accepted))
+ end
+
+ it 'calls PagerDuty webhook processor with correct parameters' do
+ make_request
+
+ expect(webhook_processor_class).to have_received(:new).with(project, nil, payload)
+ expect(webhook_processor).to have_received(:execute).with('VALID-TOKEN')
+ end
+
+ it 'responds with 202 Accepted' do
+ make_request
+
+ expect(response).to have_gitlab_http_status(:accepted)
+ end
+ end
+end
diff --git a/spec/requests/projects/merge_requests/creations_spec.rb b/spec/requests/projects/merge_requests/creations_spec.rb
index d192e1bca7f..0a3e663444f 100644
--- a/spec/requests/projects/merge_requests/creations_spec.rb
+++ b/spec/requests/projects/merge_requests/creations_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'merge requests creations' do
+RSpec.describe 'merge requests creations' do
describe 'GET /:namespace/:project/merge_requests/new' do
include ProjectForksHelper
diff --git a/spec/requests/projects/merge_requests_discussions_spec.rb b/spec/requests/projects/merge_requests_discussions_spec.rb
index 94e9c81bc3b..6ec586ed22c 100644
--- a/spec/requests/projects/merge_requests_discussions_spec.rb
+++ b/spec/requests/projects/merge_requests_discussions_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'merge requests discussions' do
+RSpec.describe 'merge requests discussions' do
# Further tests can be found at merge_requests_controller_spec.rb
describe 'GET /:namespace/:project/-/merge_requests/:iid/discussions' do
let(:project) { create(:project, :repository) }
diff --git a/spec/requests/projects/metrics_dashboard_spec.rb b/spec/requests/projects/metrics_dashboard_spec.rb
new file mode 100644
index 00000000000..ab35788387c
--- /dev/null
+++ b/spec/requests/projects/metrics_dashboard_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'metrics dashboard page' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:environment) { create(:environment, project: project) }
+ let_it_be(:environment2) { create(:environment, project: project) }
+ let_it_be(:user) { project.owner }
+
+ before do
+ project.add_developer(user)
+ login_as(user)
+ end
+
+ describe 'GET /:namespace/:project/-/metrics' do
+ it 'returns 200' do
+ send_request
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'assigns environment' do
+ send_request
+ expect(assigns(:environment).id).to eq(environment.id)
+ end
+ end
+
+ describe 'GET /:namespace/:project/-/metrics?environment=:environment.id' do
+ it 'returns 200' do
+ send_request(environment: environment2.id)
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'assigns query param environment' do
+ send_request(environment: environment2.id)
+ expect(assigns(:environment).id).to eq(environment2.id)
+ end
+
+ context 'when query param environment does not exist' do
+ it 'responds with 404' do
+ send_request(environment: 99)
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ describe 'GET /:namespace/:project/-/metrics/:dashboard_path' do
+ let(:dashboard_path) { '.gitlab/dashboards/dashboard_path.yml' }
+
+ it 'returns 200' do
+ send_request(dashboard_path: dashboard_path)
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'assigns environment' do
+ send_request(dashboard_path: dashboard_path)
+ expect(assigns(:environment).id).to eq(environment.id)
+ end
+ end
+
+ describe 'GET :/namespace/:project/-/metrics/:dashboard_path?environment=:environment.id' do
+ let(:dashboard_path) { '.gitlab/dashboards/dashboard_path.yml' }
+
+ it 'returns 200' do
+ send_request(dahboard_path: dashboard_path, environment: environment.id)
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'assigns query param environment' do
+ send_request(dashboard_path: dashboard_path, environment: environment2.id)
+ expect(assigns(:environment).id).to eq(environment2.id)
+ end
+
+ context 'when query param environment does not exist' do
+ it 'responds with 404' do
+ send_request(dashboard_path: dashboard_path, environment: 99)
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ def send_request(params = {})
+ get namespace_project_metrics_dashboard_path(namespace_id: project.namespace, project_id: project, **params)
+ end
+end
diff --git a/spec/requests/projects/uploads_spec.rb b/spec/requests/projects/uploads_spec.rb
index aca4644289d..de5ef36be7e 100644
--- a/spec/requests/projects/uploads_spec.rb
+++ b/spec/requests/projects/uploads_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'File uploads' do
+RSpec.describe 'File uploads' do
include WorkhorseHelpers
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb
index 175c5dd0088..106515a6b13 100644
--- a/spec/requests/rack_attack_global_spec.rb
+++ b/spec/requests/rack_attack_global_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Rack Attack global throttles' do
+RSpec.describe 'Rack Attack global throttles' do
include RackAttackSpecHelpers
let(:settings) { Gitlab::CurrentSettings.current_application_settings }
diff --git a/spec/requests/request_profiler_spec.rb b/spec/requests/request_profiler_spec.rb
index 36ccfc6b400..7f9999bf3d2 100644
--- a/spec/requests/request_profiler_spec.rb
+++ b/spec/requests/request_profiler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Request Profiler' do
+RSpec.describe 'Request Profiler' do
let(:user) { create(:user) }
shared_examples 'profiling a request' do |profile_type, extension|
diff --git a/spec/requests/self_monitoring_project_spec.rb b/spec/requests/self_monitoring_project_spec.rb
index 6a0258c349f..5844a27da17 100644
--- a/spec/requests/self_monitoring_project_spec.rb
+++ b/spec/requests/self_monitoring_project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Self-Monitoring project requests' do
+RSpec.describe 'Self-Monitoring project requests' do
let(:admin) { create(:admin) }
describe 'POST #create_self_monitoring_project' do
diff --git a/spec/requests/sessions_spec.rb b/spec/requests/sessions_spec.rb
index 6697700c37d..95df181b7b0 100644
--- a/spec/requests/sessions_spec.rb
+++ b/spec/requests/sessions_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Sessions' do
+RSpec.describe 'Sessions' do
context 'authentication', :allow_forgery_protection do
let(:user) { create(:user) }
diff --git a/spec/requests/user_activity_spec.rb b/spec/requests/user_activity_spec.rb
index b24760d475b..6f0726dbdc9 100644
--- a/spec/requests/user_activity_spec.rb
+++ b/spec/requests/user_activity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Update of user activity' do
+RSpec.describe 'Update of user activity' do
let(:user) { create(:user, last_activity_on: nil) }
before do
diff --git a/spec/requests/user_avatar_spec.rb b/spec/requests/user_avatar_spec.rb
index 66c7ce4d533..1397741af18 100644
--- a/spec/requests/user_avatar_spec.rb
+++ b/spec/requests/user_avatar_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Loading a user avatar' do
+RSpec.describe 'Loading a user avatar' do
let(:user) { create(:user, :with_avatar) }
context 'when logged in' do
diff --git a/spec/requests/user_spoofs_ip_spec.rb b/spec/requests/user_spoofs_ip_spec.rb
index 8da15665132..833dae78529 100644
--- a/spec/requests/user_spoofs_ip_spec.rb
+++ b/spec/requests/user_spoofs_ip_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'User spoofs their IP' do
+RSpec.describe 'User spoofs their IP' do
it 'raises a 400 error' do
get '/nonexistent', headers: { 'Client-Ip' => '1.2.3.4', 'X-Forwarded-For' => '5.6.7.8' }
diff --git a/spec/routing/admin/serverless/domains_controller_routing_spec.rb b/spec/routing/admin/serverless/domains_controller_routing_spec.rb
index 18c0db6add1..60b60809f4d 100644
--- a/spec/routing/admin/serverless/domains_controller_routing_spec.rb
+++ b/spec/routing/admin/serverless/domains_controller_routing_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Admin::Serverless::DomainsController do
+RSpec.describe Admin::Serverless::DomainsController do
it 'routes to #index' do
expect(get: '/admin/serverless/domains').to route_to('admin/serverless/domains#index')
end
diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb
index 25216b0c712..13e371ad68a 100644
--- a/spec/routing/admin_routing_spec.rb
+++ b/spec/routing/admin_routing_spec.rb
@@ -11,7 +11,7 @@ require 'spec_helper'
# admin_user GET /admin/users/:id(.:format) admin/users#show
# PUT /admin/users/:id(.:format) admin/users#update
# DELETE /admin/users/:id(.:format) admin/users#destroy
-describe Admin::UsersController, "routing" do
+RSpec.describe Admin::UsersController, "routing" do
it "to #block" do
expect(put("/admin/users/1/block")).to route_to('admin/users#block', id: '1')
end
@@ -58,7 +58,7 @@ end
# admin_project GET /admin/projects/:id(.:format) admin/projects#show {id: /[^\/]+/}
# PUT /admin/projects/:id(.:format) admin/projects#update {id: /[^\/]+/}
# DELETE /admin/projects/:id(.:format) admin/projects#destroy {id: /[^\/]+/}
-describe Admin::ProjectsController, "routing" do
+RSpec.describe Admin::ProjectsController, "routing" do
it "to #index" do
expect(get("/admin/projects")).to route_to('admin/projects#index')
end
@@ -75,7 +75,7 @@ end
# admin_hook DELETE /admin/hooks/:id(.:format) admin/hooks#destroy
# PUT /admin/hooks/:id(.:format) admin/hooks#update
# edit_admin_hook GET /admin/hooks/:id(.:format) admin/hooks#edit
-describe Admin::HooksController, "routing" do
+RSpec.describe Admin::HooksController, "routing" do
it "to #test" do
expect(post("/admin/hooks/1/test")).to route_to('admin/hooks#test', id: '1')
end
@@ -103,7 +103,7 @@ end
# admin_hook_hook_log_retry POST /admin/hooks/:hook_id/hook_logs/:id/retry(.:format) admin/hook_logs#retry
# admin_hook_hook_log GET /admin/hooks/:hook_id/hook_logs/:id(.:format) admin/hook_logs#show
-describe Admin::HookLogsController, 'routing' do
+RSpec.describe Admin::HookLogsController, 'routing' do
it 'to #retry' do
expect(post('/admin/hooks/1/hook_logs/1/retry')).to route_to('admin/hook_logs#retry', hook_id: '1', id: '1')
end
@@ -114,27 +114,27 @@ describe Admin::HookLogsController, 'routing' do
end
# admin_background_jobs GET /admin/background_jobs(.:format) admin/background_jobs#show
-describe Admin::BackgroundJobsController, "routing" do
+RSpec.describe Admin::BackgroundJobsController, "routing" do
it "to #show" do
expect(get("/admin/background_jobs")).to route_to('admin/background_jobs#show')
end
end
# admin_root /admin(.:format) admin/dashboard#index
-describe Admin::DashboardController, "routing" do
+RSpec.describe Admin::DashboardController, "routing" do
it "to #index" do
expect(get("/admin")).to route_to('admin/dashboard#index')
end
end
# admin_health_check GET /admin/health_check(.:format) admin/health_check#show
-describe Admin::HealthCheckController, "routing" do
+RSpec.describe Admin::HealthCheckController, "routing" do
it "to #show" do
expect(get("/admin/health_check")).to route_to('admin/health_check#show')
end
end
-describe Admin::GroupsController, "routing" do
+RSpec.describe Admin::GroupsController, "routing" do
let(:name) { 'complex.group-namegit' }
it "to #index" do
@@ -151,7 +151,7 @@ describe Admin::GroupsController, "routing" do
end
end
-describe Admin::SessionsController, "routing" do
+RSpec.describe Admin::SessionsController, "routing" do
it "to #new" do
expect(get("/admin/session/new")).to route_to('admin/sessions#new')
end
diff --git a/spec/routing/environments_spec.rb b/spec/routing/environments_spec.rb
index 46d4f31dd31..5ba02c384e2 100644
--- a/spec/routing/environments_spec.rb
+++ b/spec/routing/environments_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'environments routing' do
+RSpec.describe 'environments routing' do
let(:project) { create(:project) }
let(:environment) do
diff --git a/spec/routing/git_http_routing_spec.rb b/spec/routing/git_http_routing_spec.rb
index af14e5f81cb..e5216d99eb9 100644
--- a/spec/routing/git_http_routing_spec.rb
+++ b/spec/routing/git_http_routing_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'git_http routing' do
+RSpec.describe 'git_http routing' do
include RSpec::Rails::RequestExampleGroup
describe 'wiki.git routing', 'routing' do
diff --git a/spec/routing/group_routing_spec.rb b/spec/routing/group_routing_spec.rb
index 2a8454a276d..f4d5f899519 100644
--- a/spec/routing/group_routing_spec.rb
+++ b/spec/routing/group_routing_spec.rb
@@ -2,8 +2,8 @@
require 'spec_helper'
-describe "Groups", "routing" do
- let(:group_path) { 'complex.group-namegit' }
+RSpec.shared_examples 'groups routing' do
+ let(:group_path) { 'projects.abc123' }
let!(:group) { create(:group, path: group_path) }
it "to #show" do
@@ -56,3 +56,23 @@ describe "Groups", "routing" do
expect(get('/groups/gitlabhq/-/boards')).to route_to('groups/boards#index', group_id: 'gitlabhq')
end
end
+
+RSpec.describe "Groups", "routing" do
+ context 'complex group path with dot' do
+ include_examples 'groups routing' do
+ let(:group_path) { 'complex.group-namegit' }
+ end
+ end
+
+ context 'group path starting with help' do
+ include_examples 'groups routing' do
+ let(:group_path) { 'help.abc123' }
+ end
+ end
+
+ context 'group path starting with projects' do
+ include_examples 'groups routing' do
+ let(:group_path) { 'projects.abc123' }
+ end
+ end
+end
diff --git a/spec/routing/import_routing_spec.rb b/spec/routing/import_routing_spec.rb
index 7e78a1c0cd2..15d2f32de78 100644
--- a/spec/routing/import_routing_spec.rb
+++ b/spec/routing/import_routing_spec.rb
@@ -23,7 +23,7 @@ require 'spec_helper'
# let(:actions) { [:index] }
# let(:controller) { 'issues' }
# end
-shared_examples 'importer routing' do
+RSpec.shared_examples 'importer routing' do
let(:except_actions) { [] }
let(:is_realtime) { false }
@@ -62,7 +62,7 @@ end
# realtime_changes_import_github GET /import/github/realtime_changes(.:format) import/github#jobs
# import_github POST /import/github(.:format) import/github#create
# new_import_github GET /import/github/new(.:format) import/github#new
-describe Import::GithubController, 'routing' do
+RSpec.describe Import::GithubController, 'routing' do
it_behaves_like 'importer routing' do
let(:provider) { 'github' }
let(:is_realtime) { true }
@@ -78,7 +78,7 @@ end
# realtime_changes_import_gitea GET /import/gitea/realtime_changes(.:format) import/gitea#jobs
# import_gitea POST /import/gitea(.:format) import/gitea#create
# new_import_gitea GET /import/gitea/new(.:format) import/gitea#new
-describe Import::GiteaController, 'routing' do
+RSpec.describe Import::GiteaController, 'routing' do
it_behaves_like 'importer routing' do
let(:except_actions) { [:callback] }
let(:provider) { 'gitea' }
@@ -90,25 +90,39 @@ describe Import::GiteaController, 'routing' do
end
end
-# status_import_gitlab GET /import/gitlab/status(.:format) import/gitlab#status
-# callback_import_gitlab GET /import/gitlab/callback(.:format) import/gitlab#callback
-# jobs_import_gitlab GET /import/gitlab/jobs(.:format) import/gitlab#jobs
-# import_gitlab POST /import/gitlab(.:format) import/gitlab#create
-describe Import::GitlabController, 'routing' do
+# status_import_gitlab GET /import/gitlab/status(.:format) import/gitlab#status
+# callback_import_gitlab GET /import/gitlab/callback(.:format) import/gitlab#callback
+# realtime_changes_import_gitlab GET /import/gitlab/realtime_changes(.:format) import/gitlab#realtime_changes
+# import_gitlab POST /import/gitlab(.:format) import/gitlab#create
+RSpec.describe Import::GitlabController, 'routing' do
it_behaves_like 'importer routing' do
let(:except_actions) { [:new] }
let(:provider) { 'gitlab' }
+ let(:is_realtime) { true }
end
end
-# status_import_bitbucket GET /import/bitbucket/status(.:format) import/bitbucket#status
-# callback_import_bitbucket GET /import/bitbucket/callback(.:format) import/bitbucket#callback
-# jobs_import_bitbucket GET /import/bitbucket/jobs(.:format) import/bitbucket#jobs
-# import_bitbucket POST /import/bitbucket(.:format) import/bitbucket#create
-describe Import::BitbucketController, 'routing' do
+# status_import_bitbucket GET /import/bitbucket/status(.:format) import/bitbucket#status
+# callback_import_bitbucket GET /import/bitbucket/callback(.:format) import/bitbucket#callback
+# realtime_changes_import_bitbucket GET /import/bitbucket/realtime_changes(.:format) import/bitbucket#realtime_changes
+# import_bitbucket POST /import/bitbucket(.:format) import/bitbucket#create
+RSpec.describe Import::BitbucketController, 'routing' do
it_behaves_like 'importer routing' do
let(:except_actions) { [:new] }
let(:provider) { 'bitbucket' }
+ let(:is_realtime) { true }
+ end
+end
+
+# status_import_bitbucket_server GET /import/bitbucket_server/status(.:format) import/bitbucket_server#status
+# callback_import_bitbucket_server GET /import/bitbucket_server/callback(.:format) import/bitbucket_server#callback
+# realtime_changes_import_bitbucket_server GET /import/bitbucket_server/realtime_changes(.:format) import/bitbucket_server#realtime_changes
+# new_import_bitbucket_server GET /import/bitbucket_server/new(.:format) import/bitbucket_server#new
+# import_bitbucket_server POST /import/bitbucket_server(.:format) import/bitbucket_server#create
+RSpec.describe Import::BitbucketServerController, 'routing' do
+ it_behaves_like 'importer routing' do
+ let(:provider) { 'bitbucket_server' }
+ let(:is_realtime) { true }
end
end
@@ -119,7 +133,7 @@ end
# create_user_map_import_google_code POST /import/google_code/user_map(.:format) import/google_code#create_user_map
# import_google_code POST /import/google_code(.:format) import/google_code#create
# new_import_google_code GET /import/google_code/new(.:format) import/google_code#new
-describe Import::GoogleCodeController, 'routing' do
+RSpec.describe Import::GoogleCodeController, 'routing' do
it_behaves_like 'importer routing' do
let(:except_actions) { [:callback] }
let(:provider) { 'google_code' }
@@ -138,17 +152,18 @@ describe Import::GoogleCodeController, 'routing' do
end
end
-# status_import_fogbugz GET /import/fogbugz/status(.:format) import/fogbugz#status
-# callback_import_fogbugz POST /import/fogbugz/callback(.:format) import/fogbugz#callback
-# jobs_import_fogbugz GET /import/fogbugz/jobs(.:format) import/fogbugz#jobs
-# new_user_map_import_fogbugz GET /import/fogbugz/user_map(.:format) import/fogbugz#new_user_map
-# create_user_map_import_fogbugz POST /import/fogbugz/user_map(.:format) import/fogbugz#create_user_map
-# import_fogbugz POST /import/fogbugz(.:format) import/fogbugz#create
-# new_import_fogbugz GET /import/fogbugz/new(.:format) import/fogbugz#new
-describe Import::FogbugzController, 'routing' do
+# status_import_fogbugz GET /import/fogbugz/status(.:format) import/fogbugz#status
+# callback_import_fogbugz POST /import/fogbugz/callback(.:format) import/fogbugz#callback
+# realtime_changes_import_fogbugz GET /import/fogbugz/realtime_changes(.:format) import/fogbugz#realtime_changes
+# new_user_map_import_fogbugz GET /import/fogbugz/user_map(.:format) import/fogbugz#new_user_map
+# create_user_map_import_fogbugz POST /import/fogbugz/user_map(.:format) import/fogbugz#create_user_map
+# import_fogbugz POST /import/fogbugz(.:format) import/fogbugz#create
+# new_import_fogbugz GET /import/fogbugz/new(.:format) import/fogbugz#new
+RSpec.describe Import::FogbugzController, 'routing' do
it_behaves_like 'importer routing' do
let(:except_actions) { [:callback] }
let(:provider) { 'fogbugz' }
+ let(:is_realtime) { true }
end
it 'to #callback' do
@@ -167,7 +182,7 @@ end
# import_gitlab_project POST /import/gitlab_project(.:format) import/gitlab_projects#create
# POST /import/gitlab_project(.:format) import/gitlab_projects#create
# new_import_gitlab_project GET /import/gitlab_project/new(.:format) import/gitlab_projects#new
-describe Import::GitlabProjectsController, 'routing' do
+RSpec.describe Import::GitlabProjectsController, 'routing' do
it 'to #create' do
expect(post('/import/gitlab_project')).to route_to('import/gitlab_projects#create')
end
@@ -179,7 +194,7 @@ end
# new_import_phabricator GET /import/phabricator/new(.:format) import/phabricator#new
# import_phabricator POST /import/phabricator(.:format) import/phabricator#create
-describe Import::PhabricatorController, 'routing' do
+RSpec.describe Import::PhabricatorController, 'routing' do
it 'to #create' do
expect(post("/import/phabricator")).to route_to("import/phabricator#create")
end
diff --git a/spec/routing/instance_statistics_routing_spec.rb b/spec/routing/instance_statistics_routing_spec.rb
index 48a3ac4695c..7793c5cce71 100644
--- a/spec/routing/instance_statistics_routing_spec.rb
+++ b/spec/routing/instance_statistics_routing_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Instance Statistics', 'routing' do
+RSpec.describe 'Instance Statistics', 'routing' do
include RSpec::Rails::RequestExampleGroup
it "routes '/-/instance_statistics' to dev ops score" do
diff --git a/spec/routing/notifications_routing_spec.rb b/spec/routing/notifications_routing_spec.rb
index 8c2b29aabcb..f545badcdfa 100644
--- a/spec/routing/notifications_routing_spec.rb
+++ b/spec/routing/notifications_routing_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe "notifications routing" do
+RSpec.describe "notifications routing" do
it "routes to #show" do
expect(get("/profile/notifications")).to route_to("profiles/notifications#show")
end
diff --git a/spec/routing/openid_connect_spec.rb b/spec/routing/openid_connect_spec.rb
index fc170f8986c..b5291953730 100644
--- a/spec/routing/openid_connect_spec.rb
+++ b/spec/routing/openid_connect_spec.rb
@@ -6,7 +6,7 @@ require 'spec_helper'
# jwks GET /-/jwks(.:format) doorkeeper/openid_connect/discovery#keys
# oauth_discovery_provider GET /.well-known/openid-configuration(.:format) doorkeeper/openid_connect/discovery#provider
# oauth_discovery_webfinger GET /.well-known/webfinger(.:format) doorkeeper/openid_connect/discovery#webfinger
-describe Doorkeeper::OpenidConnect::DiscoveryController, 'routing' do
+RSpec.describe Doorkeeper::OpenidConnect::DiscoveryController, 'routing' do
it "to #provider" do
expect(get('/.well-known/openid-configuration')).to route_to('doorkeeper/openid_connect/discovery#provider')
end
@@ -26,7 +26,7 @@ end
# oauth_userinfo GET /oauth/userinfo(.:format) doorkeeper/openid_connect/userinfo#show
# POST /oauth/userinfo(.:format) doorkeeper/openid_connect/userinfo#show
-describe Doorkeeper::OpenidConnect::UserinfoController, 'routing' do
+RSpec.describe Doorkeeper::OpenidConnect::UserinfoController, 'routing' do
it "to #show" do
expect(get('/oauth/userinfo')).to route_to('doorkeeper/openid_connect/userinfo#show')
end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 6150a9637c7..87091da0c84 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -2,72 +2,14 @@
require 'spec_helper'
-describe 'project routing' do
+RSpec.describe 'project routing' do
+ let(:base_params) { { namespace_id: 'gitlab', project_id: 'gitlabhq' } }
+
before do
allow(Project).to receive(:find_by_full_path).and_return(false)
allow(Project).to receive(:find_by_full_path).with('gitlab/gitlabhq', any_args).and_return(true)
end
- # Shared examples for a resource inside a Project
- #
- # By default it tests all the default REST actions: index, create, new, edit,
- # show, update, and destroy. You can remove actions by customizing the
- # `actions` variable.
- #
- # It also expects a `controller` variable to be available which defines both
- # the path to the resource as well as the controller name.
- #
- # Examples
- #
- # # Default behavior
- # it_behaves_like 'RESTful project resources' do
- # let(:controller) { 'issues' }
- # end
- #
- # # Customizing actions
- # it_behaves_like 'RESTful project resources' do
- # let(:actions) { [:index] }
- # let(:controller) { 'issues' }
- # end
- #
- # # Different controller name and path
- # it_behaves_like 'RESTful project resources' do
- # let(:controller) { 'pages_domains' }
- # let(:controller_path) { 'pages/domains' }
- # end
- shared_examples 'RESTful project resources' do
- let(:actions) { [:index, :create, :new, :edit, :show, :update, :destroy] }
- let(:controller_path) { controller }
-
- it 'to #index' do
- expect(get("/gitlab/gitlabhq/#{controller_path}")).to route_to("projects/#{controller}#index", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:index)
- end
-
- it 'to #create' do
- expect(post("/gitlab/gitlabhq/#{controller_path}")).to route_to("projects/#{controller}#create", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:create)
- end
-
- it 'to #new' do
- expect(get("/gitlab/gitlabhq/#{controller_path}/new")).to route_to("projects/#{controller}#new", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:new)
- end
-
- it 'to #edit' do
- expect(get("/gitlab/gitlabhq/#{controller_path}/1/edit")).to route_to("projects/#{controller}#edit", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:edit)
- end
-
- it 'to #show' do
- expect(get("/gitlab/gitlabhq/#{controller_path}/1")).to route_to("projects/#{controller}#show", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:show)
- end
-
- it 'to #update' do
- expect(put("/gitlab/gitlabhq/#{controller_path}/1")).to route_to("projects/#{controller}#update", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:update)
- end
-
- it 'to #destroy' do
- expect(delete("/gitlab/gitlabhq/#{controller_path}/1")).to route_to("projects/#{controller}#destroy", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:destroy)
- end
- end
-
# projects POST /projects(.:format) projects#create
# new_project GET /projects/new(.:format) projects#new
# files_project GET /:id/files(.:format) projects#files
@@ -77,6 +19,10 @@ describe 'project routing' do
# DELETE /:id(.:format) projects#destroy
# preview_markdown_project POST /:id/preview_markdown(.:format) projects#preview_markdown
describe ProjectsController, 'routing' do
+ it 'to #index' do
+ expect(get('/projects')).to route_to('projects#index')
+ end
+
it 'to #create' do
expect(post('/projects')).to route_to('projects#create')
end
@@ -145,25 +91,19 @@ describe 'project routing' do
end
end
- # pages_project_wikis GET /:project_id/wikis/pages(.:format) projects/wikis#pages
- # history_project_wiki GET /:project_id/wikis/:id/history(.:format) projects/wikis#history
- # project_wikis POST /:project_id/wikis(.:format) projects/wikis#create
- # edit_project_wiki GET /:project_id/wikis/:id/edit(.:format) projects/wikis#edit
- # project_wiki GET /:project_id/wikis/:id(.:format) projects/wikis#show
- # DELETE /:project_id/wikis/:id(.:format) projects/wikis#destroy
+ # project_wikis_git_access GET /:project_id/-/wikis/git_access(.:format) projects/wikis#git_access
+ # project_wikis_pages GET /:project_id/-/wikis/pages(.:format) projects/wikis#pages
+ # project_wikis_new GET /:project_id/-/wikis/new(.:format) projects/wikis#new
+ # POST /:project_id/-/wikis(.:format) projects/wikis#create
+ # project_wiki_edit GET /:project_id/-/wikis/*id/edit projects/wikis#edit
+ # project_wiki_history GET /:project_id/-/wikis/*id/history projects/wikis#history
+ # project_wiki_preview_markdown POST /:project_id/-/wikis/*id/preview_markdown projects/wikis#preview_markdown
+ # project_wiki GET /:project_id/-/wikis/*id projects/wikis#show
+ # PUT /:project_id/-/wikis/*id projects/wikis#update
+ # DELETE /:project_id/-/wikis/*id projects/wikis#destroy
describe Projects::WikisController, 'routing' do
- it 'to #pages' do
- expect(get('/gitlab/gitlabhq/-/wikis/pages')).to route_to('projects/wikis#pages', namespace_id: 'gitlab', project_id: 'gitlabhq')
- end
-
- it 'to #history' do
- expect(get('/gitlab/gitlabhq/-/wikis/1/history')).to route_to('projects/wikis#history', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
- end
-
- it_behaves_like 'RESTful project resources' do
- let(:actions) { [:create, :edit, :show, :destroy] }
- let(:controller) { 'wikis' }
- let(:controller_path) { '/-/wikis' }
+ it_behaves_like 'wiki routing' do
+ let(:base_path) { '/gitlab/gitlabhq/-/wikis' }
end
it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/wikis", "/gitlab/gitlabhq/-/wikis"
@@ -242,10 +182,9 @@ describe 'project routing' do
# project_deploy_key PATCH /:project_id/deploy_keys/:id(.:format) deploy_keys#update
# DELETE /:project_id/deploy_keys/:id(.:format) deploy_keys#destroy
describe Projects::DeployKeysController, 'routing' do
- it_behaves_like 'RESTful project resources' do
- let(:actions) { [:index, :new, :create, :edit, :update] }
- let(:controller) { 'deploy_keys' }
- let(:controller_path) { '/-/deploy_keys' }
+ it_behaves_like 'resource routing' do
+ let(:actions) { %i[index new create edit update] }
+ let(:base_path) { '/gitlab/gitlabhq/-/deploy_keys' }
end
end
@@ -253,10 +192,9 @@ describe 'project routing' do
# POST /:project_id/protected_branches(.:format) protected_branches#create
# project_protected_branch DELETE /:project_id/protected_branches/:id(.:format) protected_branches#destroy
describe Projects::ProtectedBranchesController, 'routing' do
- it_behaves_like 'RESTful project resources' do
- let(:actions) { [:index, :create, :destroy] }
- let(:controller) { 'protected_branches' }
- let(:controller_path) { '/-/protected_branches' }
+ it_behaves_like 'resource routing' do
+ let(:actions) { %i[index create destroy] }
+ let(:base_path) { '/gitlab/gitlabhq/-/protected_branches' }
end
end
@@ -316,10 +254,9 @@ describe 'project routing' do
expect(get('/gitlab/gitlabhq/-/merge_requests/1/diffs')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', tab: 'diffs')
end
- it_behaves_like 'RESTful project resources' do
- let(:controller) { 'merge_requests' }
- let(:actions) { [:index, :edit, :show, :update] }
- let(:controller_path) { '/-/merge_requests' }
+ it_behaves_like 'resource routing' do
+ let(:actions) { %i[index edit show update] }
+ let(:base_path) { '/gitlab/gitlabhq/-/merge_requests' }
end
it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/merge_requests", "/gitlab/gitlabhq/-/merge_requests"
@@ -407,6 +344,10 @@ describe 'project routing' do
it 'to #destroy' do
expect(delete('/gitlab/gitlabhq/snippets/1')).to route_to('projects/snippets#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
end
+
+ it 'to #show from scope routing' do
+ expect(get('/gitlab/gitlabhq/-/snippets/1')).to route_to('projects/snippets#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
+ end
end
# test_project_hook POST /:project_id/hooks/:id/test(.:format) hooks#test
@@ -420,9 +361,9 @@ describe 'project routing' do
expect(post('/gitlab/gitlabhq/hooks/1/test')).to route_to('projects/hooks#test', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
end
- it_behaves_like 'RESTful project resources' do
- let(:actions) { [:index, :create, :destroy, :edit, :update] }
- let(:controller) { 'hooks' }
+ it_behaves_like 'resource routing' do
+ let(:actions) { %i[index create destroy edit update] }
+ let(:base_path) { '/gitlab/gitlabhq/hooks' }
end
end
@@ -457,10 +398,9 @@ describe 'project routing' do
# POST /:project_id/commits(.:format) commits#create
# project_commit GET /:project_id/commits/:id(.:format) commits#show
describe Projects::CommitsController, 'routing' do
- it_behaves_like 'RESTful project resources' do
- let(:actions) { [:show] }
- let(:controller) { 'commits' }
- let(:controller_path) { '/-/commits' }
+ it_behaves_like 'resource routing' do
+ let(:actions) { %i[show] }
+ let(:base_path) { '/gitlab/gitlabhq/-/commits' }
end
it 'to #show' do
@@ -477,10 +417,9 @@ describe 'project routing' do
# PUT /:project_id/project_members/:id(.:format) project_members#update
# DELETE /:project_id/project_members/:id(.:format) project_members#destroy
describe Projects::ProjectMembersController, 'routing' do
- it_behaves_like 'RESTful project resources' do
- let(:actions) { [:index, :create, :update, :destroy] }
- let(:controller) { 'project_members' }
- let(:controller_path) { '/-/project_members' }
+ it_behaves_like 'resource routing' do
+ let(:actions) { %i[index create update destroy] }
+ let(:base_path) { '/gitlab/gitlabhq/-/project_members' }
end
end
@@ -493,10 +432,9 @@ describe 'project routing' do
# DELETE /:project_id/milestones/:id(.:format) milestones#destroy
# promote_project_milestone POST /:project_id/milestones/:id/promote milestones#promote
describe Projects::MilestonesController, 'routing' do
- it_behaves_like 'RESTful project resources' do
- let(:controller) { 'milestones' }
- let(:actions) { [:index, :create, :new, :edit, :show, :update] }
- let(:controller_path) { '/-/milestones' }
+ it_behaves_like 'resource routing' do
+ let(:actions) { %i[index create new edit show update] }
+ let(:base_path) { '/gitlab/gitlabhq/-/milestones' }
end
it 'to #promote' do
@@ -526,10 +464,9 @@ describe 'project routing' do
expect(post('/gitlab/gitlabhq/-/issues/bulk_update')).to route_to('projects/issues#bulk_update', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
- it_behaves_like 'RESTful project resources' do
- let(:controller) { 'issues' }
- let(:actions) { [:index, :create, :new, :edit, :show, :update] }
- let(:controller_path) { '/-/issues' }
+ it_behaves_like 'resource routing' do
+ let(:actions) { %i[index create new edit show update] }
+ let(:base_path) { '/gitlab/gitlabhq/-/issues' }
end
it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/issues", "/gitlab/gitlabhq/-/issues"
@@ -550,9 +487,9 @@ describe 'project routing' do
)
end
- it_behaves_like 'RESTful project resources' do
- let(:actions) { [:create, :destroy] }
- let(:controller) { 'notes' }
+ it_behaves_like 'resource routing' do
+ let(:actions) { %i[create destroy] }
+ let(:base_path) { '/gitlab/gitlabhq/notes' }
end
end
@@ -741,10 +678,10 @@ describe 'project routing' do
end
describe Projects::PagesDomainsController, 'routing' do
- it_behaves_like 'RESTful project resources' do
- let(:actions) { [:show, :new, :create, :destroy] }
- let(:controller) { 'pages_domains' }
- let(:controller_path) { 'pages/domains' }
+ it_behaves_like 'resource routing' do
+ let(:actions) { %i[show new create destroy] }
+ let(:base_path) { '/gitlab/gitlabhq/pages/domains' }
+ let(:id) { 'my.domain.com' }
end
it 'to #destroy with a valid domain name' do
@@ -778,6 +715,12 @@ describe 'project routing' do
end
end
+ describe Projects::Settings::OperationsController, 'routing' do
+ it 'to #reset_alerting_token' do
+ expect(post('/gitlab/gitlabhq/-/settings/operations/reset_alerting_token')).to route_to('projects/settings/operations#reset_alerting_token', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ end
+ end
+
describe Projects::Settings::RepositoryController, 'routing' do
it 'to #show' do
expect(get('/gitlab/gitlabhq/-/settings/repository')).to route_to('projects/settings/repository#show', namespace_id: 'gitlab', project_id: 'gitlabhq')
@@ -872,4 +815,12 @@ describe 'project routing' do
expect(get('/gitlab/gitlabhq/-/design_management/designs/1/c6f00aa50b80887ada30a6fe517670be9f8f9ece/resized_image/small')).to route_to('application#route_not_found', unmatched_route: 'gitlab/gitlabhq/-/design_management/designs/1/c6f00aa50b80887ada30a6fe517670be9f8f9ece/resized_image/small')
end
end
+
+ describe Projects::Snippets::BlobsController, "routing" do
+ it "to #raw" do
+ expect(get('/gitlab/gitlabhq/-/snippets/1/raw/master/lib/version.rb'))
+ .to route_to('projects/snippets/blobs#raw', namespace_id: 'gitlab',
+ project_id: 'gitlabhq', snippet_id: '1', ref: 'master', path: 'lib/version.rb')
+ end
+ end
end
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index 9c3d17f7d8f..1218ae30781 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -9,7 +9,7 @@ require 'spec_helper'
# user_snippets GET /users/:username/snippets(.:format)
# user_calendar GET /users/:username/calendar(.:format)
# user_calendar_activities GET /users/:username/calendar_activities(.:format)
-describe UsersController, "routing" do
+RSpec.describe UsersController, "routing" do
it "to #show" do
allow_any_instance_of(::Constraints::UserUrlConstrainer).to receive(:matches?).and_return(true)
@@ -32,6 +32,13 @@ describe UsersController, "routing" do
expect(get("/users/User/snippets")).to route_to('users#snippets', username: 'User')
end
+ # get all the ssh-keys of a user
+ it "to #get_keys" do
+ allow_any_instance_of(::Constraints::UserUrlConstrainer).to receive(:matches?).and_return(true)
+
+ expect(get("/User.keys")).to route_to('users#ssh_keys', username: 'User')
+ end
+
it "to #calendar" do
expect(get("/users/User/calendar")).to route_to('users#calendar', username: 'User')
end
@@ -42,7 +49,7 @@ describe UsersController, "routing" do
end
# search GET /search(.:format) search#show
-describe SearchController, "routing" do
+RSpec.describe SearchController, "routing" do
it "to #show" do
expect(get("/search")).to route_to('search#show')
end
@@ -50,7 +57,7 @@ end
# gitlab_api /api API::API
# /:path Grack
-describe "Mounted Apps", "routing" do
+RSpec.describe "Mounted Apps", "routing" do
it "to API" do
expect(get("/api/issues")).to be_routable
end
@@ -67,7 +74,7 @@ end
# snippet GET /snippets/:id(.:format) snippets#show
# PUT /snippets/:id(.:format) snippets#update
# DELETE /snippets/:id(.:format) snippets#destroy
-describe SnippetsController, "routing" do
+RSpec.describe SnippetsController, "routing" do
it "to #raw" do
expect(get("/snippets/1/raw")).to route_to('snippets#raw', id: '1')
end
@@ -99,13 +106,17 @@ describe SnippetsController, "routing" do
it "to #destroy" do
expect(delete("/snippets/1")).to route_to('snippets#destroy', id: '1')
end
+
+ it 'to #show from scope routing' do
+ expect(get("/-/snippets/1")).to route_to('snippets#show', id: '1')
+ end
end
# help GET /help(.:format) help#index
# help_page GET /help/*path(.:format) help#show
# help_shortcuts GET /help/shortcuts(.:format) help#shortcuts
# help_ui GET /help/ui(.:format) help#ui
-describe HelpController, "routing" do
+RSpec.describe HelpController, "routing" do
it "to #index" do
expect(get("/help")).to route_to('help#index')
end
@@ -132,7 +143,7 @@ end
# profile_token GET /profile/token(.:format) profile#token
# profile GET /profile(.:format) profile#show
# profile_update PUT /profile/update(.:format) profile#update
-describe ProfilesController, "routing" do
+RSpec.describe ProfilesController, "routing" do
it "to #account" do
expect(get("/profile/account")).to route_to('profiles/accounts#show')
end
@@ -153,7 +164,7 @@ end
# profile_preferences GET /profile/preferences(.:format) profiles/preferences#show
# PATCH /profile/preferences(.:format) profiles/preferences#update
# PUT /profile/preferences(.:format) profiles/preferences#update
-describe Profiles::PreferencesController, 'routing' do
+RSpec.describe Profiles::PreferencesController, 'routing' do
it 'to #show' do
expect(get('/profile/preferences')).to route_to('profiles/preferences#show')
end
@@ -170,7 +181,7 @@ end
# key GET /keys/:id(.:format) keys#show
# PUT /keys/:id(.:format) keys#update
# DELETE /keys/:id(.:format) keys#destroy
-describe Profiles::KeysController, "routing" do
+RSpec.describe Profiles::KeysController, "routing" do
it "to #index" do
expect(get("/profile/keys")).to route_to('profiles/keys#index')
end
@@ -186,19 +197,12 @@ describe Profiles::KeysController, "routing" do
it "to #destroy" do
expect(delete("/profile/keys/1")).to route_to('profiles/keys#destroy', id: '1')
end
-
- # get all the ssh-keys of a user
- it "to #get_keys" do
- allow_any_instance_of(::Constraints::UserUrlConstrainer).to receive(:matches?).and_return(true)
-
- expect(get("/foo.keys")).to route_to('profiles/keys#get_keys', username: 'foo')
- end
end
# emails GET /emails(.:format) emails#index
# POST /keys(.:format) emails#create
# DELETE /keys/:id(.:format) keys#destroy
-describe Profiles::EmailsController, "routing" do
+RSpec.describe Profiles::EmailsController, "routing" do
it "to #index" do
expect(get("/profile/emails")).to route_to('profiles/emails#index')
end
@@ -213,7 +217,7 @@ describe Profiles::EmailsController, "routing" do
end
# profile_avatar DELETE /profile/avatar(.:format) profiles/avatars#destroy
-describe Profiles::AvatarsController, "routing" do
+RSpec.describe Profiles::AvatarsController, "routing" do
it "to #destroy" do
expect(delete("/profile/avatar")).to route_to('profiles/avatars#destroy')
end
@@ -222,7 +226,7 @@ end
# dashboard GET /dashboard(.:format) dashboard#show
# dashboard_issues GET /dashboard/issues(.:format) dashboard#issues
# dashboard_merge_requests GET /dashboard/merge_requests(.:format) dashboard#merge_requests
-describe DashboardController, "routing" do
+RSpec.describe DashboardController, "routing" do
it "to #index" do
expect(get("/dashboard")).to route_to('dashboard/projects#index')
end
@@ -241,13 +245,13 @@ describe DashboardController, "routing" do
end
# root / root#show
-describe RootController, 'routing' do
+RSpec.describe RootController, 'routing' do
it 'to #index' do
expect(get('/')).to route_to('root#index')
end
end
-describe "Authentication", "routing" do
+RSpec.describe "Authentication", "routing" do
it "GET /users/sign_in" do
expect(get("/users/sign_in")).to route_to('sessions#new')
end
@@ -304,7 +308,7 @@ describe "Authentication", "routing" do
end
end
-describe HealthCheckController, 'routing' do
+RSpec.describe HealthCheckController, 'routing' do
it 'to #index' do
expect(get('/health_check')).to route_to('health_check#index')
end
@@ -314,7 +318,7 @@ describe HealthCheckController, 'routing' do
end
end
-describe InvitesController, 'routing' do
+RSpec.describe InvitesController, 'routing' do
let_it_be(:member) { create(:project_member, :invited) }
it 'to #show' do
@@ -326,7 +330,7 @@ describe InvitesController, 'routing' do
end
end
-describe AbuseReportsController, 'routing' do
+RSpec.describe AbuseReportsController, 'routing' do
let_it_be(:user) { create(:user) }
it 'to #new' do
@@ -338,14 +342,14 @@ describe AbuseReportsController, 'routing' do
end
end
-describe SentNotificationsController, 'routing' do
+RSpec.describe SentNotificationsController, 'routing' do
it 'to #unsubscribe' do
expect(get("/-/sent_notifications/4bee17d4a63ed60cf5db53417e9aeb4c/unsubscribe"))
.to route_to('sent_notifications#unsubscribe', id: '4bee17d4a63ed60cf5db53417e9aeb4c')
end
end
-describe AutocompleteController, 'routing' do
+RSpec.describe AutocompleteController, 'routing' do
it 'to #users' do
expect(get("/-/autocomplete/users")).to route_to('autocomplete#users')
end
@@ -368,3 +372,10 @@ describe AutocompleteController, 'routing' do
expect(get("/autocomplete/award_emojis")).to route_to('autocomplete#award_emojis')
end
end
+
+RSpec.describe Snippets::BlobsController, "routing" do
+ it "to #raw" do
+ expect(get('/-/snippets/1/raw/master/lib/version.rb'))
+ .to route_to('snippets/blobs#raw', snippet_id: '1', ref: 'master', path: 'lib/version.rb')
+ end
+end
diff --git a/spec/routing/uploads_routing_spec.rb b/spec/routing/uploads_routing_spec.rb
index f94ae81eeb5..d1ddf8a6d6a 100644
--- a/spec/routing/uploads_routing_spec.rb
+++ b/spec/routing/uploads_routing_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Uploads', 'routing' do
+RSpec.describe 'Uploads', 'routing' do
it 'allows creating uploads for personal snippets' do
expect(post('/uploads/personal_snippet?id=1')).to route_to(
controller: 'uploads',
diff --git a/spec/rubocop/code_reuse_helpers_spec.rb b/spec/rubocop/code_reuse_helpers_spec.rb
index 2720141aad2..574a4a85a34 100644
--- a/spec/rubocop/code_reuse_helpers_spec.rb
+++ b/spec/rubocop/code_reuse_helpers_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'parser/current'
require_relative '../../rubocop/code_reuse_helpers'
-describe RuboCop::CodeReuseHelpers do
+RSpec.describe RuboCop::CodeReuseHelpers do
def parse_source(source, path = 'foo.rb')
buffer = Parser::Source::Buffer.new(path)
buffer.source = source
@@ -69,13 +69,13 @@ describe RuboCop::CodeReuseHelpers do
describe '#in_finder?' do
it 'returns true for a node in the finders directory' do
- node = parse_source('10', Rails.root.join('app', 'finders', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'finders', 'foo.rb'))
expect(cop.in_finder?(node)).to eq(true)
end
it 'returns false for a node outside the finders directory' do
- node = parse_source('10', Rails.root.join('app', 'foo', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'foo', 'foo.rb'))
expect(cop.in_finder?(node)).to eq(false)
end
@@ -83,13 +83,13 @@ describe RuboCop::CodeReuseHelpers do
describe '#in_model?' do
it 'returns true for a node in the models directory' do
- node = parse_source('10', Rails.root.join('app', 'models', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'models', 'foo.rb'))
expect(cop.in_model?(node)).to eq(true)
end
it 'returns false for a node outside the models directory' do
- node = parse_source('10', Rails.root.join('app', 'foo', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'foo', 'foo.rb'))
expect(cop.in_model?(node)).to eq(false)
end
@@ -97,13 +97,13 @@ describe RuboCop::CodeReuseHelpers do
describe '#in_service_class?' do
it 'returns true for a node in the services directory' do
- node = parse_source('10', Rails.root.join('app', 'services', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'services', 'foo.rb'))
expect(cop.in_service_class?(node)).to eq(true)
end
it 'returns false for a node outside the services directory' do
- node = parse_source('10', Rails.root.join('app', 'foo', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'foo', 'foo.rb'))
expect(cop.in_service_class?(node)).to eq(false)
end
@@ -111,13 +111,13 @@ describe RuboCop::CodeReuseHelpers do
describe '#in_presenter?' do
it 'returns true for a node in the presenters directory' do
- node = parse_source('10', Rails.root.join('app', 'presenters', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'presenters', 'foo.rb'))
expect(cop.in_presenter?(node)).to eq(true)
end
it 'returns false for a node outside the presenters directory' do
- node = parse_source('10', Rails.root.join('app', 'foo', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'foo', 'foo.rb'))
expect(cop.in_presenter?(node)).to eq(false)
end
@@ -125,13 +125,13 @@ describe RuboCop::CodeReuseHelpers do
describe '#in_serializer?' do
it 'returns true for a node in the serializers directory' do
- node = parse_source('10', Rails.root.join('app', 'serializers', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'serializers', 'foo.rb'))
expect(cop.in_serializer?(node)).to eq(true)
end
it 'returns false for a node outside the serializers directory' do
- node = parse_source('10', Rails.root.join('app', 'foo', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'foo', 'foo.rb'))
expect(cop.in_serializer?(node)).to eq(false)
end
@@ -139,13 +139,13 @@ describe RuboCop::CodeReuseHelpers do
describe '#in_worker?' do
it 'returns true for a node in the workers directory' do
- node = parse_source('10', Rails.root.join('app', 'workers', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'workers', 'foo.rb'))
expect(cop.in_worker?(node)).to eq(true)
end
it 'returns false for a node outside the workers directory' do
- node = parse_source('10', Rails.root.join('app', 'foo', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'foo', 'foo.rb'))
expect(cop.in_worker?(node)).to eq(false)
end
@@ -153,13 +153,13 @@ describe RuboCop::CodeReuseHelpers do
describe '#in_api?' do
it 'returns true for a node in the API directory' do
- node = parse_source('10', Rails.root.join('lib', 'api', 'foo.rb'))
+ node = parse_source('10', rails_root_join('lib', 'api', 'foo.rb'))
expect(cop.in_api?(node)).to eq(true)
end
it 'returns false for a node outside the API directory' do
- node = parse_source('10', Rails.root.join('lib', 'foo', 'foo.rb'))
+ node = parse_source('10', rails_root_join('lib', 'foo', 'foo.rb'))
expect(cop.in_api?(node)).to eq(false)
end
@@ -167,21 +167,21 @@ describe RuboCop::CodeReuseHelpers do
describe '#in_directory?' do
it 'returns true for a directory in the CE app/ directory' do
- node = parse_source('10', Rails.root.join('app', 'models', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'models', 'foo.rb'))
expect(cop.in_directory?(node, 'models')).to eq(true)
end
it 'returns true for a directory in the EE app/ directory' do
node =
- parse_source('10', Rails.root.join('ee', 'app', 'models', 'foo.rb'))
+ parse_source('10', rails_root_join('ee', 'app', 'models', 'foo.rb'))
expect(cop.in_directory?(node, 'models')).to eq(true)
end
it 'returns false for a directory in the lib/ directory' do
node =
- parse_source('10', Rails.root.join('lib', 'models', 'foo.rb'))
+ parse_source('10', rails_root_join('lib', 'models', 'foo.rb'))
expect(cop.in_directory?(node, 'models')).to eq(false)
end
diff --git a/spec/rubocop/cop/active_record_association_reload_spec.rb b/spec/rubocop/cop/active_record_association_reload_spec.rb
index d9c8069f0c3..79053a79c5a 100644
--- a/spec/rubocop/cop/active_record_association_reload_spec.rb
+++ b/spec/rubocop/cop/active_record_association_reload_spec.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require_relative '../../../rubocop/cop/active_record_association_reload'
-describe RuboCop::Cop::ActiveRecordAssociationReload do
+RSpec.describe RuboCop::Cop::ActiveRecordAssociationReload, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/api/grape_api_instance_spec.rb b/spec/rubocop/cop/api/grape_api_instance_spec.rb
new file mode 100644
index 00000000000..74f175cb707
--- /dev/null
+++ b/spec/rubocop/cop/api/grape_api_instance_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rubocop'
+require_relative '../../../../rubocop/cop/api/grape_api_instance'
+
+RSpec.describe RuboCop::Cop::API::GrapeAPIInstance do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ it 'adds an offense when inheriting from Grape::API' do
+ inspect_source(<<~CODE)
+ class SomeAPI < Grape::API
+ end
+ CODE
+
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'does not add an offense when inheriting from Grape::API::Instance' do
+ inspect_source(<<~CODE)
+ class SomeAPI < Grape::API::Instance
+ end
+ CODE
+
+ expect(cop.offenses.size).to be_zero
+ end
+end
diff --git a/spec/rubocop/cop/api/grape_array_missing_coerce_spec.rb b/spec/rubocop/cop/api/grape_array_missing_coerce_spec.rb
new file mode 100644
index 00000000000..c7bb8255398
--- /dev/null
+++ b/spec/rubocop/cop/api/grape_array_missing_coerce_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rubocop'
+require_relative '../../../../rubocop/cop/api/grape_array_missing_coerce'
+
+RSpec.describe RuboCop::Cop::API::GrapeArrayMissingCoerce do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ it 'adds an offense with a required parameter' do
+ inspect_source(<<~CODE)
+ class SomeAPI < Grape::API::Instance
+ params do
+ requires :values, type: Array[String]
+ end
+ end
+ CODE
+
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'adds an offense with an optional parameter' do
+ inspect_source(<<~CODE)
+ class SomeAPI < Grape::API::Instance
+ params do
+ optional :values, type: Array[String]
+ end
+ end
+ CODE
+
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'does not add an offense' do
+ inspect_source(<<~CODE)
+ class SomeAPI < Grape::API::Instance
+ params do
+ requires :values, type: Array[String], coerce_with: ->(val) { val.split(',').map(&:strip) }
+ requires :milestone, type: String, desc: 'Milestone title'
+ optional :assignee_id, types: [Integer, String], integer_none_any: true,
+ desc: 'Return issues which are assigned to the user with the given ID'
+ end
+ end
+ CODE
+
+ expect(cop.offenses.size).to be_zero
+ end
+
+ it 'does not add an offense for unrelated classes' do
+ inspect_source(<<~CODE)
+ class SomeClass
+ params do
+ requires :values, type: Array[String]
+ end
+ end
+ CODE
+
+ expect(cop.offenses.size).to be_zero
+ end
+end
diff --git a/spec/rubocop/cop/avoid_break_from_strong_memoize_spec.rb b/spec/rubocop/cop/avoid_break_from_strong_memoize_spec.rb
index feb85c354ef..bc9db9cafec 100644
--- a/spec/rubocop/cop/avoid_break_from_strong_memoize_spec.rb
+++ b/spec/rubocop/cop/avoid_break_from_strong_memoize_spec.rb
@@ -5,7 +5,7 @@ require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/avoid_break_from_strong_memoize'
-describe RuboCop::Cop::AvoidBreakFromStrongMemoize do
+RSpec.describe RuboCop::Cop::AvoidBreakFromStrongMemoize do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers_spec.rb b/spec/rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers_spec.rb
index 11d63d8e0ee..851493e004e 100644
--- a/spec/rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers_spec.rb
+++ b/spec/rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers'
-describe RuboCop::Cop::AvoidKeywordArgumentsInSidekiqWorkers do
+RSpec.describe RuboCop::Cop::AvoidKeywordArgumentsInSidekiqWorkers, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/avoid_return_from_blocks_spec.rb b/spec/rubocop/cop/avoid_return_from_blocks_spec.rb
index 919cd3d98f3..9e571bf96b9 100644
--- a/spec/rubocop/cop/avoid_return_from_blocks_spec.rb
+++ b/spec/rubocop/cop/avoid_return_from_blocks_spec.rb
@@ -5,7 +5,7 @@ require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/avoid_return_from_blocks'
-describe RuboCop::Cop::AvoidReturnFromBlocks do
+RSpec.describe RuboCop::Cop::AvoidReturnFromBlocks do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/avoid_route_redirect_leading_slash_spec.rb b/spec/rubocop/cop/avoid_route_redirect_leading_slash_spec.rb
index 207c3420fbd..78bc859beda 100644
--- a/spec/rubocop/cop/avoid_route_redirect_leading_slash_spec.rb
+++ b/spec/rubocop/cop/avoid_route_redirect_leading_slash_spec.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require_relative '../../../rubocop/cop/avoid_route_redirect_leading_slash'
-describe RuboCop::Cop::AvoidRouteRedirectLeadingSlash do
+RSpec.describe RuboCop::Cop::AvoidRouteRedirectLeadingSlash, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/ban_catch_throw_spec.rb b/spec/rubocop/cop/ban_catch_throw_spec.rb
index b4c277fc429..1d0ccb6d262 100644
--- a/spec/rubocop/cop/ban_catch_throw_spec.rb
+++ b/spec/rubocop/cop/ban_catch_throw_spec.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/ban_catch_throw'
-describe RuboCop::Cop::BanCatchThrow do
+RSpec.describe RuboCop::Cop::BanCatchThrow, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/code_reuse/active_record_spec.rb b/spec/rubocop/cop/code_reuse/active_record_spec.rb
index 0f3d886e4b8..25eca185f26 100644
--- a/spec/rubocop/cop/code_reuse/active_record_spec.rb
+++ b/spec/rubocop/cop/code_reuse/active_record_spec.rb
@@ -1,11 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/code_reuse/active_record'
-describe RuboCop::Cop::CodeReuse::ActiveRecord do
+RSpec.describe RuboCop::Cop::CodeReuse::ActiveRecord, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
@@ -46,7 +45,7 @@ describe RuboCop::Cop::CodeReuse::ActiveRecord do
end
it 'does not flag the use of ActiveRecord models in a model' do
- path = Rails.root.join('app', 'models', 'foo.rb').to_s
+ path = rails_root_join('app', 'models', 'foo.rb').to_s
expect_no_offenses(<<~SOURCE, path)
def foo
@@ -56,7 +55,7 @@ describe RuboCop::Cop::CodeReuse::ActiveRecord do
end
it 'does not flag the use of ActiveRecord models in a spec' do
- path = Rails.root.join('spec', 'foo_spec.rb').to_s
+ path = rails_root_join('spec', 'foo_spec.rb').to_s
expect_no_offenses(<<~SOURCE, path)
def foo
@@ -66,10 +65,7 @@ describe RuboCop::Cop::CodeReuse::ActiveRecord do
end
it 'does not flag the use of ActiveRecord models in a background migration' do
- path = Rails
- .root
- .join('lib', 'gitlab', 'background_migration', 'foo.rb')
- .to_s
+ path = rails_root_join('lib', 'gitlab', 'background_migration', 'foo.rb').to_s
expect_no_offenses(<<~SOURCE, path)
def foo
@@ -79,7 +75,7 @@ describe RuboCop::Cop::CodeReuse::ActiveRecord do
end
it 'does not flag the use of ActiveRecord models in lib/gitlab/database' do
- path = Rails.root.join('lib', 'gitlab', 'database', 'foo.rb').to_s
+ path = rails_root_join('lib', 'gitlab', 'database', 'foo.rb').to_s
expect_no_offenses(<<~SOURCE, path)
def foo
diff --git a/spec/rubocop/cop/code_reuse/finder_spec.rb b/spec/rubocop/cop/code_reuse/finder_spec.rb
index b04e053a4c3..1935d825b19 100644
--- a/spec/rubocop/cop/code_reuse/finder_spec.rb
+++ b/spec/rubocop/cop/code_reuse/finder_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/code_reuse/finder'
-describe RuboCop::Cop::CodeReuse::Finder do
+RSpec.describe RuboCop::Cop::CodeReuse::Finder, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/code_reuse/presenter_spec.rb b/spec/rubocop/cop/code_reuse/presenter_spec.rb
index 4fe72619273..1366018ec12 100644
--- a/spec/rubocop/cop/code_reuse/presenter_spec.rb
+++ b/spec/rubocop/cop/code_reuse/presenter_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/code_reuse/presenter'
-describe RuboCop::Cop::CodeReuse::Presenter do
+RSpec.describe RuboCop::Cop::CodeReuse::Presenter, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/code_reuse/serializer_spec.rb b/spec/rubocop/cop/code_reuse/serializer_spec.rb
index 4530b15eed7..d4341cc0367 100644
--- a/spec/rubocop/cop/code_reuse/serializer_spec.rb
+++ b/spec/rubocop/cop/code_reuse/serializer_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/code_reuse/serializer'
-describe RuboCop::Cop::CodeReuse::Serializer do
+RSpec.describe RuboCop::Cop::CodeReuse::Serializer, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/code_reuse/service_class_spec.rb b/spec/rubocop/cop/code_reuse/service_class_spec.rb
index 7b8d82f332e..b018e743230 100644
--- a/spec/rubocop/cop/code_reuse/service_class_spec.rb
+++ b/spec/rubocop/cop/code_reuse/service_class_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/code_reuse/service_class'
-describe RuboCop::Cop::CodeReuse::ServiceClass do
+RSpec.describe RuboCop::Cop::CodeReuse::ServiceClass, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/code_reuse/worker_spec.rb b/spec/rubocop/cop/code_reuse/worker_spec.rb
index 97acaeb7643..1f502e554c4 100644
--- a/spec/rubocop/cop/code_reuse/worker_spec.rb
+++ b/spec/rubocop/cop/code_reuse/worker_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/code_reuse/worker'
-describe RuboCop::Cop::CodeReuse::Worker do
+RSpec.describe RuboCop::Cop::CodeReuse::Worker, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
@@ -31,7 +31,7 @@ describe RuboCop::Cop::CodeReuse::Worker do
.and_return(true)
expect_offense(<<~SOURCE)
- class Foo < Grape::API
+ class Foo < Grape::API::Instance
resource :projects do
get '/' do
FooWorker.perform_async
diff --git a/spec/rubocop/cop/default_scope_spec.rb b/spec/rubocop/cop/default_scope_spec.rb
index 9520915f900..617a7f63497 100644
--- a/spec/rubocop/cop/default_scope_spec.rb
+++ b/spec/rubocop/cop/default_scope_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/default_scope'
-describe RuboCop::Cop::DefaultScope do
+RSpec.describe RuboCop::Cop::DefaultScope, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/destroy_all_spec.rb b/spec/rubocop/cop/destroy_all_spec.rb
index d06c0b2f3cf..3220d44ea2b 100644
--- a/spec/rubocop/cop/destroy_all_spec.rb
+++ b/spec/rubocop/cop/destroy_all_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/destroy_all'
-describe RuboCop::Cop::DestroyAll do
+RSpec.describe RuboCop::Cop::DestroyAll, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/filename_length_spec.rb b/spec/rubocop/cop/filename_length_spec.rb
index b1cc845787a..381218c61ed 100644
--- a/spec/rubocop/cop/filename_length_spec.rb
+++ b/spec/rubocop/cop/filename_length_spec.rb
@@ -5,7 +5,7 @@ require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/filename_length'
-describe RuboCop::Cop::FilenameLength, type: :rubocop do
+RSpec.describe RuboCop::Cop::FilenameLength, type: :rubocop do
subject(:cop) { described_class.new }
it 'does not flag files with names 100 characters long' do
diff --git a/spec/rubocop/cop/gitlab/bulk_insert_spec.rb b/spec/rubocop/cop/gitlab/bulk_insert_spec.rb
index 937c709218f..2766e4f1982 100644
--- a/spec/rubocop/cop/gitlab/bulk_insert_spec.rb
+++ b/spec/rubocop/cop/gitlab/bulk_insert_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/bulk_insert'
-describe RuboCop::Cop::Gitlab::BulkInsert do
+RSpec.describe RuboCop::Cop::Gitlab::BulkInsert, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/gitlab/change_timezone_spec.rb b/spec/rubocop/cop/gitlab/change_timezone_spec.rb
index 1e4b4048cf4..1ec5f28e811 100644
--- a/spec/rubocop/cop/gitlab/change_timezone_spec.rb
+++ b/spec/rubocop/cop/gitlab/change_timezone_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/change_timzone'
-describe RuboCop::Cop::Gitlab::ChangeTimezone do
+RSpec.describe RuboCop::Cop::Gitlab::ChangeTimezone, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/gitlab/const_get_inherit_false_spec.rb b/spec/rubocop/cop/gitlab/const_get_inherit_false_spec.rb
index bf0434e7afe..97ed6b743f9 100644
--- a/spec/rubocop/cop/gitlab/const_get_inherit_false_spec.rb
+++ b/spec/rubocop/cop/gitlab/const_get_inherit_false_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/const_get_inherit_false'
-describe RuboCop::Cop::Gitlab::ConstGetInheritFalse do
+RSpec.describe RuboCop::Cop::Gitlab::ConstGetInheritFalse, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/gitlab/duplicate_spec_location_spec.rb b/spec/rubocop/cop/gitlab/duplicate_spec_location_spec.rb
index 3a0a74a4713..1ed21e44290 100644
--- a/spec/rubocop/cop/gitlab/duplicate_spec_location_spec.rb
+++ b/spec/rubocop/cop/gitlab/duplicate_spec_location_spec.rb
@@ -2,12 +2,10 @@
require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/duplicate_spec_location'
-describe RuboCop::Cop::Gitlab::DuplicateSpecLocation do
- include RuboCop::RSpec::ExpectOffense
+RSpec.describe RuboCop::Cop::Gitlab::DuplicateSpecLocation, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/gitlab/finder_with_find_by_spec.rb b/spec/rubocop/cop/gitlab/finder_with_find_by_spec.rb
index 7af98b66218..30ee422f420 100644
--- a/spec/rubocop/cop/gitlab/finder_with_find_by_spec.rb
+++ b/spec/rubocop/cop/gitlab/finder_with_find_by_spec.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/finder_with_find_by'
-describe RuboCop::Cop::Gitlab::FinderWithFindBy do
+RSpec.describe RuboCop::Cop::Gitlab::FinderWithFindBy, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/gitlab/httparty_spec.rb b/spec/rubocop/cop/gitlab/httparty_spec.rb
index 42da97679ec..379365096ba 100644
--- a/spec/rubocop/cop/gitlab/httparty_spec.rb
+++ b/spec/rubocop/cop/gitlab/httparty_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/httparty'
-describe RuboCop::Cop::Gitlab::HTTParty do # rubocop:disable RSpec/FilePath
+RSpec.describe RuboCop::Cop::Gitlab::HTTParty, type: :rubocop do # rubocop:disable RSpec/FilePath
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/gitlab/json_spec.rb b/spec/rubocop/cop/gitlab/json_spec.rb
index d64f60c8583..6f5ec07ffb1 100644
--- a/spec/rubocop/cop/gitlab/json_spec.rb
+++ b/spec/rubocop/cop/gitlab/json_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/json'
-describe RuboCop::Cop::Gitlab::Json do
+RSpec.describe RuboCop::Cop::Gitlab::Json, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/gitlab/module_with_instance_variables_spec.rb b/spec/rubocop/cop/gitlab/module_with_instance_variables_spec.rb
index 9cb55ced1fa..3d22201c92e 100644
--- a/spec/rubocop/cop/gitlab/module_with_instance_variables_spec.rb
+++ b/spec/rubocop/cop/gitlab/module_with_instance_variables_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/module_with_instance_variables'
-describe RuboCop::Cop::Gitlab::ModuleWithInstanceVariables do
+RSpec.describe RuboCop::Cop::Gitlab::ModuleWithInstanceVariables, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/gitlab/predicate_memoization_spec.rb b/spec/rubocop/cop/gitlab/predicate_memoization_spec.rb
index ae9466368d2..ebe984b189d 100644
--- a/spec/rubocop/cop/gitlab/predicate_memoization_spec.rb
+++ b/spec/rubocop/cop/gitlab/predicate_memoization_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/predicate_memoization'
-describe RuboCop::Cop::Gitlab::PredicateMemoization do
+RSpec.describe RuboCop::Cop::Gitlab::PredicateMemoization, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/gitlab/rails_logger_spec.rb b/spec/rubocop/cop/gitlab/rails_logger_spec.rb
index f0158ddcc5c..0583079136b 100644
--- a/spec/rubocop/cop/gitlab/rails_logger_spec.rb
+++ b/spec/rubocop/cop/gitlab/rails_logger_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/rails_logger'
-describe RuboCop::Cop::Gitlab::RailsLogger do
+RSpec.describe RuboCop::Cop::Gitlab::RailsLogger, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/gitlab/union_spec.rb b/spec/rubocop/cop/gitlab/union_spec.rb
index f0544fdb66e..571dcc4eeb4 100644
--- a/spec/rubocop/cop/gitlab/union_spec.rb
+++ b/spec/rubocop/cop/gitlab/union_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/union'
-describe RuboCop::Cop::Gitlab::Union do
+RSpec.describe RuboCop::Cop::Gitlab::Union, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/graphql/authorize_types_spec.rb b/spec/rubocop/cop/graphql/authorize_types_spec.rb
index 98797a780e0..df637a26ec5 100644
--- a/spec/rubocop/cop/graphql/authorize_types_spec.rb
+++ b/spec/rubocop/cop/graphql/authorize_types_spec.rb
@@ -2,92 +2,68 @@
require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
+
require_relative '../../../../rubocop/cop/graphql/authorize_types'
-describe RuboCop::Cop::Graphql::AuthorizeTypes do
- include RuboCop::RSpec::ExpectOffense
+RSpec.describe RuboCop::Cop::Graphql::AuthorizeTypes, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
- context 'when NOT in a type folder' do
- before do
- allow(cop).to receive(:in_type?).and_return(false)
- end
-
- it 'does not add an offense even though there is no authorize call' do
- expect_no_offenses(<<~TYPE.strip)
- module Types
- class AType < BaseObject
- field :a_thing
- field :another_thing
- end
- end
- TYPE
- end
- end
-
- context 'when in a type folder' do
- before do
- allow(cop).to receive(:in_type?).and_return(true)
- end
-
- it 'adds an offense when there is no authorize call' do
- inspect_source(<<~TYPE)
- module Types
- class AType < BaseObject
- field :a_thing
- field :another_thing
- end
+ it 'adds an offense when there is no authorize call' do
+ inspect_source(<<~TYPE)
+ module Types
+ class AType < BaseObject
+ field :a_thing
+ field :another_thing
end
- TYPE
+ end
+ TYPE
- expect(cop.offenses.size).to eq 1
- end
+ expect(cop.offenses.size).to eq 1
+ end
- it 'does not add an offense for classes that have an authorize call' do
- expect_no_offenses(<<~TYPE.strip)
- module Types
- class AType < BaseObject
- graphql_name 'ATypeName'
+ it 'does not add an offense for classes that have an authorize call' do
+ expect_no_offenses(<<~TYPE.strip)
+ module Types
+ class AType < BaseObject
+ graphql_name 'ATypeName'
- authorize :an_ability, :second_ability
+ authorize :an_ability, :second_ability
- field :a_thing
- end
+ field :a_thing
end
- TYPE
- end
+ end
+ TYPE
+ end
- it 'does not add an offense for classes that only have an authorize call' do
- expect_no_offenses(<<~TYPE.strip)
- module Types
- class AType < SuperClassWithFields
- authorize :an_ability
- end
+ it 'does not add an offense for classes that only have an authorize call' do
+ expect_no_offenses(<<~TYPE.strip)
+ module Types
+ class AType < SuperClassWithFields
+ authorize :an_ability
end
- TYPE
- end
+ end
+ TYPE
+ end
- it 'does not add an offense for base types' do
- expect_no_offenses(<<~TYPE)
- module Types
- class AType < BaseEnum
- field :a_thing
- end
+ it 'does not add an offense for base types' do
+ expect_no_offenses(<<~TYPE)
+ module Types
+ class AType < BaseEnum
+ field :a_thing
end
- TYPE
- end
+ end
+ TYPE
+ end
- it 'does not add an offense for Enums' do
- expect_no_offenses(<<~TYPE)
- module Types
- class ATypeEnum < AnotherEnum
- field :a_thing
- end
+ it 'does not add an offense for Enums' do
+ expect_no_offenses(<<~TYPE)
+ module Types
+ class ATypeEnum < AnotherEnum
+ field :a_thing
end
- TYPE
- end
+ end
+ TYPE
end
end
diff --git a/spec/rubocop/cop/graphql/descriptions_spec.rb b/spec/rubocop/cop/graphql/descriptions_spec.rb
index 8cfdc05172d..3b29cd2fbee 100644
--- a/spec/rubocop/cop/graphql/descriptions_spec.rb
+++ b/spec/rubocop/cop/graphql/descriptions_spec.rb
@@ -2,11 +2,9 @@
require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/graphql/descriptions'
-describe RuboCop::Cop::Graphql::Descriptions do
- include RuboCop::RSpec::ExpectOffense
+RSpec.describe RuboCop::Cop::Graphql::Descriptions, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/group_public_or_visible_to_user_spec.rb b/spec/rubocop/cop/group_public_or_visible_to_user_spec.rb
index 8e027ad59f7..4e725deaafd 100644
--- a/spec/rubocop/cop/group_public_or_visible_to_user_spec.rb
+++ b/spec/rubocop/cop/group_public_or_visible_to_user_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/group_public_or_visible_to_user'
-describe RuboCop::Cop::GroupPublicOrVisibleToUser do
+RSpec.describe RuboCop::Cop::GroupPublicOrVisibleToUser, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/ignored_columns_spec.rb b/spec/rubocop/cop/ignored_columns_spec.rb
index 64437765018..ec5499bbdff 100644
--- a/spec/rubocop/cop/ignored_columns_spec.rb
+++ b/spec/rubocop/cop/ignored_columns_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/ignored_columns'
-describe RuboCop::Cop::IgnoredColumns do
+RSpec.describe RuboCop::Cop::IgnoredColumns, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/include_sidekiq_worker_spec.rb b/spec/rubocop/cop/include_sidekiq_worker_spec.rb
index 39965646aff..8d056c6a13e 100644
--- a/spec/rubocop/cop/include_sidekiq_worker_spec.rb
+++ b/spec/rubocop/cop/include_sidekiq_worker_spec.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/include_sidekiq_worker'
-describe RuboCop::Cop::IncludeSidekiqWorker do
+RSpec.describe RuboCop::Cop::IncludeSidekiqWorker, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
index f047baa3bc2..767ed994542 100644
--- a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
+++ b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/inject_enterprise_edition_module'
-describe RuboCop::Cop::InjectEnterpriseEditionModule do
+RSpec.describe RuboCop::Cop::InjectEnterpriseEditionModule, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/line_break_around_conditional_block_spec.rb b/spec/rubocop/cop/line_break_around_conditional_block_spec.rb
index d09de4c6614..0a26ef49e35 100644
--- a/spec/rubocop/cop/line_break_around_conditional_block_spec.rb
+++ b/spec/rubocop/cop/line_break_around_conditional_block_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/line_break_around_conditional_block'
-describe RuboCop::Cop::LineBreakAroundConditionalBlock do
+RSpec.describe RuboCop::Cop::LineBreakAroundConditionalBlock, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/add_column_with_default_spec.rb b/spec/rubocop/cop/migration/add_column_with_default_spec.rb
index 5d4fc59fb95..50af344e0d4 100644
--- a/spec/rubocop/cop/migration/add_column_with_default_spec.rb
+++ b/spec/rubocop/cop/migration/add_column_with_default_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/add_column_with_default'
-describe RuboCop::Cop::Migration::AddColumnWithDefault do
+RSpec.describe RuboCop::Cop::Migration::AddColumnWithDefault, type: :rubocop do
include CopHelper
let(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/add_columns_to_wide_tables_spec.rb b/spec/rubocop/cop/migration/add_columns_to_wide_tables_spec.rb
index 5b179168eab..6ae4fb21126 100644
--- a/spec/rubocop/cop/migration/add_columns_to_wide_tables_spec.rb
+++ b/spec/rubocop/cop/migration/add_columns_to_wide_tables_spec.rb
@@ -1,11 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
-
require_relative '../../../../rubocop/cop/migration/add_columns_to_wide_tables'
-describe RuboCop::Cop::Migration::AddColumnsToWideTables do
+RSpec.describe RuboCop::Cop::Migration::AddColumnsToWideTables, type: :rubocop do
include CopHelper
let(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb b/spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb
index dfc3898af24..b43d44dba65 100644
--- a/spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb
+++ b/spec/rubocop/cop/migration/add_concurrent_foreign_key_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/add_concurrent_foreign_key'
-describe RuboCop::Cop::Migration::AddConcurrentForeignKey do
+RSpec.describe RuboCop::Cop::Migration::AddConcurrentForeignKey, type: :rubocop do
include CopHelper
let(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/add_concurrent_index_spec.rb b/spec/rubocop/cop/migration/add_concurrent_index_spec.rb
index 9812e64216f..cef5295830c 100644
--- a/spec/rubocop/cop/migration/add_concurrent_index_spec.rb
+++ b/spec/rubocop/cop/migration/add_concurrent_index_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/add_concurrent_index'
-describe RuboCop::Cop::Migration::AddConcurrentIndex do
+RSpec.describe RuboCop::Cop::Migration::AddConcurrentIndex, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/add_index_spec.rb b/spec/rubocop/cop/migration/add_index_spec.rb
index ca1aadb381b..6bb78a7f3c7 100644
--- a/spec/rubocop/cop/migration/add_index_spec.rb
+++ b/spec/rubocop/cop/migration/add_index_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/add_index'
-describe RuboCop::Cop::Migration::AddIndex do
+RSpec.describe RuboCop::Cop::Migration::AddIndex, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/add_limit_to_text_columns_spec.rb b/spec/rubocop/cop/migration/add_limit_to_text_columns_spec.rb
index 39ca9ace73d..5f0ca419548 100644
--- a/spec/rubocop/cop/migration/add_limit_to_text_columns_spec.rb
+++ b/spec/rubocop/cop/migration/add_limit_to_text_columns_spec.rb
@@ -1,12 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/add_limit_to_text_columns'
-describe RuboCop::Cop::Migration::AddLimitToTextColumns do
+RSpec.describe RuboCop::Cop::Migration::AddLimitToTextColumns, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/add_reference_spec.rb b/spec/rubocop/cop/migration/add_reference_spec.rb
index 03348ecc744..fab70d74b55 100644
--- a/spec/rubocop/cop/migration/add_reference_spec.rb
+++ b/spec/rubocop/cop/migration/add_reference_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/add_reference'
-describe RuboCop::Cop::Migration::AddReference do
+RSpec.describe RuboCop::Cop::Migration::AddReference, type: :rubocop do
include CopHelper
let(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/add_timestamps_spec.rb b/spec/rubocop/cop/migration/add_timestamps_spec.rb
index a3314d878e5..c18b6b06ded 100644
--- a/spec/rubocop/cop/migration/add_timestamps_spec.rb
+++ b/spec/rubocop/cop/migration/add_timestamps_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/add_timestamps'
-describe RuboCop::Cop::Migration::AddTimestamps do
+RSpec.describe RuboCop::Cop::Migration::AddTimestamps, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/datetime_spec.rb b/spec/rubocop/cop/migration/datetime_spec.rb
index e3023406dce..c5f6fd503ec 100644
--- a/spec/rubocop/cop/migration/datetime_spec.rb
+++ b/spec/rubocop/cop/migration/datetime_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/datetime'
-describe RuboCop::Cop::Migration::Datetime do
+RSpec.describe RuboCop::Cop::Migration::Datetime, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/drop_table_spec.rb b/spec/rubocop/cop/migration/drop_table_spec.rb
index 4fe7fc8c5a5..44a1106ba62 100644
--- a/spec/rubocop/cop/migration/drop_table_spec.rb
+++ b/spec/rubocop/cop/migration/drop_table_spec.rb
@@ -7,7 +7,7 @@ require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/migration/drop_table'
-describe RuboCop::Cop::Migration::DropTable do
+RSpec.describe RuboCop::Cop::Migration::DropTable do
include CopHelper
subject(:cop) { described_class.new }
@@ -17,20 +17,70 @@ describe RuboCop::Cop::Migration::DropTable do
allow(cop).to receive(:in_deployment_migration?).and_return(true)
end
- it 'registers an offense' do
- expect_offense(<<~PATTERN)
- def change
- drop_table :table
- ^^^^^^^^^^ #{described_class::MSG}
-
- add_column(:users, :username, :text)
+ context 'with drop_table DSL method' do
+ context 'when in down method' do
+ it 'does not register an offense' do
+ expect_no_offenses(<<~PATTERN)
+ def down
+ drop_table :table
+ end
+ PATTERN
+ end
+ end
- execute "DROP TABLE table"
- ^^^^^^^ #{described_class::MSG}
+ context 'when in up method' do
+ it 'registers an offense' do
+ expect_offense(<<~PATTERN)
+ def up
+ drop_table :table
+ ^^^^^^^^^^ #{described_class::MSG}
+ end
+ PATTERN
+ end
+ end
- execute "CREATE UNIQUE INDEX email_index ON users (email);"
+ context 'when in change method' do
+ it 'registers an offense' do
+ expect_offense(<<~PATTERN)
+ def change
+ drop_table :table
+ ^^^^^^^^^^ #{described_class::MSG}
+ end
+ PATTERN
end
- PATTERN
+ end
+ end
+
+ context 'with DROP TABLE SQL literal' do
+ it 'does not register an offense' do
+ expect_no_offenses(<<~PATTERN)
+ def down
+ execute "DROP TABLE table"
+ end
+ PATTERN
+ end
+ end
+
+ context 'when in up method' do
+ it 'registers an offense' do
+ expect_offense(<<~PATTERN)
+ def up
+ execute "DROP TABLE table"
+ ^^^^^^^ #{described_class::MSG}
+ end
+ PATTERN
+ end
+ end
+
+ context 'when in change method' do
+ it 'registers an offense' do
+ expect_offense(<<~PATTERN)
+ def change
+ execute "DROP TABLE table"
+ ^^^^^^^ #{described_class::MSG}
+ end
+ PATTERN
+ end
end
end
diff --git a/spec/rubocop/cop/migration/hash_index_spec.rb b/spec/rubocop/cop/migration/hash_index_spec.rb
index e8b05a94653..3d26ea41d08 100644
--- a/spec/rubocop/cop/migration/hash_index_spec.rb
+++ b/spec/rubocop/cop/migration/hash_index_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/hash_index'
-describe RuboCop::Cop::Migration::HashIndex do
+RSpec.describe RuboCop::Cop::Migration::HashIndex, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/prevent_strings_spec.rb b/spec/rubocop/cop/migration/prevent_strings_spec.rb
index d0e97874aed..6882dca1926 100644
--- a/spec/rubocop/cop/migration/prevent_strings_spec.rb
+++ b/spec/rubocop/cop/migration/prevent_strings_spec.rb
@@ -1,12 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/prevent_strings'
-describe RuboCop::Cop::Migration::PreventStrings do
+RSpec.describe RuboCop::Cop::Migration::PreventStrings, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/remove_column_spec.rb b/spec/rubocop/cop/migration/remove_column_spec.rb
index bc2fa04ce64..7ef5556d8d7 100644
--- a/spec/rubocop/cop/migration/remove_column_spec.rb
+++ b/spec/rubocop/cop/migration/remove_column_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/remove_column'
-describe RuboCop::Cop::Migration::RemoveColumn do
+RSpec.describe RuboCop::Cop::Migration::RemoveColumn, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/remove_concurrent_index_spec.rb b/spec/rubocop/cop/migration/remove_concurrent_index_spec.rb
index 9de4c756f12..f70febb571d 100644
--- a/spec/rubocop/cop/migration/remove_concurrent_index_spec.rb
+++ b/spec/rubocop/cop/migration/remove_concurrent_index_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/remove_concurrent_index'
-describe RuboCop::Cop::Migration::RemoveConcurrentIndex do
+RSpec.describe RuboCop::Cop::Migration::RemoveConcurrentIndex, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/remove_index_spec.rb b/spec/rubocop/cop/migration/remove_index_spec.rb
index d343d27484a..cc82306a0f5 100644
--- a/spec/rubocop/cop/migration/remove_index_spec.rb
+++ b/spec/rubocop/cop/migration/remove_index_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/remove_index'
-describe RuboCop::Cop::Migration::RemoveIndex do
+RSpec.describe RuboCop::Cop::Migration::RemoveIndex, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/safer_boolean_column_spec.rb b/spec/rubocop/cop/migration/safer_boolean_column_spec.rb
index b27a4cd4f80..013f2edc5e9 100644
--- a/spec/rubocop/cop/migration/safer_boolean_column_spec.rb
+++ b/spec/rubocop/cop/migration/safer_boolean_column_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/safer_boolean_column'
-describe RuboCop::Cop::Migration::SaferBooleanColumn do
+RSpec.describe RuboCop::Cop::Migration::SaferBooleanColumn, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/schedule_async_spec.rb b/spec/rubocop/cop/migration/schedule_async_spec.rb
index 3453f1c51cc..a7246dfa73a 100644
--- a/spec/rubocop/cop/migration/schedule_async_spec.rb
+++ b/spec/rubocop/cop/migration/schedule_async_spec.rb
@@ -7,7 +7,7 @@ require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/migration/schedule_async'
-describe RuboCop::Cop::Migration::ScheduleAsync do
+RSpec.describe RuboCop::Cop::Migration::ScheduleAsync do
include CopHelper
let(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/timestamps_spec.rb b/spec/rubocop/cop/migration/timestamps_spec.rb
index d03c75e7cfc..14b3cb36cf8 100644
--- a/spec/rubocop/cop/migration/timestamps_spec.rb
+++ b/spec/rubocop/cop/migration/timestamps_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/timestamps'
-describe RuboCop::Cop::Migration::Timestamps do
+RSpec.describe RuboCop::Cop::Migration::Timestamps, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/update_column_in_batches_spec.rb b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
index f72efaf2eb2..5d96e8048bf 100644
--- a/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
+++ b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
@@ -7,7 +7,7 @@ require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/migration/update_column_in_batches'
-describe RuboCop::Cop::Migration::UpdateColumnInBatches do
+RSpec.describe RuboCop::Cop::Migration::UpdateColumnInBatches do
let(:cop) { described_class.new }
let(:tmp_rails_root) { Rails.root.join('tmp', 'rails_root') }
let(:migration_code) do
diff --git a/spec/rubocop/cop/migration/with_lock_retries_disallowed_method_spec.rb b/spec/rubocop/cop/migration/with_lock_retries_disallowed_method_spec.rb
index 48570c1c8d8..11e4d784617 100644
--- a/spec/rubocop/cop/migration/with_lock_retries_disallowed_method_spec.rb
+++ b/spec/rubocop/cop/migration/with_lock_retries_disallowed_method_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/with_lock_retries_disallowed_method'
-describe RuboCop::Cop::Migration::WithLockRetriesDisallowedMethod do
+RSpec.describe RuboCop::Cop::Migration::WithLockRetriesDisallowedMethod, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/migration/with_lock_retries_with_change_spec.rb b/spec/rubocop/cop/migration/with_lock_retries_with_change_spec.rb
index 75a1f939a9f..93b96f3a20e 100644
--- a/spec/rubocop/cop/migration/with_lock_retries_with_change_spec.rb
+++ b/spec/rubocop/cop/migration/with_lock_retries_with_change_spec.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
-
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/migration/with_lock_retries_with_change'
-describe RuboCop::Cop::Migration::WithLockRetriesWithChange do
+RSpec.describe RuboCop::Cop::Migration::WithLockRetriesWithChange, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/performance/ar_count_each_spec.rb b/spec/rubocop/cop/performance/ar_count_each_spec.rb
index 534fa55dd45..33b667dd52f 100644
--- a/spec/rubocop/cop/performance/ar_count_each_spec.rb
+++ b/spec/rubocop/cop/performance/ar_count_each_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
require 'fast_spec_helper'
+require 'rubocop'
require_relative '../../../../rubocop/cop/performance/ar_count_each.rb'
-describe RuboCop::Cop::Performance::ARCountEach, type: :rubocop do
+RSpec.describe RuboCop::Cop::Performance::ARCountEach, type: :rubocop do
include CopHelper
- include ExpectOffense
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/performance/ar_exists_and_present_blank_spec.rb b/spec/rubocop/cop/performance/ar_exists_and_present_blank_spec.rb
index da44004f947..972f3c52153 100644
--- a/spec/rubocop/cop/performance/ar_exists_and_present_blank_spec.rb
+++ b/spec/rubocop/cop/performance/ar_exists_and_present_blank_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
require 'fast_spec_helper'
+require 'rubocop'
require_relative '../../../../rubocop/cop/performance/ar_exists_and_present_blank.rb'
-describe RuboCop::Cop::Performance::ARExistsAndPresentBlank, type: :rubocop do
+RSpec.describe RuboCop::Cop::Performance::ARExistsAndPresentBlank, type: :rubocop do
include CopHelper
- include ExpectOffense
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/performance/readlines_each_spec.rb b/spec/rubocop/cop/performance/readlines_each_spec.rb
index e71aaaf3056..2f222f1dc00 100644
--- a/spec/rubocop/cop/performance/readlines_each_spec.rb
+++ b/spec/rubocop/cop/performance/readlines_each_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
require 'fast_spec_helper'
+require 'rubocop'
require_relative '../../../../rubocop/cop/performance/readlines_each'
-describe RuboCop::Cop::Performance::ReadlinesEach, type: :rubocop do
+RSpec.describe RuboCop::Cop::Performance::ReadlinesEach, type: :rubocop do
include CopHelper
- include ExpectOffense
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/prefer_class_methods_over_module_spec.rb b/spec/rubocop/cop/prefer_class_methods_over_module_spec.rb
index 4739f0e6c47..bed8d331209 100644
--- a/spec/rubocop/cop/prefer_class_methods_over_module_spec.rb
+++ b/spec/rubocop/cop/prefer_class_methods_over_module_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/prefer_class_methods_over_module'
-describe RuboCop::Cop::PreferClassMethodsOverModule do
+RSpec.describe RuboCop::Cop::PreferClassMethodsOverModule, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/project_path_helper_spec.rb b/spec/rubocop/cop/project_path_helper_spec.rb
index 1b69030c798..0e5b0b10ae6 100644
--- a/spec/rubocop/cop/project_path_helper_spec.rb
+++ b/spec/rubocop/cop/project_path_helper_spec.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/project_path_helper'
-describe RuboCop::Cop::ProjectPathHelper do
+RSpec.describe RuboCop::Cop::ProjectPathHelper, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/put_group_routes_under_scope_spec.rb b/spec/rubocop/cop/put_group_routes_under_scope_spec.rb
index c77412f91b4..2e577c9c578 100644
--- a/spec/rubocop/cop/put_group_routes_under_scope_spec.rb
+++ b/spec/rubocop/cop/put_group_routes_under_scope_spec.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require_relative '../../../rubocop/cop/put_group_routes_under_scope'
-describe RuboCop::Cop::PutGroupRoutesUnderScope do
+RSpec.describe RuboCop::Cop::PutGroupRoutesUnderScope, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/put_project_routes_under_scope_spec.rb b/spec/rubocop/cop/put_project_routes_under_scope_spec.rb
index 80ac4cc52e9..66e9044c453 100644
--- a/spec/rubocop/cop/put_project_routes_under_scope_spec.rb
+++ b/spec/rubocop/cop/put_project_routes_under_scope_spec.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require_relative '../../../rubocop/cop/put_project_routes_under_scope'
-describe RuboCop::Cop::PutProjectRoutesUnderScope do
+RSpec.describe RuboCop::Cop::PutProjectRoutesUnderScope, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/qa/ambiguous_page_object_name_spec.rb b/spec/rubocop/cop/qa/ambiguous_page_object_name_spec.rb
index 8ee720af9a5..484b5fad473 100644
--- a/spec/rubocop/cop/qa/ambiguous_page_object_name_spec.rb
+++ b/spec/rubocop/cop/qa/ambiguous_page_object_name_spec.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/qa/ambiguous_page_object_name'
-describe RuboCop::Cop::QA::AmbiguousPageObjectName do
+RSpec.describe RuboCop::Cop::QA::AmbiguousPageObjectName, type: :rubocop do
include CopHelper
let(:source_file) { 'qa/page.rb' }
diff --git a/spec/rubocop/cop/qa/element_with_pattern_spec.rb b/spec/rubocop/cop/qa/element_with_pattern_spec.rb
index fee390caa9f..0e599701531 100644
--- a/spec/rubocop/cop/qa/element_with_pattern_spec.rb
+++ b/spec/rubocop/cop/qa/element_with_pattern_spec.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/qa/element_with_pattern'
-describe RuboCop::Cop::QA::ElementWithPattern do
+RSpec.describe RuboCop::Cop::QA::ElementWithPattern, type: :rubocop do
include CopHelper
let(:source_file) { 'qa/page.rb' }
diff --git a/spec/rubocop/cop/rspec/any_instance_of_spec.rb b/spec/rubocop/cop/rspec/any_instance_of_spec.rb
index b16f8ac189c..11c0f109850 100644
--- a/spec/rubocop/cop/rspec/any_instance_of_spec.rb
+++ b/spec/rubocop/cop/rspec/any_instance_of_spec.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require_relative '../../../../rubocop/cop/rspec/any_instance_of'
-describe RuboCop::Cop::RSpec::AnyInstanceOf do
+RSpec.describe RuboCop::Cop::RSpec::AnyInstanceOf, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/rspec/be_success_matcher_spec.rb b/spec/rubocop/cop/rspec/be_success_matcher_spec.rb
index 12aa7d1643e..a16cd8b634f 100644
--- a/spec/rubocop/cop/rspec/be_success_matcher_spec.rb
+++ b/spec/rubocop/cop/rspec/be_success_matcher_spec.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require_relative '../../../../rubocop/cop/rspec/be_success_matcher'
-describe RuboCop::Cop::RSpec::BeSuccessMatcher do
+RSpec.describe RuboCop::Cop::RSpec::BeSuccessMatcher, type: :rubocop do
include CopHelper
let(:source_file) { 'spec/foo_spec.rb' }
diff --git a/spec/rubocop/cop/rspec/env_assignment_spec.rb b/spec/rubocop/cop/rspec/env_assignment_spec.rb
index 1c7cfb5c827..72ad584dd6f 100644
--- a/spec/rubocop/cop/rspec/env_assignment_spec.rb
+++ b/spec/rubocop/cop/rspec/env_assignment_spec.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/rspec/env_assignment'
-describe RuboCop::Cop::RSpec::EnvAssignment do
+RSpec.describe RuboCop::Cop::RSpec::EnvAssignment, type: :rubocop do
include CopHelper
offense_call_single_quotes_key = %(ENV['FOO'] = 'bar').freeze
diff --git a/spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb b/spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb
index 20013519db4..1bb93402a5b 100644
--- a/spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb
+++ b/spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/rspec/factories_in_migration_specs'
-describe RuboCop::Cop::RSpec::FactoriesInMigrationSpecs do
+RSpec.describe RuboCop::Cop::RSpec::FactoriesInMigrationSpecs, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/rspec/have_gitlab_http_status_spec.rb b/spec/rubocop/cop/rspec/have_gitlab_http_status_spec.rb
index 4aa45e66ca7..f6040350dc0 100644
--- a/spec/rubocop/cop/rspec/have_gitlab_http_status_spec.rb
+++ b/spec/rubocop/cop/rspec/have_gitlab_http_status_spec.rb
@@ -8,7 +8,7 @@ require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/rspec/have_gitlab_http_status'
-describe RuboCop::Cop::RSpec::HaveGitlabHttpStatus do
+RSpec.describe RuboCop::Cop::RSpec::HaveGitlabHttpStatus do
include CopHelper
using RSpec::Parameterized::TableSyntax
diff --git a/spec/rubocop/cop/rspec/modify_sidekiq_middleware_spec.rb b/spec/rubocop/cop/rspec/modify_sidekiq_middleware_spec.rb
index d3d323b6643..ca47bba4264 100644
--- a/spec/rubocop/cop/rspec/modify_sidekiq_middleware_spec.rb
+++ b/spec/rubocop/cop/rspec/modify_sidekiq_middleware_spec.rb
@@ -4,9 +4,8 @@ require 'fast_spec_helper'
require 'rubocop'
require_relative '../../../../rubocop/cop/rspec/modify_sidekiq_middleware'
-describe RuboCop::Cop::RSpec::ModifySidekiqMiddleware, type: :rubocop do
+RSpec.describe RuboCop::Cop::RSpec::ModifySidekiqMiddleware, type: :rubocop do
include CopHelper
- include ExpectOffense
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/rspec/top_level_describe_path_spec.rb b/spec/rubocop/cop/rspec/top_level_describe_path_spec.rb
index ee6b6d39cb4..92a76bde243 100644
--- a/spec/rubocop/cop/rspec/top_level_describe_path_spec.rb
+++ b/spec/rubocop/cop/rspec/top_level_describe_path_spec.rb
@@ -1,14 +1,10 @@
# frozen_string_literal: true
require 'fast_spec_helper'
-
require 'rubocop'
-require 'rubocop/rspec/support'
-
require_relative '../../../../rubocop/cop/rspec/top_level_describe_path'
-describe RuboCop::Cop::RSpec::TopLevelDescribePath do
- include RuboCop::RSpec::ExpectOffense
+RSpec.describe RuboCop::Cop::RSpec::TopLevelDescribePath, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/ruby_interpolation_in_translation_spec.rb b/spec/rubocop/cop/ruby_interpolation_in_translation_spec.rb
index 7bd50866577..938ddc7fb6a 100644
--- a/spec/rubocop/cop/ruby_interpolation_in_translation_spec.rb
+++ b/spec/rubocop/cop/ruby_interpolation_in_translation_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
@@ -9,7 +9,7 @@ require_relative '../../../rubocop/cop/ruby_interpolation_in_translation'
# Disabling interpolation check as we deliberately want to have #{} in strings.
# rubocop:disable Lint/InterpolationCheck
-describe RuboCop::Cop::RubyInterpolationInTranslation do
+RSpec.describe RuboCop::Cop::RubyInterpolationInTranslation, type: :rubocop do
subject(:cop) { described_class.new }
it 'does not add an offence for a regular messages' do
diff --git a/spec/rubocop/cop/safe_params_spec.rb b/spec/rubocop/cop/safe_params_spec.rb
index 4f02b8e9008..c4f683a41bf 100644
--- a/spec/rubocop/cop/safe_params_spec.rb
+++ b/spec/rubocop/cop/safe_params_spec.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/safe_params'
-describe RuboCop::Cop::SafeParams do
+RSpec.describe RuboCop::Cop::SafeParams, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/scalability/bulk_perform_with_context_spec.rb b/spec/rubocop/cop/scalability/bulk_perform_with_context_spec.rb
index 61603d0100e..48964ab76e0 100644
--- a/spec/rubocop/cop/scalability/bulk_perform_with_context_spec.rb
+++ b/spec/rubocop/cop/scalability/bulk_perform_with_context_spec.rb
@@ -4,9 +4,8 @@ require 'fast_spec_helper'
require 'rubocop'
require_relative '../../../../rubocop/cop/scalability/bulk_perform_with_context'
-describe RuboCop::Cop::Scalability::BulkPerformWithContext, type: :rubocop do
+RSpec.describe RuboCop::Cop::Scalability::BulkPerformWithContext, type: :rubocop do
include CopHelper
- include ExpectOffense
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/scalability/cron_worker_context_spec.rb b/spec/rubocop/cop/scalability/cron_worker_context_spec.rb
index e917d33b1e5..4b4ee7258f1 100644
--- a/spec/rubocop/cop/scalability/cron_worker_context_spec.rb
+++ b/spec/rubocop/cop/scalability/cron_worker_context_spec.rb
@@ -4,9 +4,8 @@ require 'fast_spec_helper'
require 'rubocop'
require_relative '../../../../rubocop/cop/scalability/cron_worker_context'
-describe RuboCop::Cop::Scalability::CronWorkerContext, type: :rubocop do
+RSpec.describe RuboCop::Cop::Scalability::CronWorkerContext, type: :rubocop do
include CopHelper
- include ExpectOffense
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/scalability/file_uploads_spec.rb b/spec/rubocop/cop/scalability/file_uploads_spec.rb
index b0be9ac2b51..ed826728681 100644
--- a/spec/rubocop/cop/scalability/file_uploads_spec.rb
+++ b/spec/rubocop/cop/scalability/file_uploads_spec.rb
@@ -4,9 +4,8 @@ require 'fast_spec_helper'
require 'rubocop'
require_relative '../../../../rubocop/cop/scalability/file_uploads'
-describe RuboCop::Cop::Scalability::FileUploads, type: :rubocop do
+RSpec.describe RuboCop::Cop::Scalability::FileUploads, type: :rubocop do
include CopHelper
- include ExpectOffense
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/scalability/idempotent_worker_spec.rb b/spec/rubocop/cop/scalability/idempotent_worker_spec.rb
index 73cacc984e9..9197cc954f5 100644
--- a/spec/rubocop/cop/scalability/idempotent_worker_spec.rb
+++ b/spec/rubocop/cop/scalability/idempotent_worker_spec.rb
@@ -4,9 +4,8 @@ require 'fast_spec_helper'
require 'rubocop'
require_relative '../../../../rubocop/cop/scalability/idempotent_worker'
-describe RuboCop::Cop::Scalability::IdempotentWorker, type: :rubocop do
+RSpec.describe RuboCop::Cop::Scalability::IdempotentWorker, type: :rubocop do
include CopHelper
- include ExpectOffense
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/sidekiq_options_queue_spec.rb b/spec/rubocop/cop/sidekiq_options_queue_spec.rb
index c10fd7bd32b..e76265d426c 100644
--- a/spec/rubocop/cop/sidekiq_options_queue_spec.rb
+++ b/spec/rubocop/cop/sidekiq_options_queue_spec.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../rubocop/cop/sidekiq_options_queue'
-describe RuboCop::Cop::SidekiqOptionsQueue do
+RSpec.describe RuboCop::Cop::SidekiqOptionsQueue, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/static_translation_definition_spec.rb b/spec/rubocop/cop/static_translation_definition_spec.rb
index b85f9da9b4e..b6c9f6a25df 100644
--- a/spec/rubocop/cop/static_translation_definition_spec.rb
+++ b/spec/rubocop/cop/static_translation_definition_spec.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
-require 'rubocop/rspec/support'
+require 'rspec-parameterized'
require_relative '../../../rubocop/cop/static_translation_definition'
-describe RuboCop::Cop::StaticTranslationDefinition do
+RSpec.describe RuboCop::Cop::StaticTranslationDefinition, type: :rubocop do
include CopHelper
using RSpec::Parameterized::TableSyntax
diff --git a/spec/rubocop/migration_helpers_spec.rb b/spec/rubocop/migration_helpers_spec.rb
index 73ced8c58da..f0be21c9d70 100644
--- a/spec/rubocop/migration_helpers_spec.rb
+++ b/spec/rubocop/migration_helpers_spec.rb
@@ -6,7 +6,7 @@ require 'rspec-parameterized'
require_relative '../../rubocop/migration_helpers'
-describe RuboCop::MigrationHelpers do
+RSpec.describe RuboCop::MigrationHelpers do
using RSpec::Parameterized::TableSyntax
subject(:fake_cop) { Class.new { include RuboCop::MigrationHelpers }.new }
diff --git a/spec/rubocop/qa_helpers_spec.rb b/spec/rubocop/qa_helpers_spec.rb
index 26e4c1ca6f0..786b9c78952 100644
--- a/spec/rubocop/qa_helpers_spec.rb
+++ b/spec/rubocop/qa_helpers_spec.rb
@@ -1,10 +1,11 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rubocop'
+require 'parser/current'
require_relative '../../rubocop/qa_helpers'
-describe RuboCop::QAHelpers do
+RSpec.describe RuboCop::QAHelpers, type: :rubocop do
def parse_source(source, path = 'foo.rb')
buffer = Parser::Source::Buffer.new(path)
buffer.source = source
@@ -23,13 +24,13 @@ describe RuboCop::QAHelpers do
describe '#in_qa_file?' do
it 'returns true for a node in the qa/ directory' do
- node = parse_source('10', Rails.root.join('qa', 'qa', 'page', 'dashboard', 'groups.rb'))
+ node = parse_source('10', rails_root_join('qa', 'qa', 'page', 'dashboard', 'groups.rb'))
expect(cop.in_qa_file?(node)).to eq(true)
end
it 'returns false for a node outside the qa/ directory' do
- node = parse_source('10', Rails.root.join('app', 'foo', 'foo.rb'))
+ node = parse_source('10', rails_root_join('app', 'foo', 'foo.rb'))
expect(cop.in_qa_file?(node)).to eq(false)
end
diff --git a/spec/serializers/accessibility_error_entity_spec.rb b/spec/serializers/accessibility_error_entity_spec.rb
index e9bfabb7aa8..afbff15a195 100644
--- a/spec/serializers/accessibility_error_entity_spec.rb
+++ b/spec/serializers/accessibility_error_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AccessibilityErrorEntity do
+RSpec.describe AccessibilityErrorEntity do
let(:entity) { described_class.new(accessibility_error) }
describe '#as_json' do
diff --git a/spec/serializers/accessibility_reports_comparer_entity_spec.rb b/spec/serializers/accessibility_reports_comparer_entity_spec.rb
index ed2c17de640..3024974710e 100644
--- a/spec/serializers/accessibility_reports_comparer_entity_spec.rb
+++ b/spec/serializers/accessibility_reports_comparer_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AccessibilityReportsComparerEntity do
+RSpec.describe AccessibilityReportsComparerEntity do
let(:entity) { described_class.new(comparer) }
let(:comparer) { Gitlab::Ci::Reports::AccessibilityReportsComparer.new(base_report, head_report) }
let(:base_report) { Gitlab::Ci::Reports::AccessibilityReports.new }
diff --git a/spec/serializers/accessibility_reports_comparer_serializer_spec.rb b/spec/serializers/accessibility_reports_comparer_serializer_spec.rb
index 37dc760fdec..ef56f5b6b6b 100644
--- a/spec/serializers/accessibility_reports_comparer_serializer_spec.rb
+++ b/spec/serializers/accessibility_reports_comparer_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AccessibilityReportsComparerSerializer do
+RSpec.describe AccessibilityReportsComparerSerializer do
let(:project) { double(:project) }
let(:serializer) { described_class.new(project: project).represent(comparer) }
let(:comparer) { Gitlab::Ci::Reports::AccessibilityReportsComparer.new(base_report, head_report) }
diff --git a/spec/serializers/analytics_build_entity_spec.rb b/spec/serializers/analytics_build_entity_spec.rb
index dfa16075d20..20bd017d1cf 100644
--- a/spec/serializers/analytics_build_entity_spec.rb
+++ b/spec/serializers/analytics_build_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AnalyticsBuildEntity do
+RSpec.describe AnalyticsBuildEntity do
let(:entity) do
described_class.new(build, request: double)
end
diff --git a/spec/serializers/analytics_build_serializer_spec.rb b/spec/serializers/analytics_build_serializer_spec.rb
index 04a387fd353..94a42ba4ffa 100644
--- a/spec/serializers/analytics_build_serializer_spec.rb
+++ b/spec/serializers/analytics_build_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AnalyticsBuildSerializer do
+RSpec.describe AnalyticsBuildSerializer do
let(:resource) { create(:ci_build) }
subject { described_class.new.represent(resource) }
diff --git a/spec/serializers/analytics_issue_entity_spec.rb b/spec/serializers/analytics_issue_entity_spec.rb
index 555efe136e6..2518eec8c23 100644
--- a/spec/serializers/analytics_issue_entity_spec.rb
+++ b/spec/serializers/analytics_issue_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AnalyticsIssueEntity do
+RSpec.describe AnalyticsIssueEntity do
let(:user) { create(:user) }
let(:entity_hash) do
{
diff --git a/spec/serializers/analytics_issue_serializer_spec.rb b/spec/serializers/analytics_issue_serializer_spec.rb
index 9b29739a8f2..5d268fe514b 100644
--- a/spec/serializers/analytics_issue_serializer_spec.rb
+++ b/spec/serializers/analytics_issue_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AnalyticsIssueSerializer do
+RSpec.describe AnalyticsIssueSerializer do
subject do
described_class
.new(entity: :merge_request)
diff --git a/spec/serializers/analytics_merge_request_serializer_spec.rb b/spec/serializers/analytics_merge_request_serializer_spec.rb
index c82eb28a28b..adf7321c738 100644
--- a/spec/serializers/analytics_merge_request_serializer_spec.rb
+++ b/spec/serializers/analytics_merge_request_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AnalyticsMergeRequestSerializer do
+RSpec.describe AnalyticsMergeRequestSerializer do
subject do
described_class
.new(entity: :merge_request)
diff --git a/spec/serializers/analytics_stage_serializer_spec.rb b/spec/serializers/analytics_stage_serializer_spec.rb
index 1f1a0180b1f..0f2de262188 100644
--- a/spec/serializers/analytics_stage_serializer_spec.rb
+++ b/spec/serializers/analytics_stage_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AnalyticsStageSerializer do
+RSpec.describe AnalyticsStageSerializer do
subject do
described_class.new.represent(resource)
end
diff --git a/spec/serializers/analytics_summary_serializer_spec.rb b/spec/serializers/analytics_summary_serializer_spec.rb
index ed126720a55..cd8be07827d 100644
--- a/spec/serializers/analytics_summary_serializer_spec.rb
+++ b/spec/serializers/analytics_summary_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AnalyticsSummarySerializer do
+RSpec.describe AnalyticsSummarySerializer do
subject do
described_class.new.represent(resource)
end
diff --git a/spec/serializers/blob_entity_spec.rb b/spec/serializers/blob_entity_spec.rb
index 3cd967ed44c..b8c8c4c17de 100644
--- a/spec/serializers/blob_entity_spec.rb
+++ b/spec/serializers/blob_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BlobEntity do
+RSpec.describe BlobEntity do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:blob) { project.commit('master').diffs.diff_files.first.blob }
diff --git a/spec/serializers/board_serializer_spec.rb b/spec/serializers/board_serializer_spec.rb
index 8a633e46316..9e6d5a93d53 100644
--- a/spec/serializers/board_serializer_spec.rb
+++ b/spec/serializers/board_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BoardSerializer do
+RSpec.describe BoardSerializer do
let(:resource) { create(:board) }
let(:json_entity) do
described_class.new
diff --git a/spec/serializers/build_action_entity_spec.rb b/spec/serializers/build_action_entity_spec.rb
index 7cd1fdcda22..75ae244db83 100644
--- a/spec/serializers/build_action_entity_spec.rb
+++ b/spec/serializers/build_action_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BuildActionEntity do
+RSpec.describe BuildActionEntity do
let(:job) { create(:ci_build, name: 'test_job') }
let(:request) { double('request') }
let(:user) { create(:user) }
diff --git a/spec/serializers/build_artifact_entity_spec.rb b/spec/serializers/build_artifact_entity_spec.rb
index afa2aa3d254..02c172d723f 100644
--- a/spec/serializers/build_artifact_entity_spec.rb
+++ b/spec/serializers/build_artifact_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BuildArtifactEntity do
+RSpec.describe BuildArtifactEntity do
let(:job) { create(:ci_build) }
let(:artifact) { create(:ci_job_artifact, :codequality, expire_at: 1.hour.from_now, job: job) }
diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb
index 92917f6ea25..ef6472e07a0 100644
--- a/spec/serializers/build_details_entity_spec.rb
+++ b/spec/serializers/build_details_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BuildDetailsEntity do
+RSpec.describe BuildDetailsEntity do
include ProjectForksHelper
let_it_be(:user) { create(:admin) }
diff --git a/spec/serializers/build_serializer_spec.rb b/spec/serializers/build_serializer_spec.rb
index c7bb6864361..f3584beb39b 100644
--- a/spec/serializers/build_serializer_spec.rb
+++ b/spec/serializers/build_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BuildSerializer do
+RSpec.describe BuildSerializer do
let(:user) { create(:user) }
let(:serializer) do
diff --git a/spec/serializers/build_trace_entity_spec.rb b/spec/serializers/build_trace_entity_spec.rb
index bafead04a51..82bd56caaac 100644
--- a/spec/serializers/build_trace_entity_spec.rb
+++ b/spec/serializers/build_trace_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BuildTraceEntity do
+RSpec.describe BuildTraceEntity do
let(:build) { build_stubbed(:ci_build) }
let(:request) { double('request') }
@@ -13,7 +13,7 @@ describe BuildTraceEntity do
end
let(:build_trace) do
- Ci::BuildTrace.new(build: build, stream: stream, content_format: content_format, state: nil)
+ Ci::BuildTrace.new(build: build, stream: stream, state: nil)
end
let(:entity) do
@@ -22,42 +22,24 @@ describe BuildTraceEntity do
subject { entity.as_json }
- shared_examples 'includes build and trace metadata' do
- it 'includes build attributes' do
- expect(subject[:id]).to eq(build.id)
- expect(subject[:status]).to eq(build.status)
- expect(subject[:complete]).to eq(build.complete?)
- end
-
- it 'includes trace metadata' do
- expect(subject).to include(:state)
- expect(subject).to include(:append)
- expect(subject).to include(:truncated)
- expect(subject).to include(:offset)
- expect(subject).to include(:size)
- expect(subject).to include(:total)
- end
+ it 'includes build attributes' do
+ expect(subject[:id]).to eq(build.id)
+ expect(subject[:status]).to eq(build.status)
+ expect(subject[:complete]).to eq(build.complete?)
end
- context 'when content format is :json' do
- let(:content_format) { :json }
-
- it_behaves_like 'includes build and trace metadata'
-
- it 'includes the trace content in json' do
- expect(subject[:lines]).to eq([
- { offset: 0, content: [{ text: 'the-trace' }] }
- ])
- end
+ it 'includes trace metadata' do
+ expect(subject).to include(:state)
+ expect(subject).to include(:append)
+ expect(subject).to include(:truncated)
+ expect(subject).to include(:offset)
+ expect(subject).to include(:size)
+ expect(subject).to include(:total)
end
- context 'when content format is :html' do
- let(:content_format) { :html }
-
- it_behaves_like 'includes build and trace metadata'
-
- it 'includes the trace content in json' do
- expect(subject[:html]).to eq('<span>the-trace</span>')
- end
+ it 'includes the trace content in json' do
+ expect(subject[:lines]).to eq([
+ { offset: 0, content: [{ text: 'the-trace' }] }
+ ])
end
end
diff --git a/spec/serializers/ci/dag_job_entity_spec.rb b/spec/serializers/ci/dag_job_entity_spec.rb
index eaaf39d6bfc..5e2b186186f 100644
--- a/spec/serializers/ci/dag_job_entity_spec.rb
+++ b/spec/serializers/ci/dag_job_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::DagJobEntity do
+RSpec.describe Ci::DagJobEntity do
let_it_be(:request) { double(:request) }
let(:job) { create(:ci_build, name: 'dag_job') }
@@ -11,10 +11,18 @@ describe Ci::DagJobEntity do
describe '#as_json' do
subject { entity.as_json }
+ RSpec.shared_examples "matches schema" do
+ it "matches schema" do
+ expect(subject.to_json).to match_schema('entities/dag_job')
+ end
+ end
+
it 'contains the name' do
expect(subject[:name]).to eq 'dag_job'
end
+ it_behaves_like "matches schema"
+
context 'when job is stage scheduled' do
it 'contains the name scheduling_type' do
expect(subject[:scheduling_type]).to eq 'stage'
@@ -23,6 +31,8 @@ describe Ci::DagJobEntity do
it 'does not expose needs' do
expect(subject).not_to include(:needs)
end
+
+ it_behaves_like "matches schema"
end
context 'when job is dag scheduled' do
@@ -32,18 +42,24 @@ describe Ci::DagJobEntity do
expect(subject[:scheduling_type]).to eq 'dag'
end
+ it_behaves_like "matches schema"
+
context 'when job has needs' do
let!(:need) { create(:ci_build_need, build: job, name: 'compile') }
it 'exposes the array of needs' do
expect(subject[:needs]).to eq ['compile']
end
+
+ it_behaves_like "matches schema"
end
context 'when job has empty needs' do
it 'exposes an empty array of needs' do
expect(subject[:needs]).to eq []
end
+
+ it_behaves_like "matches schema"
end
end
end
diff --git a/spec/serializers/ci/dag_job_group_entity_spec.rb b/spec/serializers/ci/dag_job_group_entity_spec.rb
index a25723894fd..5a75c04efe5 100644
--- a/spec/serializers/ci/dag_job_group_entity_spec.rb
+++ b/spec/serializers/ci/dag_job_group_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::DagJobGroupEntity do
+RSpec.describe Ci::DagJobGroupEntity do
let_it_be(:request) { double(:request) }
let_it_be(:pipeline) { create(:ci_pipeline) }
let_it_be(:stage) { create(:ci_stage, pipeline: pipeline) }
@@ -31,6 +31,10 @@ describe Ci::DagJobGroupEntity do
expect(exposed_jobs.size).to eq 1
expect(exposed_jobs.first.fetch(:name)).to eq 'test'
end
+
+ it 'matches schema' do
+ expect(subject.to_json).to match_schema('entities/dag_job_group')
+ end
end
context 'when group contains multiple parallel jobs' do
@@ -53,6 +57,10 @@ describe Ci::DagJobGroupEntity do
expect(exposed_jobs.first.fetch(:name)).to eq 'test 1/2'
expect(exposed_jobs.last.fetch(:name)).to eq 'test 2/2'
end
+
+ it 'matches schema' do
+ expect(subject.to_json).to match_schema('entities/dag_job_group')
+ end
end
end
end
diff --git a/spec/serializers/ci/dag_pipeline_entity_spec.rb b/spec/serializers/ci/dag_pipeline_entity_spec.rb
index fab8798effc..e1703b09f97 100644
--- a/spec/serializers/ci/dag_pipeline_entity_spec.rb
+++ b/spec/serializers/ci/dag_pipeline_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::DagPipelineEntity do
+RSpec.describe Ci::DagPipelineEntity do
let_it_be(:request) { double(:request) }
let(:pipeline) { create(:ci_pipeline) }
@@ -11,12 +11,20 @@ describe Ci::DagPipelineEntity do
describe '#as_json' do
subject { entity.as_json }
+ RSpec.shared_examples "matches schema" do
+ it 'matches schema' do
+ expect(subject.to_json).to match_schema('entities/dag_pipeline')
+ end
+ end
+
context 'when pipeline is empty' do
it 'contains stages' do
expect(subject).to include(:stages)
expect(subject[:stages]).to be_empty
end
+
+ it_behaves_like "matches schema"
end
context 'when pipeline has jobs' do
@@ -30,6 +38,8 @@ describe Ci::DagPipelineEntity do
expect(stages.size).to eq 3
expect(stages.map { |s| s[:name] }).to contain_exactly('build', 'test', 'deploy')
end
+
+ it_behaves_like "matches schema"
end
context 'when pipeline has parallel jobs, DAG needs and GenericCommitStatus' do
@@ -138,6 +148,8 @@ describe Ci::DagPipelineEntity do
expect(subject.fetch(:stages)[2].fetch(:name)).to eq 'deploy'
expect(subject.fetch(:stages)[2]).to eq expected_result.fetch(:stages)[2]
end
+
+ it_behaves_like "matches schema"
end
end
end
diff --git a/spec/serializers/ci/dag_pipeline_serializer_spec.rb b/spec/serializers/ci/dag_pipeline_serializer_spec.rb
index abf895c3e77..856f6760d5d 100644
--- a/spec/serializers/ci/dag_pipeline_serializer_spec.rb
+++ b/spec/serializers/ci/dag_pipeline_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::DagPipelineSerializer do
+RSpec.describe Ci::DagPipelineSerializer do
describe '#represent' do
subject { described_class.new.represent(pipeline) }
@@ -13,5 +13,9 @@ describe Ci::DagPipelineSerializer do
expect(subject[:stages]).to be_present
expect(subject[:stages].size).to eq 1
end
+
+ it 'matches schema' do
+ expect(subject.to_json).to match_schema('entities/dag_pipeline')
+ end
end
end
diff --git a/spec/serializers/ci/dag_stage_entity_spec.rb b/spec/serializers/ci/dag_stage_entity_spec.rb
index 5c6aa7faee4..0262ccdac68 100644
--- a/spec/serializers/ci/dag_stage_entity_spec.rb
+++ b/spec/serializers/ci/dag_stage_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::DagStageEntity do
+RSpec.describe Ci::DagStageEntity do
let_it_be(:pipeline) { create(:ci_pipeline) }
let_it_be(:request) { double(:request) }
@@ -27,5 +27,9 @@ describe Ci::DagStageEntity do
expect(job_group[:size]).to eq 1
expect(job_group[:jobs]).not_to be_empty
end
+
+ it "matches schema" do
+ expect(subject.to_json).to match_schema('entities/dag_stage')
+ end
end
end
diff --git a/spec/serializers/ci/daily_build_group_report_result_entity_spec.rb b/spec/serializers/ci/daily_build_group_report_result_entity_spec.rb
index cc35b3bc8b8..f468acdcc64 100644
--- a/spec/serializers/ci/daily_build_group_report_result_entity_spec.rb
+++ b/spec/serializers/ci/daily_build_group_report_result_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::DailyBuildGroupReportResultEntity do
+RSpec.describe Ci::DailyBuildGroupReportResultEntity do
let(:report_result) { double(date: '2020-05-20', group_name: 'rspec', data: { 'coverage' => 79.1 }) }
let(:entity) { described_class.new(report_result, param_type: param_type) }
let(:param_type) { 'coverage' }
diff --git a/spec/serializers/ci/daily_build_group_report_result_serializer_spec.rb b/spec/serializers/ci/daily_build_group_report_result_serializer_spec.rb
index 4a781971ae0..69bf599c0dd 100644
--- a/spec/serializers/ci/daily_build_group_report_result_serializer_spec.rb
+++ b/spec/serializers/ci/daily_build_group_report_result_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::DailyBuildGroupReportResultSerializer do
+RSpec.describe Ci::DailyBuildGroupReportResultSerializer do
let(:report_result) do
[
double(date: '2020-05-20', group_name: 'rspec', data: { 'coverage' => 79.1 }),
diff --git a/spec/serializers/group_variable_entity_spec.rb b/spec/serializers/ci/group_variable_entity_spec.rb
index e6b51e0d626..a7e12905924 100644
--- a/spec/serializers/group_variable_entity_spec.rb
+++ b/spec/serializers/ci/group_variable_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupVariableEntity do
+RSpec.describe Ci::GroupVariableEntity do
let(:variable) { create(:ci_group_variable) }
let(:entity) { described_class.new(variable) }
diff --git a/spec/serializers/variable_entity_spec.rb b/spec/serializers/ci/variable_entity_spec.rb
index 3cb18dab314..38da0b16bbd 100644
--- a/spec/serializers/variable_entity_spec.rb
+++ b/spec/serializers/ci/variable_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe VariableEntity do
+RSpec.describe Ci::VariableEntity do
let(:variable) { create(:ci_variable) }
let(:entity) { described_class.new(variable) }
diff --git a/spec/serializers/cluster_application_entity_spec.rb b/spec/serializers/cluster_application_entity_spec.rb
index b81bdaa0d72..aa2bb25b17c 100644
--- a/spec/serializers/cluster_application_entity_spec.rb
+++ b/spec/serializers/cluster_application_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ClusterApplicationEntity do
+RSpec.describe ClusterApplicationEntity do
describe '#as_json' do
let(:application) { build(:clusters_applications_helm, version: '0.1.1') }
diff --git a/spec/serializers/cluster_entity_spec.rb b/spec/serializers/cluster_entity_spec.rb
index 16247eef655..223d37b6acd 100644
--- a/spec/serializers/cluster_entity_spec.rb
+++ b/spec/serializers/cluster_entity_spec.rb
@@ -2,9 +2,14 @@
require 'spec_helper'
-describe ClusterEntity do
+RSpec.describe ClusterEntity do
+ include Gitlab::Routing.url_helpers
+
describe '#as_json' do
- subject { described_class.new(cluster).as_json }
+ let(:user) { nil }
+ let(:request) { EntityRequest.new({ current_user: user }) }
+
+ subject { described_class.new(cluster, request: request).as_json }
context 'when provider type is gcp' do
let(:cluster) { create(:cluster, :instance, provider_type: :gcp, provider_gcp: provider) }
@@ -40,7 +45,7 @@ describe ClusterEntity do
context 'when no application has been installed' do
let(:cluster) { create(:cluster, :instance) }
- subject { described_class.new(cluster).as_json[:applications]}
+ subject { described_class.new(cluster, request: request).as_json[:applications]}
it 'contains helm as not_installable' do
expect(subject).not_to be_empty
@@ -50,5 +55,28 @@ describe ClusterEntity do
expect(helm[:status]).to eq(:not_installable)
end
end
+
+ context 'gitlab_managed_apps_logs_path' do
+ let(:cluster) { create(:cluster, :project) }
+ let(:user) { create(:user) }
+
+ subject { described_class.new(cluster, request: request).as_json }
+
+ before do
+ allow_next_instance_of(Clusters::ClusterPresenter) do |presenter|
+ allow(presenter).to receive(:show_path).and_return(nil)
+ end
+ end
+
+ it 'return projects log explorer path' do
+ log_explorer_path = project_logs_path(cluster.project, cluster_id: cluster.id)
+
+ expect_next_instance_of(Clusters::ClusterPresenter, cluster, current_user: user) do |presenter|
+ expect(presenter).to receive(:gitlab_managed_apps_logs_path).and_return(log_explorer_path)
+ end
+
+ expect(subject[:gitlab_managed_apps_logs_path]).to eq(log_explorer_path)
+ end
+ end
end
end
diff --git a/spec/serializers/cluster_serializer_spec.rb b/spec/serializers/cluster_serializer_spec.rb
index b7d7307d40b..ea1cf6ff59a 100644
--- a/spec/serializers/cluster_serializer_spec.rb
+++ b/spec/serializers/cluster_serializer_spec.rb
@@ -2,17 +2,18 @@
require 'spec_helper'
-describe ClusterSerializer do
+RSpec.describe ClusterSerializer do
let(:cluster) { create(:cluster, :project, provider_type: :user) }
describe '#represent_list' do
- subject { described_class.new.represent_list(cluster).keys }
+ subject { described_class.new(current_user: nil).represent_list(cluster).keys }
it 'serializes attrs correctly' do
is_expected.to contain_exactly(
:cluster_type,
:enabled,
:environment_scope,
+ :gitlab_managed_apps_logs_path,
:name,
:nodes,
:path,
@@ -22,7 +23,7 @@ describe ClusterSerializer do
end
describe '#represent_status' do
- subject { described_class.new.represent_status(cluster).keys }
+ subject { described_class.new(current_user: nil).represent_status(cluster).keys }
context 'when provider type is gcp and cluster is errored' do
let(:cluster) do
diff --git a/spec/serializers/commit_entity_spec.rb b/spec/serializers/commit_entity_spec.rb
index 6abe8504b93..e2ea63893a4 100644
--- a/spec/serializers/commit_entity_spec.rb
+++ b/spec/serializers/commit_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CommitEntity do
+RSpec.describe CommitEntity do
let(:signature_html) { 'TEST' }
let(:entity) do
diff --git a/spec/serializers/container_repositories_serializer_spec.rb b/spec/serializers/container_repositories_serializer_spec.rb
index 382778389b3..a0d08a8ba44 100644
--- a/spec/serializers/container_repositories_serializer_spec.rb
+++ b/spec/serializers/container_repositories_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerRepositoriesSerializer do
+RSpec.describe ContainerRepositoriesSerializer do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:resource) { create(:container_repository, name: 'image', project: project) }
diff --git a/spec/serializers/container_repository_entity_spec.rb b/spec/serializers/container_repository_entity_spec.rb
index 1f85c6e6a46..43969c63471 100644
--- a/spec/serializers/container_repository_entity_spec.rb
+++ b/spec/serializers/container_repository_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerRepositoryEntity do
+RSpec.describe ContainerRepositoryEntity do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:repository) { create(:container_repository, project: project) }
diff --git a/spec/serializers/container_tag_entity_spec.rb b/spec/serializers/container_tag_entity_spec.rb
index 8440e56f08f..8e47a6269bc 100644
--- a/spec/serializers/container_tag_entity_spec.rb
+++ b/spec/serializers/container_tag_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerTagEntity do
+RSpec.describe ContainerTagEntity do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:repository) { create(:container_repository, name: 'image', project: project) }
diff --git a/spec/serializers/deploy_key_entity_spec.rb b/spec/serializers/deploy_key_entity_spec.rb
index 0dbbf0de59b..3404d27a23c 100644
--- a/spec/serializers/deploy_key_entity_spec.rb
+++ b/spec/serializers/deploy_key_entity_spec.rb
@@ -2,15 +2,16 @@
require 'spec_helper'
-describe DeployKeyEntity do
+RSpec.describe DeployKeyEntity do
include RequestAwareEntity
let(:user) { create(:user) }
let(:project) { create(:project, :internal)}
let(:project_private) { create(:project, :private)}
let(:deploy_key) { create(:deploy_key) }
+ let(:options) { { user: user } }
- let(:entity) { described_class.new(deploy_key, user: user) }
+ let(:entity) { described_class.new(deploy_key, options) }
before do
project.deploy_keys << deploy_key
@@ -74,4 +75,42 @@ describe DeployKeyEntity do
it { expect(entity_public.as_json).to include(can_edit: true) }
end
end
+
+ describe 'with_owner option' do
+ it 'does not return an owner payload when it is set to false' do
+ options[:with_owner] = false
+
+ payload = entity.as_json
+
+ expect(payload[:owner]).not_to be_present
+ end
+
+ describe 'when with_owner is set to true' do
+ before do
+ options[:with_owner] = true
+ end
+
+ it 'returns an owner payload' do
+ payload = entity.as_json
+
+ expect(payload[:owner]).to be_present
+ expect(payload[:owner].keys).to include(:id, :name, :username, :avatar_url)
+ end
+
+ it 'does not return an owner if current_user cannot read the owner' do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?).with(options[:user], :read_user, deploy_key.user).and_return(false)
+
+ payload = entity.as_json
+
+ expect(payload[:owner]).to be_nil
+ end
+ end
+ end
+
+ it 'does not return an owner payload with_owner option not passed in' do
+ payload = entity.as_json
+
+ expect(payload[:owner]).not_to be_present
+ end
end
diff --git a/spec/serializers/deployment_cluster_entity_spec.rb b/spec/serializers/deployment_cluster_entity_spec.rb
index b22a93fcec7..95f2f8ce6fc 100644
--- a/spec/serializers/deployment_cluster_entity_spec.rb
+++ b/spec/serializers/deployment_cluster_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeploymentClusterEntity do
+RSpec.describe DeploymentClusterEntity do
describe '#as_json' do
subject { described_class.new(deployment, request: request).as_json }
diff --git a/spec/serializers/deployment_entity_spec.rb b/spec/serializers/deployment_entity_spec.rb
index 7abe74fae8f..27673b905d3 100644
--- a/spec/serializers/deployment_entity_spec.rb
+++ b/spec/serializers/deployment_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeploymentEntity do
+RSpec.describe DeploymentEntity do
let(:user) { developer }
let(:developer) { create(:user) }
let(:reporter) { create(:user) }
diff --git a/spec/serializers/deployment_serializer_spec.rb b/spec/serializers/deployment_serializer_spec.rb
index 67fccaa3f25..cfd43227b18 100644
--- a/spec/serializers/deployment_serializer_spec.rb
+++ b/spec/serializers/deployment_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeploymentSerializer do
+RSpec.describe DeploymentSerializer do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user, email: project.commit.author_email) }
let(:resource) { create(:deployment, project: project, sha: project.commit.id) }
diff --git a/spec/serializers/detailed_status_entity_spec.rb b/spec/serializers/detailed_status_entity_spec.rb
index a4b51f1e02e..33ecb4572a8 100644
--- a/spec/serializers/detailed_status_entity_spec.rb
+++ b/spec/serializers/detailed_status_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DetailedStatusEntity do
+RSpec.describe DetailedStatusEntity do
let(:entity) { described_class.new(status) }
let(:status) do
diff --git a/spec/serializers/diff_file_base_entity_spec.rb b/spec/serializers/diff_file_base_entity_spec.rb
index 1fd697970de..153f854aa58 100644
--- a/spec/serializers/diff_file_base_entity_spec.rb
+++ b/spec/serializers/diff_file_base_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffFileBaseEntity do
+RSpec.describe DiffFileBaseEntity do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:entity) { described_class.new(diff_file, options).as_json }
@@ -40,10 +40,6 @@ describe DiffFileBaseEntity do
let(:options) { { request: EntityRequest.new(current_user: create(:user)), merge_request: merge_request } }
let(:params) { {} }
- before do
- stub_feature_flags(web_ide_default: false)
- end
-
shared_examples 'a diff file edit path to the source branch' do
it do
expect(entity[:edit_path]).to eq(Gitlab::Routing.url_helpers.project_edit_blob_path(project, File.join(merge_request.source_branch, diff_file.new_path), params))
diff --git a/spec/serializers/diff_file_entity_spec.rb b/spec/serializers/diff_file_entity_spec.rb
index e3ecd72b275..bebe2e2dfb5 100644
--- a/spec/serializers/diff_file_entity_spec.rb
+++ b/spec/serializers/diff_file_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffFileEntity do
+RSpec.describe DiffFileEntity do
include RepoHelpers
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/serializers/diff_line_entity_spec.rb b/spec/serializers/diff_line_entity_spec.rb
index 2549f64bcd3..862b06c800a 100644
--- a/spec/serializers/diff_line_entity_spec.rb
+++ b/spec/serializers/diff_line_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffLineEntity do
+RSpec.describe DiffLineEntity do
include RepoHelpers
let(:code) { 'hello world' }
diff --git a/spec/serializers/diff_line_serializer_spec.rb b/spec/serializers/diff_line_serializer_spec.rb
index bdfcb8e2459..b91f15211de 100644
--- a/spec/serializers/diff_line_serializer_spec.rb
+++ b/spec/serializers/diff_line_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffLineSerializer do
+RSpec.describe DiffLineSerializer do
let(:line) { Gitlab::Diff::Line.new('hello world', 'new', 1, nil, 1) }
let(:serializer) { described_class.new.represent(line) }
diff --git a/spec/serializers/diff_viewer_entity_spec.rb b/spec/serializers/diff_viewer_entity_spec.rb
index 76d2728c597..53601fcff61 100644
--- a/spec/serializers/diff_viewer_entity_spec.rb
+++ b/spec/serializers/diff_viewer_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffViewerEntity do
+RSpec.describe DiffViewerEntity do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/serializers/diffs_entity_spec.rb b/spec/serializers/diffs_entity_spec.rb
index 435d8a6aff2..7c59e4aed83 100644
--- a/spec/serializers/diffs_entity_spec.rb
+++ b/spec/serializers/diffs_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffsEntity do
+RSpec.describe DiffsEntity do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:request) { EntityRequest.new(project: project, current_user: user) }
diff --git a/spec/serializers/diffs_metadata_entity_spec.rb b/spec/serializers/diffs_metadata_entity_spec.rb
index 3ed2b7c9452..8ed47569b75 100644
--- a/spec/serializers/diffs_metadata_entity_spec.rb
+++ b/spec/serializers/diffs_metadata_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiffsMetadataEntity do
+RSpec.describe DiffsMetadataEntity do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:request) { EntityRequest.new(project: project, current_user: user) }
diff --git a/spec/serializers/discussion_diff_file_entity_spec.rb b/spec/serializers/discussion_diff_file_entity_spec.rb
index 101ac918a98..05438450d78 100644
--- a/spec/serializers/discussion_diff_file_entity_spec.rb
+++ b/spec/serializers/discussion_diff_file_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiscussionDiffFileEntity do
+RSpec.describe DiscussionDiffFileEntity do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/serializers/discussion_entity_spec.rb b/spec/serializers/discussion_entity_spec.rb
index b441fd08b98..306a4fa43a9 100644
--- a/spec/serializers/discussion_entity_spec.rb
+++ b/spec/serializers/discussion_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DiscussionEntity do
+RSpec.describe DiscussionEntity do
include RepoHelpers
let(:user) { create(:user) }
diff --git a/spec/serializers/entity_date_helper_spec.rb b/spec/serializers/entity_date_helper_spec.rb
index 6f99074c3a2..a8c338675e2 100644
--- a/spec/serializers/entity_date_helper_spec.rb
+++ b/spec/serializers/entity_date_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EntityDateHelper do
+RSpec.describe EntityDateHelper do
let(:date_helper_class) { Class.new { include EntityDateHelper }.new }
it 'converts 0 seconds' do
diff --git a/spec/serializers/entity_request_spec.rb b/spec/serializers/entity_request_spec.rb
index 947c4b165d3..43a53b57c97 100644
--- a/spec/serializers/entity_request_spec.rb
+++ b/spec/serializers/entity_request_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EntityRequest do
+RSpec.describe EntityRequest do
subject do
described_class.new(user: 'user', project: 'some project')
end
diff --git a/spec/serializers/environment_entity_spec.rb b/spec/serializers/environment_entity_spec.rb
index 36e971c467a..6232a0d2973 100644
--- a/spec/serializers/environment_entity_spec.rb
+++ b/spec/serializers/environment_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EnvironmentEntity do
+RSpec.describe EnvironmentEntity do
include Gitlab::Routing.url_helpers
let(:request) { double('request') }
diff --git a/spec/serializers/environment_serializer_spec.rb b/spec/serializers/environment_serializer_spec.rb
index 304457d83a1..1e3980b7720 100644
--- a/spec/serializers/environment_serializer_spec.rb
+++ b/spec/serializers/environment_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EnvironmentSerializer do
+RSpec.describe EnvironmentSerializer do
let(:user) { create(:user) }
let(:project) { create(:project) }
diff --git a/spec/serializers/environment_status_entity_spec.rb b/spec/serializers/environment_status_entity_spec.rb
index 11455c57677..a940c4b465e 100644
--- a/spec/serializers/environment_status_entity_spec.rb
+++ b/spec/serializers/environment_status_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EnvironmentStatusEntity do
+RSpec.describe EnvironmentStatusEntity do
let(:user) { create(:user) }
let(:request) { double('request', project: project) }
diff --git a/spec/serializers/evidences/evidence_entity_spec.rb b/spec/serializers/evidences/evidence_entity_spec.rb
index fa13bd21edd..8ec0422fea2 100644
--- a/spec/serializers/evidences/evidence_entity_spec.rb
+++ b/spec/serializers/evidences/evidence_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Evidences::EvidenceEntity do
+RSpec.describe Evidences::EvidenceEntity do
let_it_be(:project) { create(:project) }
let(:release) { create(:release, project: project) }
let(:evidence) { build(:evidence, release: release) }
diff --git a/spec/serializers/evidences/evidence_serializer_spec.rb b/spec/serializers/evidences/evidence_serializer_spec.rb
index 5322f6a43fc..73d0700b7cf 100644
--- a/spec/serializers/evidences/evidence_serializer_spec.rb
+++ b/spec/serializers/evidences/evidence_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Evidences::EvidenceSerializer do
+RSpec.describe Evidences::EvidenceSerializer do
it 'represents an EvidenceEntity entity' do
expect(described_class.entity_class).to eq(Evidences::EvidenceEntity)
end
diff --git a/spec/serializers/evidences/issue_entity_spec.rb b/spec/serializers/evidences/issue_entity_spec.rb
index 915df986887..71392d0abd9 100644
--- a/spec/serializers/evidences/issue_entity_spec.rb
+++ b/spec/serializers/evidences/issue_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Evidences::IssueEntity do
+RSpec.describe Evidences::IssueEntity do
let(:entity) { described_class.new(build(:issue)) }
subject { entity.as_json }
diff --git a/spec/serializers/evidences/milestone_entity_spec.rb b/spec/serializers/evidences/milestone_entity_spec.rb
index 68eb12093da..337ce966763 100644
--- a/spec/serializers/evidences/milestone_entity_spec.rb
+++ b/spec/serializers/evidences/milestone_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Evidences::MilestoneEntity do
+RSpec.describe Evidences::MilestoneEntity do
let(:milestone) { build(:milestone) }
let(:entity) { described_class.new(milestone) }
diff --git a/spec/serializers/evidences/project_entity_spec.rb b/spec/serializers/evidences/project_entity_spec.rb
index 01c160425a8..d5cb1a53a7e 100644
--- a/spec/serializers/evidences/project_entity_spec.rb
+++ b/spec/serializers/evidences/project_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Evidences::ProjectEntity do
+RSpec.describe Evidences::ProjectEntity do
let(:entity) { described_class.new(build(:project)) }
subject { entity.as_json }
diff --git a/spec/serializers/evidences/release_entity_spec.rb b/spec/serializers/evidences/release_entity_spec.rb
index 8e2be748169..63e25efc65f 100644
--- a/spec/serializers/evidences/release_entity_spec.rb
+++ b/spec/serializers/evidences/release_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Evidences::ReleaseEntity do
+RSpec.describe Evidences::ReleaseEntity do
let(:release) { build(:release) }
let(:entity) { described_class.new(release) }
diff --git a/spec/serializers/evidences/release_serializer_spec.rb b/spec/serializers/evidences/release_serializer_spec.rb
index a0dbf50137c..c93e0cd7c19 100644
--- a/spec/serializers/evidences/release_serializer_spec.rb
+++ b/spec/serializers/evidences/release_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Evidences::ReleaseSerializer do
+RSpec.describe Evidences::ReleaseSerializer do
it 'represents an Evidence::ReleaseEntity entity' do
expect(described_class.entity_class).to eq(Evidences::ReleaseEntity)
end
diff --git a/spec/serializers/fork_namespace_entity_spec.rb b/spec/serializers/fork_namespace_entity_spec.rb
new file mode 100644
index 00000000000..7ce6b77da44
--- /dev/null
+++ b/spec/serializers/fork_namespace_entity_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ForkNamespaceEntity do
+ include Gitlab::Routing.url_helpers
+ include ProjectForksHelper
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+
+ let(:namespace) { create(:group, :with_avatar, description: 'test') }
+ let(:entity) { described_class.new(namespace, current_user: user, project: project) }
+
+ subject(:json) { entity.as_json }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ it 'renders json' do
+ is_expected.not_to be_nil
+ end
+
+ %w[id
+ name
+ description
+ markdown_description
+ visibility
+ full_name
+ created_at
+ updated_at
+ avatar_url].each do |attribute|
+ it "includes #{attribute}" do
+ expect(json[attribute.to_sym]).to be_present
+ end
+ end
+
+ it 'exposes path for forking project to the namespace' do
+ expect(json[:fork_path]).to eq project_forks_path(project, namespace_key: namespace.id)
+ end
+
+ it 'exposes forked_project_path when fork exists in namespace' do
+ namespace.add_maintainer(user)
+ fork_in_namespace = fork_project(project, user, namespace: namespace)
+
+ expect(json[:forked_project_path]).to eql project_path(fork_in_namespace)
+ end
+
+ it 'exposes relative path to the namespace' do
+ expect(json[:relative_path]).to eql polymorphic_path(namespace)
+ end
+
+ it 'exposes human readable permission level' do
+ namespace.add_developer(user)
+ expect(json[:permission]).to eql 'Developer'
+ end
+
+ it 'sets can_create_project to true when user can create projects in namespace' do
+ allow(user).to receive(:can?).with(:create_projects, namespace).and_return(true)
+
+ expect(json[:can_create_project]).to be true
+ end
+
+ it 'sets can_create_project to false when user is not allowed create projects in namespace' do
+ allow(user).to receive(:can?).with(:create_projects, namespace).and_return(false)
+
+ expect(json[:can_create_project]).to be false
+ end
+end
diff --git a/spec/serializers/fork_namespace_serializer_spec.rb b/spec/serializers/fork_namespace_serializer_spec.rb
new file mode 100644
index 00000000000..6793b900af6
--- /dev/null
+++ b/spec/serializers/fork_namespace_serializer_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ForkNamespaceSerializer do
+ it 'represents ForkNamespaceEntity entities' do
+ expect(described_class.entity_class).to eq(ForkNamespaceEntity)
+ end
+end
diff --git a/spec/serializers/group_child_entity_spec.rb b/spec/serializers/group_child_entity_spec.rb
index cae263e7fd5..7f330da44a7 100644
--- a/spec/serializers/group_child_entity_spec.rb
+++ b/spec/serializers/group_child_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupChildEntity do
+RSpec.describe GroupChildEntity do
include ExternalAuthorizationServiceHelpers
include Gitlab::Routing.url_helpers
diff --git a/spec/serializers/group_child_serializer_spec.rb b/spec/serializers/group_child_serializer_spec.rb
index 92f083ed23e..63582019d32 100644
--- a/spec/serializers/group_child_serializer_spec.rb
+++ b/spec/serializers/group_child_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupChildSerializer do
+RSpec.describe GroupChildSerializer do
let(:request) { double('request') }
let(:user) { create(:user) }
diff --git a/spec/serializers/import/bitbucket_provider_repo_entity_spec.rb b/spec/serializers/import/bitbucket_provider_repo_entity_spec.rb
index ed3ef26db65..2a0386b779f 100644
--- a/spec/serializers/import/bitbucket_provider_repo_entity_spec.rb
+++ b/spec/serializers/import/bitbucket_provider_repo_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Import::BitbucketProviderRepoEntity do
+RSpec.describe Import::BitbucketProviderRepoEntity do
let(:repo_data) do
{
'name' => 'repo_name',
diff --git a/spec/serializers/import/bitbucket_server_provider_repo_entity_spec.rb b/spec/serializers/import/bitbucket_server_provider_repo_entity_spec.rb
index 9891809cc67..6e00d608d9a 100644
--- a/spec/serializers/import/bitbucket_server_provider_repo_entity_spec.rb
+++ b/spec/serializers/import/bitbucket_server_provider_repo_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Import::BitbucketServerProviderRepoEntity do
+RSpec.describe Import::BitbucketServerProviderRepoEntity do
let(:repo_data) do
{
'name' => 'test',
diff --git a/spec/serializers/import/fogbugz_provider_repo_entity_spec.rb b/spec/serializers/import/fogbugz_provider_repo_entity_spec.rb
index b9029b67aab..748ddd2a108 100644
--- a/spec/serializers/import/fogbugz_provider_repo_entity_spec.rb
+++ b/spec/serializers/import/fogbugz_provider_repo_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Import::FogbugzProviderRepoEntity do
+RSpec.describe Import::FogbugzProviderRepoEntity do
let(:provider_url) { 'https://demo.fogbugz.com/' }
let(:repo_data) do
{
diff --git a/spec/serializers/import/githubish_provider_repo_entity_spec.rb b/spec/serializers/import/githubish_provider_repo_entity_spec.rb
index c6a07b2d64a..b8292f71714 100644
--- a/spec/serializers/import/githubish_provider_repo_entity_spec.rb
+++ b/spec/serializers/import/githubish_provider_repo_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Import::GithubishProviderRepoEntity do
+RSpec.describe Import::GithubishProviderRepoEntity do
let(:provider_url) { 'https://github.com/' }
let(:repo) do
{
diff --git a/spec/serializers/import/gitlab_provider_repo_entity_spec.rb b/spec/serializers/import/gitlab_provider_repo_entity_spec.rb
index 3f862c16fe2..25e445d336c 100644
--- a/spec/serializers/import/gitlab_provider_repo_entity_spec.rb
+++ b/spec/serializers/import/gitlab_provider_repo_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Import::GitlabProviderRepoEntity do
+RSpec.describe Import::GitlabProviderRepoEntity do
let(:repo_data) do
{
'id' => 1,
diff --git a/spec/serializers/import/provider_repo_serializer_spec.rb b/spec/serializers/import/provider_repo_serializer_spec.rb
index 9bf55e6c65d..430bad151d3 100644
--- a/spec/serializers/import/provider_repo_serializer_spec.rb
+++ b/spec/serializers/import/provider_repo_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Import::ProviderRepoSerializer do
+RSpec.describe Import::ProviderRepoSerializer do
using RSpec::Parameterized::TableSyntax
describe '#represent' do
diff --git a/spec/serializers/issuable_sidebar_extras_entity_spec.rb b/spec/serializers/issuable_sidebar_extras_entity_spec.rb
index a1a7c554b49..f49b9acfd5d 100644
--- a/spec/serializers/issuable_sidebar_extras_entity_spec.rb
+++ b/spec/serializers/issuable_sidebar_extras_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssuableSidebarExtrasEntity do
+RSpec.describe IssuableSidebarExtrasEntity do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:resource) { create(:issue, project: project) }
diff --git a/spec/serializers/issue_board_entity_spec.rb b/spec/serializers/issue_board_entity_spec.rb
index d013b27369b..e60a063b9eb 100644
--- a/spec/serializers/issue_board_entity_spec.rb
+++ b/spec/serializers/issue_board_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssueBoardEntity do
+RSpec.describe IssueBoardEntity do
let_it_be(:project) { create(:project) }
let_it_be(:resource) { create(:issue, project: project) }
let_it_be(:user) { create(:user) }
diff --git a/spec/serializers/issue_entity_spec.rb b/spec/serializers/issue_entity_spec.rb
index a1868b2631b..5c5ac184778 100644
--- a/spec/serializers/issue_entity_spec.rb
+++ b/spec/serializers/issue_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssueEntity do
+RSpec.describe IssueEntity do
let(:project) { create(:project) }
let(:resource) { create(:issue, project: project) }
let(:user) { create(:user) }
diff --git a/spec/serializers/issue_serializer_spec.rb b/spec/serializers/issue_serializer_spec.rb
index e25becc4709..a51297d6d80 100644
--- a/spec/serializers/issue_serializer_spec.rb
+++ b/spec/serializers/issue_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssueSerializer do
+RSpec.describe IssueSerializer do
let(:resource) { create(:issue) }
let(:user) { create(:user) }
let(:json_entity) do
diff --git a/spec/serializers/job_artifact_report_entity_spec.rb b/spec/serializers/job_artifact_report_entity_spec.rb
index 3cd12f0e9fe..1aaaad544ba 100644
--- a/spec/serializers/job_artifact_report_entity_spec.rb
+++ b/spec/serializers/job_artifact_report_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe JobArtifactReportEntity do
+RSpec.describe JobArtifactReportEntity do
let(:report) { create(:ci_job_artifact, :codequality) }
let(:entity) { described_class.new(report, request: double) }
diff --git a/spec/serializers/job_entity_spec.rb b/spec/serializers/job_entity_spec.rb
index 60843f1a599..02262be9511 100644
--- a/spec/serializers/job_entity_spec.rb
+++ b/spec/serializers/job_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe JobEntity do
+RSpec.describe JobEntity do
let(:user) { create(:user) }
let(:job) { create(:ci_build) }
let(:project) { job.project }
diff --git a/spec/serializers/label_serializer_spec.rb b/spec/serializers/label_serializer_spec.rb
index 7e1bb2cc127..ae1466b16e5 100644
--- a/spec/serializers/label_serializer_spec.rb
+++ b/spec/serializers/label_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LabelSerializer do
+RSpec.describe LabelSerializer do
let(:user) { create(:user) }
let(:serializer) do
diff --git a/spec/serializers/lfs_file_lock_entity_spec.rb b/spec/serializers/lfs_file_lock_entity_spec.rb
index 4ffffad7d5a..5869941c920 100644
--- a/spec/serializers/lfs_file_lock_entity_spec.rb
+++ b/spec/serializers/lfs_file_lock_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LfsFileLockEntity do
+RSpec.describe LfsFileLockEntity do
let(:user) { create(:user) }
let(:resource) { create(:lfs_file_lock, user: user) }
diff --git a/spec/serializers/merge_request_basic_entity_spec.rb b/spec/serializers/merge_request_basic_entity_spec.rb
index 53ba66a79ac..1cddd87e917 100644
--- a/spec/serializers/merge_request_basic_entity_spec.rb
+++ b/spec/serializers/merge_request_basic_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestBasicEntity do
+RSpec.describe MergeRequestBasicEntity do
let(:resource) { build(:merge_request) }
subject do
diff --git a/spec/serializers/merge_request_diff_entity_spec.rb b/spec/serializers/merge_request_diff_entity_spec.rb
index 2e3b0d092fe..542ef6cb3c3 100644
--- a/spec/serializers/merge_request_diff_entity_spec.rb
+++ b/spec/serializers/merge_request_diff_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestDiffEntity do
+RSpec.describe MergeRequestDiffEntity do
let_it_be(:project) { create(:project, :repository) }
let(:request) { EntityRequest.new(project: project) }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
diff --git a/spec/serializers/merge_request_for_pipeline_entity_spec.rb b/spec/serializers/merge_request_for_pipeline_entity_spec.rb
index b39fbce8c3a..ed66716974a 100644
--- a/spec/serializers/merge_request_for_pipeline_entity_spec.rb
+++ b/spec/serializers/merge_request_for_pipeline_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestForPipelineEntity do
+RSpec.describe MergeRequestForPipelineEntity do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:request) { EntityRequest.new(project: project) }
diff --git a/spec/serializers/merge_request_poll_cached_widget_entity_spec.rb b/spec/serializers/merge_request_poll_cached_widget_entity_spec.rb
index 9f96e5711a4..51564de6041 100644
--- a/spec/serializers/merge_request_poll_cached_widget_entity_spec.rb
+++ b/spec/serializers/merge_request_poll_cached_widget_entity_spec.rb
@@ -2,8 +2,9 @@
require 'spec_helper'
-describe MergeRequestPollCachedWidgetEntity do
+RSpec.describe MergeRequestPollCachedWidgetEntity do
include ProjectForksHelper
+ using RSpec::Parameterized::TableSyntax
let(:project) { create :project, :repository }
let(:resource) { create(:merge_request, source_project: project, target_project: project) }
@@ -181,6 +182,27 @@ describe MergeRequestPollCachedWidgetEntity do
end
end
+ describe 'squash defaults for projects' do
+ where(:squash_option, :value, :default, :readonly) do
+ 'always' | true | true | true
+ 'never' | false | false | true
+ 'default_on' | false | true | false
+ 'default_off' | false | false | false
+ end
+
+ with_them do
+ before do
+ project.project_setting.update!(squash_option: squash_option)
+ end
+
+ it 'the key reflects the correct value' do
+ expect(subject[:squash_on_merge]).to eq(value)
+ expect(subject[:squash_enabled_by_default]).to eq(default)
+ expect(subject[:squash_readonly]).to eq(readonly)
+ end
+ end
+ end
+
describe 'attributes for squash commit message' do
context 'when merge request is mergeable' do
before do
diff --git a/spec/serializers/merge_request_poll_widget_entity_spec.rb b/spec/serializers/merge_request_poll_widget_entity_spec.rb
index 4b3bfc99c88..f0493699209 100644
--- a/spec/serializers/merge_request_poll_widget_entity_spec.rb
+++ b/spec/serializers/merge_request_poll_widget_entity_spec.rb
@@ -2,8 +2,9 @@
require 'spec_helper'
-describe MergeRequestPollWidgetEntity do
+RSpec.describe MergeRequestPollWidgetEntity do
include ProjectForksHelper
+ using RSpec::Parameterized::TableSyntax
let(:project) { create :project, :repository }
let(:resource) { create(:merge_request, source_project: project, target_project: project) }
@@ -171,6 +172,27 @@ describe MergeRequestPollWidgetEntity do
end
end
+ describe 'squash defaults for projects' do
+ where(:squash_option, :value, :default, :readonly) do
+ 'always' | true | true | true
+ 'never' | false | false | true
+ 'default_on' | false | true | false
+ 'default_off' | false | false | false
+ end
+
+ with_them do
+ before do
+ project.project_setting.update!(squash_option: squash_option)
+ end
+
+ it 'the key reflects the correct value' do
+ expect(subject[:squash_on_merge]).to eq(value)
+ expect(subject[:squash_enabled_by_default]).to eq(default)
+ expect(subject[:squash_readonly]).to eq(readonly)
+ end
+ end
+ end
+
context 'when head pipeline is finished' do
before do
create(:ci_pipeline, :success, project: project,
diff --git a/spec/serializers/merge_request_serializer_spec.rb b/spec/serializers/merge_request_serializer_spec.rb
index 90b3efae412..a2ce1ed4ac2 100644
--- a/spec/serializers/merge_request_serializer_spec.rb
+++ b/spec/serializers/merge_request_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestSerializer do
+RSpec.describe MergeRequestSerializer do
let_it_be(:user) { create(:user) }
let_it_be(:resource) { create(:merge_request, description: "Description") }
diff --git a/spec/serializers/merge_request_sidebar_basic_entity_spec.rb b/spec/serializers/merge_request_sidebar_basic_entity_spec.rb
index b2db57801ea..e950e6d6592 100644
--- a/spec/serializers/merge_request_sidebar_basic_entity_spec.rb
+++ b/spec/serializers/merge_request_sidebar_basic_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestSidebarBasicEntity do
+RSpec.describe MergeRequestSidebarBasicEntity do
let(:project) { create :project, :repository }
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:user) { create(:user) }
diff --git a/spec/serializers/merge_request_user_entity_spec.rb b/spec/serializers/merge_request_user_entity_spec.rb
index 47b9b0a57ab..8d6f066481e 100644
--- a/spec/serializers/merge_request_user_entity_spec.rb
+++ b/spec/serializers/merge_request_user_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestUserEntity do
+RSpec.describe MergeRequestUserEntity do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:request) { EntityRequest.new(project: project, current_user: user) }
diff --git a/spec/serializers/merge_request_widget_commit_entity_spec.rb b/spec/serializers/merge_request_widget_commit_entity_spec.rb
index ce83978c49a..0d13308f27d 100644
--- a/spec/serializers/merge_request_widget_commit_entity_spec.rb
+++ b/spec/serializers/merge_request_widget_commit_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestWidgetCommitEntity do
+RSpec.describe MergeRequestWidgetCommitEntity do
let(:project) { create(:project, :repository) }
let(:commit) { project.commit }
let(:request) { double('request') }
diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb
index 039fb311bfc..aaee47fb981 100644
--- a/spec/serializers/merge_request_widget_entity_spec.rb
+++ b/spec/serializers/merge_request_widget_entity_spec.rb
@@ -2,11 +2,12 @@
require 'spec_helper'
-describe MergeRequestWidgetEntity do
+RSpec.describe MergeRequestWidgetEntity do
include ProjectForksHelper
let(:project) { create :project, :repository }
let(:resource) { create(:merge_request, source_project: project, target_project: project) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:user) { create(:user) }
let(:request) { double('request', current_user: user, project: project) }
@@ -53,6 +54,42 @@ describe MergeRequestWidgetEntity do
.to eq("/#{resource.project.full_path}/-/merge_requests/#{resource.iid}.diff")
end
+ it 'has blob path data' do
+ allow(resource).to receive_messages(
+ base_pipeline: pipeline,
+ head_pipeline: pipeline
+ )
+
+ expect(subject).to include(:blob_path)
+ expect(subject[:blob_path]).to include(:base_path)
+ expect(subject[:blob_path]).to include(:head_path)
+ end
+
+ describe 'codequality report artifacts', :request_store do
+ before do
+ project.add_developer(user)
+
+ allow(resource).to receive_messages(
+ base_pipeline: pipeline,
+ head_pipeline: pipeline
+ )
+ end
+
+ context "with report artifacts" do
+ let(:pipeline) { create(:ci_pipeline, :with_codequality_report, project: project) }
+
+ it "has data entry" do
+ expect(subject).to include(:codeclimate)
+ end
+ end
+
+ context "without artifacts" do
+ it "does not have data entry" do
+ expect(subject).not_to include(:codeclimate)
+ end
+ end
+ end
+
describe 'merge_request_add_ci_config_path' do
let!(:project_auto_devops) { create(:project_auto_devops, :disabled, project: project) }
@@ -155,6 +192,36 @@ describe MergeRequestWidgetEntity do
expect(subject[:merge_request_add_ci_config_path]).to be_nil
end
end
+
+ context 'when merge request is merged' do
+ before do
+ resource.mark_as_merged!
+ end
+
+ it 'returns a blank ci config path' do
+ expect(subject[:merge_request_add_ci_config_path]).to be_nil
+ end
+ end
+
+ context 'when merge request is closed' do
+ before do
+ resource.close!
+ end
+
+ it 'returns a blank ci config path' do
+ expect(subject[:merge_request_add_ci_config_path]).to be_nil
+ end
+ end
+
+ context 'when source branch does not exist' do
+ before do
+ resource.source_project.repository.rm_branch(user, resource.source_branch)
+ end
+
+ it 'returns a blank ci config path' do
+ expect(subject[:merge_request_add_ci_config_path]).to be_nil
+ end
+ end
end
context 'when user does not have permissions' do
diff --git a/spec/serializers/move_to_project_entity_spec.rb b/spec/serializers/move_to_project_entity_spec.rb
index ac495eadb68..a14bc3ae622 100644
--- a/spec/serializers/move_to_project_entity_spec.rb
+++ b/spec/serializers/move_to_project_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MoveToProjectEntity do
+RSpec.describe MoveToProjectEntity do
describe '#as_json' do
let(:project) { build(:project, id: 1) }
diff --git a/spec/serializers/move_to_project_serializer_spec.rb b/spec/serializers/move_to_project_serializer_spec.rb
index 841ac969eeb..60bcca3269f 100644
--- a/spec/serializers/move_to_project_serializer_spec.rb
+++ b/spec/serializers/move_to_project_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MoveToProjectSerializer do
+RSpec.describe MoveToProjectSerializer do
describe '#represent' do
it 'includes the name and name with namespace' do
project = build(:project, id: 1)
diff --git a/spec/serializers/namespace_basic_entity_spec.rb b/spec/serializers/namespace_basic_entity_spec.rb
index d3d666d57aa..8b69af5696a 100644
--- a/spec/serializers/namespace_basic_entity_spec.rb
+++ b/spec/serializers/namespace_basic_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NamespaceBasicEntity do
+RSpec.describe NamespaceBasicEntity do
let_it_be(:group) { create(:group) }
let(:entity) do
described_class.represent(group)
diff --git a/spec/serializers/namespace_serializer_spec.rb b/spec/serializers/namespace_serializer_spec.rb
index 6e5bdd8c52d..0c4b099ef60 100644
--- a/spec/serializers/namespace_serializer_spec.rb
+++ b/spec/serializers/namespace_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NamespaceSerializer do
+RSpec.describe NamespaceSerializer do
it 'represents NamespaceBasicEntity entities' do
expect(described_class.entity_class).to eq(NamespaceBasicEntity)
end
diff --git a/spec/serializers/note_entity_spec.rb b/spec/serializers/note_entity_spec.rb
index f37fffb5048..19438e69a10 100644
--- a/spec/serializers/note_entity_spec.rb
+++ b/spec/serializers/note_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NoteEntity do
+RSpec.describe NoteEntity do
include Gitlab::Routing
let(:request) { double('request', current_user: user, noteable: note.noteable) }
diff --git a/spec/serializers/paginated_diff_entity_spec.rb b/spec/serializers/paginated_diff_entity_spec.rb
index a6b83cb4286..30360b00537 100644
--- a/spec/serializers/paginated_diff_entity_spec.rb
+++ b/spec/serializers/paginated_diff_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PaginatedDiffEntity do
+RSpec.describe PaginatedDiffEntity do
let(:user) { create(:user) }
let(:request) { double('request', current_user: user) }
let(:merge_request) { create(:merge_request, :with_diffs) }
diff --git a/spec/serializers/pipeline_details_entity_spec.rb b/spec/serializers/pipeline_details_entity_spec.rb
index 849dab102c2..35ce7c7175c 100644
--- a/spec/serializers/pipeline_details_entity_spec.rb
+++ b/spec/serializers/pipeline_details_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelineDetailsEntity do
+RSpec.describe PipelineDetailsEntity do
let_it_be(:user) { create(:user) }
let(:request) { double('request') }
@@ -157,20 +157,30 @@ describe PipelineDetailsEntity do
context 'when pipeline triggered other pipeline' do
let(:pipeline) { create(:ci_empty_pipeline) }
- let(:build) { create(:ci_build, pipeline: pipeline) }
+ let(:build) { create(:ci_build, name: 'child', stage: 'test', pipeline: pipeline) }
+ let(:bridge) { create(:ci_bridge, name: 'cross-project', stage: 'build', pipeline: pipeline) }
+ let(:child_pipeline) { create(:ci_pipeline, project: pipeline.project) }
+ let(:cross_project_pipeline) { create(:ci_pipeline) }
before do
- create(:ci_sources_pipeline, source_job: build)
- create(:ci_sources_pipeline, source_job: build)
+ create(:ci_sources_pipeline, source_job: build, pipeline: child_pipeline)
+ create(:ci_sources_pipeline, source_job: bridge, pipeline: cross_project_pipeline)
end
- it 'contains an information about depedent pipeline' do
+ it 'contains an information about dependent pipeline', :aggregate_failures do
expect(subject[:triggered]).to be_a(Array)
expect(subject[:triggered].length).to eq(2)
expect(subject[:triggered].first[:path]).not_to be_nil
expect(subject[:triggered].first[:details]).not_to be_nil
expect(subject[:triggered].first[:details][:status]).not_to be_nil
expect(subject[:triggered].first[:project]).not_to be_nil
+
+ source_jobs = subject[:triggered]
+ .index_by { |pipeline| pipeline[:id] }
+ .transform_values { |pipeline| pipeline.fetch(:source_job) }
+
+ expect(source_jobs[cross_project_pipeline.id][:name]).to eq('cross-project')
+ expect(source_jobs[child_pipeline.id][:name]).to eq('child')
end
end
end
diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb
index 914f42054bd..e638b14765b 100644
--- a/spec/serializers/pipeline_entity_spec.rb
+++ b/spec/serializers/pipeline_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelineEntity do
+RSpec.describe PipelineEntity do
include Gitlab::Routing
let_it_be(:project) { create(:project) }
@@ -261,5 +261,29 @@ describe PipelineEntity do
end
end
end
+
+ context 'when pipeline has build report results' do
+ let(:pipeline) { create(:ci_pipeline, :with_report_results, project: project, user: user) }
+
+ context 'when feature is enabled' do
+ before do
+ stub_feature_flags(build_report_summary: true)
+ end
+
+ it 'exposes tests total count' do
+ expect(subject[:tests_total_count]).to eq(2)
+ end
+ end
+
+ context 'when feature is disabled' do
+ before do
+ stub_feature_flags(build_report_summary: false)
+ end
+
+ it 'do not expose tests total count' do
+ expect(subject).not_to include(:tests_total_count)
+ end
+ end
+ end
end
end
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index c8f25423f85..c1386ac4eb2 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelineSerializer do
+RSpec.describe PipelineSerializer do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
@@ -155,11 +155,25 @@ describe PipelineSerializer do
it 'verifies number of queries', :request_store do
recorded = ActiveRecord::QueryRecorder.new { subject }
- expected_queries = Gitlab.ee? ? 43 : 40
+ expected_queries = Gitlab.ee? ? 46 : 43
expect(recorded.count).to be_within(2).of(expected_queries)
expect(recorded.cached_count).to eq(0)
end
+
+ context 'with the :build_report_summary flag turned off' do
+ before do
+ stub_feature_flags(build_report_summary: false)
+ end
+
+ it 'verifies number of queries', :request_store do
+ recorded = ActiveRecord::QueryRecorder.new { subject }
+ expected_queries = Gitlab.ee? ? 43 : 40
+
+ expect(recorded.count).to be_within(2).of(expected_queries)
+ expect(recorded.cached_count).to eq(0)
+ end
+ end
end
context 'with different refs' do
@@ -176,7 +190,48 @@ describe PipelineSerializer do
# pipeline. With the same ref this check is cached but if refs are
# different then there is an extra query per ref
# https://gitlab.com/gitlab-org/gitlab-foss/issues/46368
- expected_queries = Gitlab.ee? ? 46 : 43
+ expected_queries = Gitlab.ee? ? 49 : 46
+
+ expect(recorded.count).to be_within(2).of(expected_queries)
+ expect(recorded.cached_count).to eq(0)
+ end
+
+ context 'with the :build_report_summary flag turned off' do
+ before do
+ stub_feature_flags(build_report_summary: false)
+ end
+
+ it 'verifies number of queries', :request_store do
+ recorded = ActiveRecord::QueryRecorder.new { subject }
+ expected_queries = Gitlab.ee? ? 46 : 43
+
+ expect(recorded.count).to be_within(2).of(expected_queries)
+ expect(recorded.cached_count).to eq(0)
+ end
+ end
+ end
+
+ context 'with triggered pipelines' do
+ let(:ref) { 'feature' }
+
+ before do
+ pipeline_1 = create(:ci_pipeline)
+ build_1 = create(:ci_build, pipeline: pipeline_1)
+ create(:ci_sources_pipeline, source_job: build_1)
+
+ pipeline_2 = create(:ci_pipeline)
+ build_2 = create(:ci_build, pipeline: pipeline_2)
+ create(:ci_sources_pipeline, source_job: build_2)
+ end
+
+ it 'verifies number of queries', :request_store do
+ recorded = ActiveRecord::QueryRecorder.new { subject }
+
+ # 99 queries by default + 2 related to preloading
+ # :source_pipeline and :source_job
+ # Existing numbers are high and require performance optimization
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/225156
+ expected_queries = Gitlab.ee? ? 101 : 92
expect(recorded.count).to be_within(2).of(expected_queries)
expect(recorded.cached_count).to eq(0)
diff --git a/spec/serializers/project_import_entity_spec.rb b/spec/serializers/project_import_entity_spec.rb
index 9ccae798091..1481d4122ae 100644
--- a/spec/serializers/project_import_entity_spec.rb
+++ b/spec/serializers/project_import_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectImportEntity do
+RSpec.describe ProjectImportEntity do
include ImportHelper
let_it_be(:project) { create(:project, import_status: :started, import_source: 'namespace/project') }
diff --git a/spec/serializers/project_mirror_entity_spec.rb b/spec/serializers/project_mirror_entity_spec.rb
index 0d64199ecf6..7ed530ed9e8 100644
--- a/spec/serializers/project_mirror_entity_spec.rb
+++ b/spec/serializers/project_mirror_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectMirrorEntity do
+RSpec.describe ProjectMirrorEntity do
let(:project) { create(:project, :repository, :remote_mirror) }
let(:entity) { described_class.new(project) }
diff --git a/spec/serializers/project_mirror_serializer_spec.rb b/spec/serializers/project_mirror_serializer_spec.rb
index b50c2267ced..0d83e25e27b 100644
--- a/spec/serializers/project_mirror_serializer_spec.rb
+++ b/spec/serializers/project_mirror_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectMirrorSerializer do
+RSpec.describe ProjectMirrorSerializer do
it 'represents ProjectMirror entities' do
expect(described_class.entity_class).to eq(ProjectMirrorEntity)
end
diff --git a/spec/serializers/project_note_entity_spec.rb b/spec/serializers/project_note_entity_spec.rb
index 469bf2b86de..aaf7764a123 100644
--- a/spec/serializers/project_note_entity_spec.rb
+++ b/spec/serializers/project_note_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectNoteEntity do
+RSpec.describe ProjectNoteEntity do
include Gitlab::Routing
let(:request) { double('request', current_user: user, noteable: note.noteable) }
diff --git a/spec/serializers/project_serializer_spec.rb b/spec/serializers/project_serializer_spec.rb
index ef3dd1dc15e..4bf0657129f 100644
--- a/spec/serializers/project_serializer_spec.rb
+++ b/spec/serializers/project_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectSerializer do
+RSpec.describe ProjectSerializer do
let_it_be(:project) { create(:project) }
let(:provider_url) { 'http://provider.com' }
diff --git a/spec/serializers/prometheus_alert_entity_spec.rb b/spec/serializers/prometheus_alert_entity_spec.rb
index 2b6d8b62c4d..aeee8de2a5b 100644
--- a/spec/serializers/prometheus_alert_entity_spec.rb
+++ b/spec/serializers/prometheus_alert_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PrometheusAlertEntity do
+RSpec.describe PrometheusAlertEntity do
let(:user) { create(:user) }
let(:prometheus_alert) { create(:prometheus_alert) }
let(:request) { double('prometheus_alert', current_user: user) }
diff --git a/spec/serializers/remote_mirror_entity_spec.rb b/spec/serializers/remote_mirror_entity_spec.rb
index 27472c46436..4cbf87e4d67 100644
--- a/spec/serializers/remote_mirror_entity_spec.rb
+++ b/spec/serializers/remote_mirror_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RemoteMirrorEntity do
+RSpec.describe RemoteMirrorEntity do
let(:project) { create(:project, :repository, :remote_mirror, url: "https://test:password@gitlab.com") }
let(:remote_mirror) { project.remote_mirrors.first }
let(:entity) { described_class.new(remote_mirror) }
diff --git a/spec/serializers/request_aware_entity_spec.rb b/spec/serializers/request_aware_entity_spec.rb
index 8ddb35f5f61..d5e87f0a618 100644
--- a/spec/serializers/request_aware_entity_spec.rb
+++ b/spec/serializers/request_aware_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RequestAwareEntity do
+RSpec.describe RequestAwareEntity do
subject do
Class.new.include(described_class).new
end
diff --git a/spec/serializers/review_app_setup_entity_spec.rb b/spec/serializers/review_app_setup_entity_spec.rb
index 19949fa9282..0893d7ee47f 100644
--- a/spec/serializers/review_app_setup_entity_spec.rb
+++ b/spec/serializers/review_app_setup_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ReviewAppSetupEntity do
+RSpec.describe ReviewAppSetupEntity do
let_it_be(:user) { create(:admin) }
let(:project) { create(:project) }
let(:presenter) { ProjectPresenter.new(project, current_user: user) }
diff --git a/spec/serializers/runner_entity_spec.rb b/spec/serializers/runner_entity_spec.rb
index 11a6aba431b..84c7d1720e2 100644
--- a/spec/serializers/runner_entity_spec.rb
+++ b/spec/serializers/runner_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RunnerEntity do
+RSpec.describe RunnerEntity do
let(:project) { create(:project) }
let(:runner) { create(:ci_runner, :project, projects: [project]) }
let(:entity) { described_class.new(runner, request: request, current_user: user) }
diff --git a/spec/serializers/serverless/domain_entity_spec.rb b/spec/serializers/serverless/domain_entity_spec.rb
index bdf0ccb176c..4d73a0f651a 100644
--- a/spec/serializers/serverless/domain_entity_spec.rb
+++ b/spec/serializers/serverless/domain_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Serverless::DomainEntity do
+RSpec.describe Serverless::DomainEntity do
describe '#as_json' do
let(:domain) { create(:pages_domain, :instance_serverless) }
diff --git a/spec/serializers/service_event_entity_spec.rb b/spec/serializers/service_event_entity_spec.rb
index fc11263807b..09bb8bca43b 100644
--- a/spec/serializers/service_event_entity_spec.rb
+++ b/spec/serializers/service_event_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ServiceEventEntity do
+RSpec.describe ServiceEventEntity do
let(:request) { double('request') }
subject { described_class.new(event, request: request, service: service).as_json }
diff --git a/spec/serializers/service_field_entity_spec.rb b/spec/serializers/service_field_entity_spec.rb
index 277890d143a..f10639dfa1b 100644
--- a/spec/serializers/service_field_entity_spec.rb
+++ b/spec/serializers/service_field_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ServiceFieldEntity do
+RSpec.describe ServiceFieldEntity do
let(:request) { double('request') }
subject { described_class.new(field, request: request, service: service).as_json }
@@ -55,12 +55,12 @@ describe ServiceFieldEntity do
end
context 'EmailsOnPush Service' do
- let(:service) { create(:emails_on_push_service) }
+ let(:service) { create(:emails_on_push_service, send_from_committer_email: '1') }
context 'field with type checkbox' do
let(:field) { service.global_fields.find { |field| field[:name] == 'send_from_committer_email' } }
- it 'exposes correct attributes' do
+ it 'exposes correct attributes and casts value to Boolean' do
expected_hash = {
type: 'checkbox',
name: 'send_from_committer_email',
@@ -68,7 +68,7 @@ describe ServiceFieldEntity do
placeholder: nil,
required: nil,
choices: nil,
- value: true
+ value: 'true'
}
is_expected.to include(expected_hash)
diff --git a/spec/serializers/stage_entity_spec.rb b/spec/serializers/stage_entity_spec.rb
index fb08d6994ae..b977d5d33aa 100644
--- a/spec/serializers/stage_entity_spec.rb
+++ b/spec/serializers/stage_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe StageEntity do
+RSpec.describe StageEntity do
let(:pipeline) { create(:ci_pipeline) }
let(:request) { double('request') }
let(:user) { create(:user) }
diff --git a/spec/serializers/stage_serializer_spec.rb b/spec/serializers/stage_serializer_spec.rb
index aae17cfbcb9..0b5e87dc95b 100644
--- a/spec/serializers/stage_serializer_spec.rb
+++ b/spec/serializers/stage_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe StageSerializer do
+RSpec.describe StageSerializer do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:resource) { create(:ci_stage_entity) }
diff --git a/spec/serializers/suggestion_entity_spec.rb b/spec/serializers/suggestion_entity_spec.rb
index d282a7f9c7a..b133c3fb82e 100644
--- a/spec/serializers/suggestion_entity_spec.rb
+++ b/spec/serializers/suggestion_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SuggestionEntity do
+RSpec.describe SuggestionEntity do
include RepoHelpers
let(:user) { create(:user) }
@@ -13,10 +13,119 @@ describe SuggestionEntity do
subject { entity.as_json }
it 'exposes correct attributes' do
- expect(subject.keys).to match_array([:id, :appliable, :applied, :diff_lines, :current_user])
+ expect(subject.keys).to match_array([:id, :appliable, :applied, :diff_lines, :current_user, :inapplicable_reason])
end
it 'exposes current user abilities' do
expect(subject[:current_user]).to include(:can_apply)
end
+
+ describe 'inapplicable_reason' do
+ let(:inapplicable_reason) { subject[:inapplicable_reason] }
+
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+
+ allow(Ability)
+ .to receive(:allowed?)
+ .with(user, :apply_suggestion, suggestion)
+ .and_return(can_apply_suggestion)
+ end
+
+ context 'when user can apply suggestion' do
+ let(:can_apply_suggestion) { true }
+
+ before do
+ allow(suggestion).to receive(:appliable?).and_return(appliable)
+ end
+
+ context 'and suggestion is appliable' do
+ let(:appliable) { true }
+
+ it 'returns nil' do
+ expect(inapplicable_reason).to be_nil
+ end
+ end
+
+ context 'but suggestion is not applicable' do
+ let(:appliable) { false }
+
+ before do
+ allow(suggestion).to receive(:inapplicable_reason).and_return(reason)
+ end
+
+ context 'and merge request was merged' do
+ let(:reason) { :merge_request_merged }
+
+ it 'returns appropriate message' do
+ expect(inapplicable_reason).to eq("This merge request was merged. To apply this suggestion, edit this file directly.")
+ end
+ end
+
+ context 'and source branch was deleted' do
+ let(:reason) { :source_branch_deleted }
+
+ it 'returns appropriate message' do
+ expect(inapplicable_reason).to eq("Can't apply as the source branch was deleted.")
+ end
+ end
+
+ context 'and merge request is closed' do
+ let(:reason) { :merge_request_closed }
+
+ it 'returns appropriate message' do
+ expect(inapplicable_reason).to eq("This merge request is closed. To apply this suggestion, edit this file directly.")
+ end
+ end
+
+ context 'and suggestion is outdated' do
+ let(:reason) { :outdated }
+
+ before do
+ allow(suggestion).to receive(:single_line?).and_return(single_line)
+ end
+
+ context 'and suggestion is for a single line' do
+ let(:single_line) { true }
+
+ it 'returns appropriate message' do
+ expect(inapplicable_reason).to eq("Can't apply as this line was changed in a more recent version.")
+ end
+ end
+
+ context 'and suggestion is for multiple lines' do
+ let(:single_line) { false }
+
+ it 'returns appropriate message' do
+ expect(inapplicable_reason).to eq("Can't apply as these lines were changed in a more recent version.")
+ end
+ end
+ end
+
+ context 'and suggestion has the same content' do
+ let(:reason) { :same_content }
+
+ it 'returns appropriate message' do
+ expect(inapplicable_reason).to eq("This suggestion already matches its content.")
+ end
+ end
+
+ context 'and suggestion is inapplicable for other reasons' do
+ let(:reason) { :some_other_reason }
+
+ it 'returns default message' do
+ expect(inapplicable_reason).to eq("Can't apply this suggestion.")
+ end
+ end
+ end
+ end
+
+ context 'when user cannot apply suggestion' do
+ let(:can_apply_suggestion) { false }
+
+ it 'returns appropriate message' do
+ expect(inapplicable_reason).to eq("You don't have write access to the source branch.")
+ end
+ end
+ end
end
diff --git a/spec/serializers/test_case_entity_spec.rb b/spec/serializers/test_case_entity_spec.rb
index 9f1822ff581..bd2a1b0fb98 100644
--- a/spec/serializers/test_case_entity_spec.rb
+++ b/spec/serializers/test_case_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TestCaseEntity do
+RSpec.describe TestCaseEntity do
include TestReportsHelper
let(:entity) { described_class.new(test_case) }
diff --git a/spec/serializers/test_report_entity_spec.rb b/spec/serializers/test_report_entity_spec.rb
index 5913d1c0208..549b14c04dc 100644
--- a/spec/serializers/test_report_entity_spec.rb
+++ b/spec/serializers/test_report_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TestReportEntity do
+RSpec.describe TestReportEntity do
let(:pipeline) { create(:ci_pipeline, :with_test_reports) }
let(:entity) { described_class.new(pipeline.test_reports) }
diff --git a/spec/serializers/test_report_summary_entity_spec.rb b/spec/serializers/test_report_summary_entity_spec.rb
new file mode 100644
index 00000000000..fcac9af5c23
--- /dev/null
+++ b/spec/serializers/test_report_summary_entity_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe TestReportSummaryEntity do
+ let(:pipeline) { create(:ci_pipeline, :with_report_results) }
+ let(:entity) { described_class.new(pipeline.test_report_summary) }
+
+ describe '#as_json' do
+ subject(:as_json) { entity.as_json }
+
+ it 'contains the total time' do
+ expect(as_json).to include(:total_time)
+ end
+
+ it 'contains the counts' do
+ expect(as_json).to include(:total_count, :success_count, :failed_count, :skipped_count, :error_count)
+ end
+
+ context 'when summary has test suites' do
+ it 'contains the test suites' do
+ expect(as_json).to include(:test_suites)
+ expect(as_json[:test_suites].count).to eq(1)
+ end
+
+ it 'contains build_ids' do
+ expect(as_json[:test_suites].first).to include(:build_ids)
+ end
+ end
+ end
+end
diff --git a/spec/serializers/test_reports_comparer_entity_spec.rb b/spec/serializers/test_reports_comparer_entity_spec.rb
index e7dabc67325..3f88438ccde 100644
--- a/spec/serializers/test_reports_comparer_entity_spec.rb
+++ b/spec/serializers/test_reports_comparer_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TestReportsComparerEntity do
+RSpec.describe TestReportsComparerEntity do
include TestReportsHelper
let(:entity) { described_class.new(comparer) }
diff --git a/spec/serializers/test_reports_comparer_serializer_spec.rb b/spec/serializers/test_reports_comparer_serializer_spec.rb
index 0d833afe9e4..f9c37f49039 100644
--- a/spec/serializers/test_reports_comparer_serializer_spec.rb
+++ b/spec/serializers/test_reports_comparer_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TestReportsComparerSerializer do
+RSpec.describe TestReportsComparerSerializer do
include TestReportsHelper
let(:project) { double(:project) }
diff --git a/spec/serializers/test_suite_comparer_entity_spec.rb b/spec/serializers/test_suite_comparer_entity_spec.rb
index 9790777a570..882991a6208 100644
--- a/spec/serializers/test_suite_comparer_entity_spec.rb
+++ b/spec/serializers/test_suite_comparer_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TestSuiteComparerEntity do
+RSpec.describe TestSuiteComparerEntity do
include TestReportsHelper
let(:entity) { described_class.new(comparer) }
diff --git a/spec/serializers/test_suite_entity_spec.rb b/spec/serializers/test_suite_entity_spec.rb
index bd88d235013..83d3086ea6b 100644
--- a/spec/serializers/test_suite_entity_spec.rb
+++ b/spec/serializers/test_suite_entity_spec.rb
@@ -2,36 +2,46 @@
require 'spec_helper'
-describe TestSuiteEntity do
- let(:pipeline) { create(:ci_pipeline, :with_test_reports) }
+RSpec.describe TestSuiteEntity do
+ let(:pipeline) { create(:ci_pipeline, :with_test_reports) }
let(:test_suite) { pipeline.test_reports.test_suites.each_value.first }
- let(:entity) { described_class.new(test_suite) }
+ let(:user) { create(:user) }
+ let(:request) { double('request', current_user: user) }
- describe '#as_json' do
- subject(:as_json) { entity.as_json }
+ subject { described_class.new(test_suite, request: request).as_json }
+
+ context 'when details option is not present' do
+ it 'does not expose suite error and test cases', :aggregate_failures do
+ expect(subject).not_to include(:test_cases)
+ expect(subject).not_to include(:suite_error)
+ end
+ end
+
+ context 'when details option is present' do
+ subject { described_class.new(test_suite, request: request, details: true).as_json }
it 'contains the suite name' do
- expect(as_json[:name]).to be_present
+ expect(subject[:name]).to be_present
end
it 'contains the total time' do
- expect(as_json[:total_time]).to be_present
+ expect(subject[:total_time]).to be_present
end
it 'contains the counts' do
- expect(as_json[:total_count]).to eq(4)
- expect(as_json[:success_count]).to eq(2)
- expect(as_json[:failed_count]).to eq(2)
- expect(as_json[:skipped_count]).to eq(0)
- expect(as_json[:error_count]).to eq(0)
+ expect(subject[:total_count]).to eq(4)
+ expect(subject[:success_count]).to eq(2)
+ expect(subject[:failed_count]).to eq(2)
+ expect(subject[:skipped_count]).to eq(0)
+ expect(subject[:error_count]).to eq(0)
end
it 'contains the test cases' do
- expect(as_json[:test_cases].count).to eq(4)
+ expect(subject[:test_cases].count).to eq(4)
end
it 'contains an empty error message' do
- expect(as_json[:suite_error]).to be_nil
+ expect(subject[:suite_error]).to be_nil
end
context 'with a suite error' do
@@ -40,27 +50,27 @@ describe TestSuiteEntity do
end
it 'contains the suite name' do
- expect(as_json[:name]).to be_present
+ expect(subject[:name]).to be_present
end
it 'contains the total time' do
- expect(as_json[:total_time]).to be_present
+ expect(subject[:total_time]).to be_present
end
it 'returns all the counts as 0' do
- expect(as_json[:total_count]).to eq(0)
- expect(as_json[:success_count]).to eq(0)
- expect(as_json[:failed_count]).to eq(0)
- expect(as_json[:skipped_count]).to eq(0)
- expect(as_json[:error_count]).to eq(0)
+ expect(subject[:total_count]).to eq(0)
+ expect(subject[:success_count]).to eq(0)
+ expect(subject[:failed_count]).to eq(0)
+ expect(subject[:skipped_count]).to eq(0)
+ expect(subject[:error_count]).to eq(0)
end
it 'returns no test cases' do
- expect(as_json[:test_cases]).to be_empty
+ expect(subject[:test_cases]).to be_empty
end
it 'returns a suite error' do
- expect(as_json[:suite_error]).to eq('a really bad error')
+ expect(subject[:suite_error]).to eq('a really bad error')
end
end
end
diff --git a/spec/serializers/test_suite_summary_entity_spec.rb b/spec/serializers/test_suite_summary_entity_spec.rb
new file mode 100644
index 00000000000..d26592bc60e
--- /dev/null
+++ b/spec/serializers/test_suite_summary_entity_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe TestSuiteSummaryEntity do
+ let(:pipeline) { create(:ci_pipeline, :with_report_results) }
+ let(:entity) { described_class.new(pipeline.test_report_summary.total) }
+
+ describe '#as_json' do
+ subject(:as_json) { entity.as_json }
+
+ it 'contains the total time' do
+ expect(as_json).to include(:total_time)
+ end
+
+ it 'contains the counts' do
+ expect(as_json).to include(:total_count, :success_count, :failed_count, :skipped_count, :error_count)
+ end
+
+ it 'contains the build_ids' do
+ expect(as_json).to include(:build_ids)
+ end
+ end
+end
diff --git a/spec/serializers/trigger_variable_entity_spec.rb b/spec/serializers/trigger_variable_entity_spec.rb
index f5a21f943d8..e90bfc24f9f 100644
--- a/spec/serializers/trigger_variable_entity_spec.rb
+++ b/spec/serializers/trigger_variable_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TriggerVariableEntity do
+RSpec.describe TriggerVariableEntity do
let(:project) { create(:project) }
let(:request) { double('request') }
let(:user) { create(:user) }
diff --git a/spec/serializers/user_entity_spec.rb b/spec/serializers/user_entity_spec.rb
index 71107daf6ac..e9d86fe7282 100644
--- a/spec/serializers/user_entity_spec.rb
+++ b/spec/serializers/user_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserEntity do
+RSpec.describe UserEntity do
include Gitlab::Routing
let(:entity) { described_class.new(user) }
diff --git a/spec/serializers/user_serializer_spec.rb b/spec/serializers/user_serializer_spec.rb
index 2e4a8c644fe..d54f33b6a23 100644
--- a/spec/serializers/user_serializer_spec.rb
+++ b/spec/serializers/user_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserSerializer do
+RSpec.describe UserSerializer do
let(:user1) { create(:user) }
let(:user2) { create(:user) }
diff --git a/spec/serializers/web_ide_terminal_entity_spec.rb b/spec/serializers/web_ide_terminal_entity_spec.rb
index e163afa14ed..a6f108bf25e 100644
--- a/spec/serializers/web_ide_terminal_entity_spec.rb
+++ b/spec/serializers/web_ide_terminal_entity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WebIdeTerminalEntity do
+RSpec.describe WebIdeTerminalEntity do
let(:build) { create(:ci_build) }
let(:entity) { described_class.new(WebIdeTerminal.new(build)) }
diff --git a/spec/serializers/web_ide_terminal_serializer_spec.rb b/spec/serializers/web_ide_terminal_serializer_spec.rb
index 01133deaf84..7ddc4bfedc8 100644
--- a/spec/serializers/web_ide_terminal_serializer_spec.rb
+++ b/spec/serializers/web_ide_terminal_serializer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WebIdeTerminalSerializer do
+RSpec.describe WebIdeTerminalSerializer do
let(:build) { create(:ci_build) }
subject { described_class.new.represent(WebIdeTerminal.new(build)) }
diff --git a/spec/services/access_token_validation_service_spec.rb b/spec/services/access_token_validation_service_spec.rb
index b2a8da6c4c6..2bf74d64dc9 100644
--- a/spec/services/access_token_validation_service_spec.rb
+++ b/spec/services/access_token_validation_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AccessTokenValidationService do
+RSpec.describe AccessTokenValidationService do
describe ".include_any_scope?" do
let(:request) { double("request") }
diff --git a/spec/services/alert_management/alerts/todo/create_service_spec.rb b/spec/services/alert_management/alerts/todo/create_service_spec.rb
new file mode 100644
index 00000000000..e3d9de8b4df
--- /dev/null
+++ b/spec/services/alert_management/alerts/todo/create_service_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe AlertManagement::Alerts::Todo::CreateService do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:alert) { create(:alert_management_alert) }
+
+ let(:current_user) { user }
+
+ describe '#execute' do
+ subject(:result) { AlertManagement::Alerts::Todo::CreateService.new(alert, current_user).execute }
+
+ shared_examples 'permissions error' do
+ it 'returns an error', :aggregate_failures do
+ expect(result.error?).to be(true)
+ expect(result.message).to eq('You have insufficient permissions to create a Todo for this alert')
+ expect(result.payload[:todo]).to be(nil)
+ expect(result.payload[:alert]).to be(alert)
+ end
+ end
+
+ context 'when the user is anonymous' do
+ let(:current_user) { nil }
+
+ it_behaves_like 'permissions error'
+ end
+
+ context 'when the user does not have permission' do
+ it_behaves_like 'permissions error'
+ end
+
+ context 'when user has permission' do
+ before do
+ alert.project.add_developer(user)
+ end
+
+ it 'creates a todo' do
+ expect { result }.to change { Todo.count }.by(1)
+ end
+
+ it 'returns the alert and todo in the payload', :aggregate_failures do
+ expect(result.success?).to be(true)
+ expect(result.payload[:alert][:id]).to be(alert.id)
+ expect(result.payload[:todo][:id]).to be(Todo.last.id)
+ end
+
+ context 'when the user has a marked todo for the alert' do
+ let_it_be(:todo_params) do
+ { project: alert.project,
+ target: alert,
+ user: user,
+ action: Todo::MARKED }
+ end
+
+ context 'when todo is pending' do
+ before_all do
+ create(:todo, :pending, **todo_params)
+ end
+
+ it 'does not create a todo' do
+ expect { result }.not_to change { Todo.count }
+ end
+
+ it 'returns an error', :aggregate_failures do
+ expect(result.error?).to be(true)
+ expect(result.message).to be('You already have pending todo for this alert')
+ expect(result.payload[:todo]).to be(nil)
+ expect(result.payload[:alert]).to be(alert)
+ end
+ end
+
+ context 'when todo is done' do
+ before do
+ create(:todo, :done, **todo_params)
+ end
+
+ it { expect(result.success?).to be(true) }
+ it { expect { result }.to change { Todo.count }.by(1) }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/alert_management/alerts/update_service_spec.rb b/spec/services/alert_management/alerts/update_service_spec.rb
index e185e67c5cf..91b02325bad 100644
--- a/spec/services/alert_management/alerts/update_service_spec.rb
+++ b/spec/services/alert_management/alerts/update_service_spec.rb
@@ -2,11 +2,12 @@
require 'spec_helper'
-describe AlertManagement::Alerts::UpdateService do
+RSpec.describe AlertManagement::Alerts::UpdateService do
let_it_be(:user_with_permissions) { create(:user) }
+ let_it_be(:other_user_with_permissions) { create(:user) }
let_it_be(:user_without_permissions) { create(:user) }
- let_it_be(:alert, reload: true) { create(:alert_management_alert) }
- let_it_be(:project) { alert.project }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:alert, reload: true) { create(:alert_management_alert, :triggered, project: project) }
let(:current_user) { user_with_permissions }
let(:params) { {} }
@@ -15,119 +16,225 @@ describe AlertManagement::Alerts::UpdateService do
before_all do
project.add_developer(user_with_permissions)
+ project.add_developer(other_user_with_permissions)
end
describe '#execute' do
+ shared_examples 'does not add a todo' do
+ specify { expect { response }.not_to change(Todo, :count) }
+ end
+
+ shared_examples 'does not add a system note' do
+ specify { expect { response }.not_to change(Note, :count) }
+ end
+
+ shared_examples 'adds a system note' do
+ specify { expect { response }.to change { alert.reload.notes.count }.by(1) }
+ end
+
+ shared_examples 'error response' do |message|
+ it_behaves_like 'does not add a todo'
+ it_behaves_like 'does not add a system note'
+
+ it 'has an informative message' do
+ expect(response).to be_error
+ expect(response.message).to eq(message)
+ end
+ end
+
subject(:response) { service.execute }
context 'when the current_user is nil' do
let(:current_user) { nil }
- it 'results in an error' do
- expect(response).to be_error
- expect(response.message).to eq('You have no permissions')
- end
+ it_behaves_like 'error response', 'You have no permissions'
end
- context 'when user does not have permission to update alerts' do
+ context 'when current_user does not have permission to update alerts' do
let(:current_user) { user_without_permissions }
- it 'results in an error' do
- expect(response).to be_error
- expect(response.message).to eq('You have no permissions')
- end
+ it_behaves_like 'error response', 'You have no permissions'
end
context 'when no parameters are included' do
- it 'results in an error' do
- expect(response).to be_error
- expect(response.message).to eq('Please provide attributes to update')
- end
+ it_behaves_like 'error response', 'Please provide attributes to update'
end
- context 'when an error occures during update' do
+ context 'when an error occurs during update' do
let(:params) { { title: nil } }
- it 'results in an error' do
- expect { response }.not_to change { alert.reload.notes.count }
- expect(response).to be_error
- expect(response.message).to eq("Title can't be blank")
- end
+ it_behaves_like 'error response', "Title can't be blank"
end
- context 'when a model attribute is included without assignees' do
- let(:params) { { title: 'This is an updated alert.' } }
+ shared_examples 'title update' do
+ it_behaves_like 'does not add a todo'
+ it_behaves_like 'does not add a system note'
it 'updates the attribute' do
original_title = alert.title
- expect { response }.to change { alert.title }.from(original_title).to(params[:title])
+ expect { response }.to change { alert.title }.from(original_title).to(expected_title)
expect(response).to be_success
end
+ end
- it 'skips adding a todo' do
- expect { response }.not_to change(Todo, :count)
- end
+ context 'when a model attribute is included without assignees' do
+ let(:params) { { title: 'This is an updated alert.' } }
+ let(:expected_title) { params[:title] }
+
+ it_behaves_like 'title update'
+ end
+
+ context 'when alert is resolved and another existing open alert' do
+ let!(:alert) { create(:alert_management_alert, :resolved, project: project) }
+ let!(:existing_alert) { create(:alert_management_alert, :triggered, project: project) }
+
+ let(:params) { { title: 'This is an updated alert.' } }
+ let(:expected_title) { params[:title] }
+
+ it_behaves_like 'title update'
end
context 'when assignees are included' do
- let(:params) { { assignees: [user_with_permissions] } }
+ shared_examples 'adds a todo' do
+ let(:assignee) { expected_assignees.first }
- after do
- alert.assignees = []
+ specify do
+ expect { response }.to change { assignee.reload.todos.count }.by(1)
+ expect(assignee.todos.last.author).to eq(current_user)
+ end
end
- it 'assigns the user' do
- expect { response }.to change { alert.reload.assignees }.from([]).to(params[:assignees])
- expect(response).to be_success
+ shared_examples 'successful assignment' do
+ it_behaves_like 'adds a system note'
+ it_behaves_like 'adds a todo'
+
+ after do
+ alert.assignees = []
+ end
+
+ specify do
+ expect { response }.to change { alert.reload.assignees }.from([]).to(expected_assignees)
+ expect(response).to be_success
+ end
+ end
+
+ let(:expected_assignees) { params[:assignees] }
+
+ context 'when the assignee is the current user' do
+ let(:params) { { assignees: [current_user] } }
+
+ it_behaves_like 'successful assignment'
end
- it 'creates a system note for the assignment' do
- expect { response }.to change { alert.reload.notes.count }.by(1)
+ context 'when the assignee has read permissions' do
+ let(:params) { { assignees: [other_user_with_permissions] } }
+
+ it_behaves_like 'successful assignment'
end
- it 'adds a todo' do
- expect { response }.to change { Todo.where(user: user_with_permissions).count }.by(1)
+ context 'when the assignee does not have read permissions' do
+ let(:params) { { assignees: [user_without_permissions] } }
+
+ it_behaves_like 'error response', 'Assignee has no permissions'
end
- context 'when current user is not the assignee' do
- let(:assignee_user) { create(:user) }
- let(:params) { { assignees: [assignee_user] } }
+ context 'when user is already assigned' do
+ let(:params) { { assignees: [user_with_permissions] } }
- it 'skips adding todo for assignee without permission to read alert' do
- expect { response }.not_to change(Todo, :count)
+ before do
+ alert.assignees << user_with_permissions
end
- context 'when assignee has read permission' do
- before do
- project.add_developer(assignee_user)
- end
+ it_behaves_like 'does not add a system note'
+ # TODO: We should not add another todo in this scenario
+ it_behaves_like 'adds a todo'
+ end
- it 'adds a todo' do
- response
+ context 'with multiple users included' do
+ let(:params) { { assignees: [user_with_permissions, user_without_permissions] } }
+ let(:expected_assignees) { [user_with_permissions] }
- expect(Todo.first.author).to eq(current_user)
- end
+ it_behaves_like 'successful assignment'
+ end
+ end
+
+ context 'when a status is included' do
+ let(:params) { { status: new_status } }
+ let(:new_status) { AlertManagement::Alert::STATUSES[:acknowledged] }
+
+ it 'successfully changes the status' do
+ expect { response }.to change { alert.acknowledged? }.to(true)
+ expect(response).to be_success
+ expect(response.payload[:alert]).to eq(alert)
+ end
+
+ it_behaves_like 'adds a system note'
+
+ context 'with unknown status' do
+ let(:new_status) { -1 }
+
+ it_behaves_like 'error response', 'Invalid status'
+ end
+
+ context 'with resolving status' do
+ let(:new_status) { AlertManagement::Alert::STATUSES[:resolved] }
+
+ it 'changes the status' do
+ expect { response }.to change { alert.resolved? }.to(true)
+ end
+
+ it "resolves the current user's related todos" do
+ todo = create(:todo, :pending, target: alert, user: current_user, project: alert.project)
+
+ expect { response }.to change { todo.reload.state }.from('pending').to('done')
+ end
+ end
+
+ context 'with an opening status and existing open alert' do
+ let_it_be(:alert) { create(:alert_management_alert, :resolved, :with_fingerprint, project: project) }
+ let_it_be(:existing_alert) { create(:alert_management_alert, :triggered, fingerprint: alert.fingerprint, project: project) }
+ let_it_be(:url) { Gitlab::Routing.url_helpers.details_project_alert_management_path(project, existing_alert) }
+ let_it_be(:link) { ActionController::Base.helpers.link_to(_('alert'), url) }
+
+ let(:message) do
+ "An #{link} with the same fingerprint is already open. " \
+ 'To change the status of this alert, resolve the linked alert.'
end
- context 'when current_user is nil' do
- let(:current_user) { nil }
+ it_behaves_like 'does not add a todo'
+ it_behaves_like 'does not add a system note'
+
+ it 'has an informative message' do
+ expect(response).to be_error
+ expect(response.message).to eq(message)
+ end
- it 'skips adding todo if current_user is nil' do
- project.add_developer(assignee_user)
+ context 'fingerprints are blank' do
+ let_it_be(:alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: nil) }
+ let_it_be(:existing_alert) { create(:alert_management_alert, :triggered, fingerprint: alert.fingerprint, project: project) }
- expect { response }.not_to change(Todo, :count)
+ it 'successfully changes the status' do
+ expect { response }.to change { alert.acknowledged? }.to(true)
+ expect(response).to be_success
+ expect(response.payload[:alert]).to eq(alert)
end
+
+ it_behaves_like 'adds a system note'
end
end
- context 'with multiple users included' do
- let(:params) { { assignees: [user_with_permissions, user_without_permissions] } }
+ context 'two existing closed alerts' do
+ let_it_be(:alert) { create(:alert_management_alert, :resolved, :with_fingerprint, project: project) }
+ let_it_be(:existing_alert) { create(:alert_management_alert, :resolved, fingerprint: alert.fingerprint, project: project) }
- it 'assigns the first permissioned user' do
- expect { response }.to change { alert.reload.assignees }.from([]).to([user_with_permissions])
+ it 'successfully changes the status' do
+ expect { response }.to change { alert.acknowledged? }.to(true)
expect(response).to be_success
+ expect(response.payload[:alert]).to eq(alert)
end
+
+ it_behaves_like 'adds a system note'
end
end
end
diff --git a/spec/services/alert_management/create_alert_issue_service_spec.rb b/spec/services/alert_management/create_alert_issue_service_spec.rb
index 9bc8b731dc1..a8f2b4ee09c 100644
--- a/spec/services/alert_management/create_alert_issue_service_spec.rb
+++ b/spec/services/alert_management/create_alert_issue_service_spec.rb
@@ -4,19 +4,18 @@ require 'spec_helper'
RSpec.describe AlertManagement::CreateAlertIssueService do
let_it_be(:user) { create(:user) }
- let_it_be(:project) { create(:project) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
let_it_be(:payload) do
{
- 'annotations' => {
- 'title' => 'Alert title'
- },
'startsAt' => '2020-04-27T10:10:22.265949279Z',
'generatorURL' => 'http://8d467bd4607a:9090/graph?g0.expr=vector%281%29&g0.tab=1'
}
end
let_it_be(:generic_alert, reload: true) { create(:alert_management_alert, :triggered, project: project, payload: payload) }
- let_it_be(:prometheus_alert) { create(:alert_management_alert, :triggered, :prometheus, project: project, payload: payload) }
+ let_it_be(:prometheus_alert, reload: true) { create(:alert_management_alert, :triggered, :prometheus, project: project, payload: payload) }
let(:alert) { generic_alert }
+ let(:alert_presenter) { alert.present }
let(:created_issue) { Issue.last! }
describe '#execute' do
@@ -29,7 +28,7 @@ RSpec.describe AlertManagement::CreateAlertIssueService do
.and_return(can_create)
end
- shared_examples 'creating an alert' do
+ shared_examples 'creating an alert issue' do
it 'creates an issue' do
expect { execute }.to change { project.issues.count }.by(1)
end
@@ -48,11 +47,27 @@ RSpec.describe AlertManagement::CreateAlertIssueService do
expect(alert.reload.issue_id).to eq(created_issue.id)
end
- it 'sets issue author to the current user' do
+ it 'creates a system note' do
+ expect { execute }.to change { alert.reload.notes.count }.by(1)
+ end
+ end
+
+ shared_examples 'setting an issue attributes' do
+ before do
execute
+ end
+ it 'sets issue author to the current user' do
expect(created_issue.author).to eq(user)
end
+
+ it 'sets the issue title' do
+ expect(created_issue.title).to eq(alert.title)
+ end
+
+ it 'sets the issue description' do
+ expect(created_issue.description).to include(alert_presenter.issue_summary_markdown.strip)
+ end
end
context 'when a user is allowed to create an issue' do
@@ -69,27 +84,33 @@ RSpec.describe AlertManagement::CreateAlertIssueService do
context 'when the alert is prometheus alert' do
let(:alert) { prometheus_alert }
+ let(:issue) { subject.payload[:issue] }
- it_behaves_like 'creating an alert'
+ it_behaves_like 'creating an alert issue'
+ it_behaves_like 'setting an issue attributes'
+ it_behaves_like 'create alert issue sets issue labels'
end
context 'when the alert is generic' do
let(:alert) { generic_alert }
+ let(:issue) { subject.payload[:issue] }
- it_behaves_like 'creating an alert'
+ it_behaves_like 'creating an alert issue'
+ it_behaves_like 'setting an issue attributes'
+ it_behaves_like 'create alert issue sets issue labels'
end
context 'when issue cannot be created' do
- let(:alert) { prometheus_alert }
+ let(:alert) { generic_alert }
before do
- # set invalid payload for Prometheus alert
- alert.update!(payload: {})
+ # Invalid alert
+ alert.update_columns(title: '')
end
it 'has an unsuccessful status' do
expect(execute).to be_error
- expect(execute.message).to eq('invalid alert')
+ expect(execute.message).to eq("Title can't be blank")
end
end
diff --git a/spec/services/alert_management/process_prometheus_alert_service_spec.rb b/spec/services/alert_management/process_prometheus_alert_service_spec.rb
index 5b4da5e9077..0ce88f6b5b7 100644
--- a/spec/services/alert_management/process_prometheus_alert_service_spec.rb
+++ b/spec/services/alert_management/process_prometheus_alert_service_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe AlertManagement::ProcessPrometheusAlertService do
- let_it_be(:project) { create(:project) }
+ let_it_be(:project) { create(:project, :repository) }
before do
allow(ProjectServiceWorker).to receive(:perform_async)
@@ -35,26 +35,31 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
}
end
- context 'when Prometheus alert status is firing' do
- let(:status) { 'firing' }
+ let(:status) { 'firing' }
+ context 'when Prometheus alert status is firing' do
context 'when alert with the same fingerprint already exists' do
- let!(:alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: parsed_alert.gitlab_fingerprint) }
+ let!(:alert) { create(:alert_management_alert, project: project, fingerprint: parsed_alert.gitlab_fingerprint) }
+
+ it_behaves_like 'adds an alert management alert event'
+
+ context 'existing alert is resolved' do
+ let!(:alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: parsed_alert.gitlab_fingerprint) }
- it 'increases alert events count' do
- expect { execute }.to change { alert.reload.events }.by(1)
+ it_behaves_like 'creates an alert management alert'
end
- context 'when status can be changed' do
- it 'changes status to triggered' do
- expect { execute }.to change { alert.reload.triggered? }.to(true)
- end
+ context 'existing alert is ignored' do
+ let!(:alert) { create(:alert_management_alert, :ignored, project: project, fingerprint: parsed_alert.gitlab_fingerprint) }
+
+ it_behaves_like 'adds an alert management alert event'
end
- it 'does not executes the alert service hooks' do
- expect(alert).not_to receive(:execute_services)
+ context 'two existing alerts, one resolved one open' do
+ let!(:resolved_alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: parsed_alert.gitlab_fingerprint) }
+ let!(:alert) { create(:alert_management_alert, project: project, fingerprint: parsed_alert.gitlab_fingerprint) }
- subject
+ it_behaves_like 'adds an alert management alert event'
end
context 'when status change did not succeed' do
@@ -73,23 +78,11 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
execute
end
end
-
- it { is_expected.to be_success }
end
context 'when alert does not exist' do
context 'when alert can be created' do
- it 'creates a new alert' do
- expect { execute }.to change { AlertManagement::Alert.where(project: project).count }.by(1)
- end
-
- it 'executes the alert service hooks' do
- slack_service = create(:service, type: 'SlackService', project: project, alert_events: true, active: true)
-
- subject
-
- expect(ProjectServiceWorker).to have_received(:perform_async).with(slack_service.id, an_instance_of(Hash))
- end
+ it_behaves_like 'creates an alert management alert'
end
context 'when alert cannot be created' do
@@ -123,6 +116,31 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
it 'resolves an existing alert' do
expect { execute }.to change { alert.reload.resolved? }.to(true)
end
+
+ [true, false].each do |state_tracking_enabled|
+ context 'existing issue' do
+ before do
+ stub_feature_flags(track_resource_state_change_events: state_tracking_enabled)
+ end
+
+ let!(:alert) { create(:alert_management_alert, :with_issue, project: project, fingerprint: parsed_alert.gitlab_fingerprint) }
+
+ it 'closes the issue' do
+ issue = alert.issue
+
+ expect { execute }
+ .to change { issue.reload.state }
+ .from('opened')
+ .to('closed')
+ end
+
+ if state_tracking_enabled
+ specify { expect { execute }.to change(ResourceStateEvent, :count).by(1) }
+ else
+ specify { expect { execute }.to change(Note, :count).by(1) }
+ end
+ end
+ end
end
context 'when status change did not succeed' do
@@ -144,6 +162,33 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
it { is_expected.to be_success }
end
+
+ context 'environment given' do
+ let(:environment) { create(:environment, project: project) }
+
+ it 'sets the environment' do
+ payload['labels']['gitlab_environment_name'] = environment.name
+ execute
+
+ alert = project.alert_management_alerts.last
+
+ expect(alert.environment).to eq(environment)
+ end
+ end
+
+ context 'prometheus alert given' do
+ let(:prometheus_alert) { create(:prometheus_alert, project: project) }
+
+ it 'sets the prometheus alert and environment' do
+ payload['labels']['gitlab_alert_id'] = prometheus_alert.prometheus_metric_id
+ execute
+
+ alert = project.alert_management_alerts.last
+
+ expect(alert.prometheus_alert).to eq(prometheus_alert)
+ expect(alert.environment).to eq(prometheus_alert.environment)
+ end
+ end
end
context 'when alert payload is invalid' do
diff --git a/spec/services/alert_management/update_alert_status_service_spec.rb b/spec/services/alert_management/update_alert_status_service_spec.rb
deleted file mode 100644
index b287d0d1614..00000000000
--- a/spec/services/alert_management/update_alert_status_service_spec.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe AlertManagement::UpdateAlertStatusService do
- let(:project) { alert.project }
- let_it_be(:user) { build(:user) }
-
- let_it_be(:alert, reload: true) do
- create(:alert_management_alert, :triggered)
- end
-
- let(:service) { described_class.new(alert, user, new_status) }
-
- describe '#execute' do
- shared_examples 'update failure' do |error_message|
- it 'returns an error' do
- expect(response).to be_error
- expect(response.message).to eq(error_message)
- expect(response.payload[:alert]).to eq(alert)
- end
-
- it 'does not update the status' do
- expect { response }.not_to change { alert.status }
- end
- end
-
- let(:new_status) { Types::AlertManagement::StatusEnum.values['ACKNOWLEDGED'].value }
- let(:can_update) { true }
-
- subject(:response) { service.execute }
-
- before do
- allow(user).to receive(:can?)
- .with(:update_alert_management_alert, project)
- .and_return(can_update)
- end
-
- it 'returns success' do
- expect(response).to be_success
- expect(response.payload[:alert]).to eq(alert)
- end
-
- it 'updates the status' do
- expect { response }.to change { alert.acknowledged? }.to(true)
- end
-
- context 'when user has no permissions' do
- let(:can_update) { false }
-
- include_examples 'update failure', _('You have no permissions')
- end
-
- context 'with no status' do
- let(:new_status) { nil }
-
- include_examples 'update failure', _('Invalid status')
- end
-
- context 'with unknown status' do
- let(:new_status) { -1 }
-
- include_examples 'update failure', _('Invalid status')
- end
- end
-end
diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb
index 3a37cbc3522..e5060fa2eeb 100644
--- a/spec/services/application_settings/update_service_spec.rb
+++ b/spec/services/application_settings/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ApplicationSettings::UpdateService do
+RSpec.describe ApplicationSettings::UpdateService do
include ExternalAuthorizationServiceHelpers
let(:application_settings) { create(:application_setting) }
diff --git a/spec/services/applications/create_service_spec.rb b/spec/services/applications/create_service_spec.rb
index c8134087fa1..58ac723ee55 100644
--- a/spec/services/applications/create_service_spec.rb
+++ b/spec/services/applications/create_service_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe ::Applications::CreateService do
+RSpec.describe ::Applications::CreateService do
include TestRequestHelpers
let(:user) { create(:user) }
diff --git a/spec/services/audit_event_service_spec.rb b/spec/services/audit_event_service_spec.rb
index dc86735805c..530d3469481 100644
--- a/spec/services/audit_event_service_spec.rb
+++ b/spec/services/audit_event_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AuditEventService do
+RSpec.describe AuditEventService do
let(:project) { create(:project) }
let(:user) { create(:user, :with_sign_ins) }
let(:project_member) { create(:project_member, user: user) }
@@ -17,6 +17,7 @@ describe AuditEventService do
it 'creates an event and logs to a file' do
expect(service).to receive(:file_logger).and_return(logger)
expect(logger).to receive(:info).with(author_id: user.id,
+ author_name: user.name,
entity_id: project.id,
entity_type: "Project",
action: :destroy)
@@ -35,6 +36,7 @@ describe AuditEventService do
})
expect(service).to receive(:file_logger).and_return(logger)
expect(logger).to receive(:info).with(author_id: user.id,
+ author_name: user.name,
entity_type: 'Project',
entity_id: project.id,
from: 'true',
@@ -56,6 +58,7 @@ describe AuditEventService do
it 'logs security event to file' do
expect(service).to receive(:file_logger).and_return(logger)
expect(logger).to receive(:info).with(author_id: user.id,
+ author_name: user.name,
entity_type: 'Project',
entity_id: project.id,
action: :destroy)
diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb
index 70eb35f0826..8d58c4b27e1 100644
--- a/spec/services/auth/container_registry_authentication_service_spec.rb
+++ b/spec/services/auth/container_registry_authentication_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Auth::ContainerRegistryAuthenticationService do
+RSpec.describe Auth::ContainerRegistryAuthenticationService do
let(:current_project) { nil }
let(:current_user) { nil }
let(:current_params) { {} }
diff --git a/spec/services/authorized_project_update/periodic_recalculate_service_spec.rb b/spec/services/authorized_project_update/periodic_recalculate_service_spec.rb
index 020056da36e..c776e013fdf 100644
--- a/spec/services/authorized_project_update/periodic_recalculate_service_spec.rb
+++ b/spec/services/authorized_project_update/periodic_recalculate_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AuthorizedProjectUpdate::PeriodicRecalculateService do
+RSpec.describe AuthorizedProjectUpdate::PeriodicRecalculateService do
subject(:service) { described_class.new }
describe '#execute' do
diff --git a/spec/services/authorized_project_update/project_create_service_spec.rb b/spec/services/authorized_project_update/project_create_service_spec.rb
index 5b3e36af766..891800bfb87 100644
--- a/spec/services/authorized_project_update/project_create_service_spec.rb
+++ b/spec/services/authorized_project_update/project_create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AuthorizedProjectUpdate::ProjectCreateService do
+RSpec.describe AuthorizedProjectUpdate::ProjectCreateService do
let_it_be(:group_parent) { create(:group, :private) }
let_it_be(:group) { create(:group, :private, parent: group_parent) }
let_it_be(:group_child) { create(:group, :private, parent: group) }
diff --git a/spec/services/authorized_project_update/project_group_link_create_service_spec.rb b/spec/services/authorized_project_update/project_group_link_create_service_spec.rb
new file mode 100644
index 00000000000..d30d9f1e766
--- /dev/null
+++ b/spec/services/authorized_project_update/project_group_link_create_service_spec.rb
@@ -0,0 +1,190 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe AuthorizedProjectUpdate::ProjectGroupLinkCreateService do
+ let_it_be(:group_parent) { create(:group, :private) }
+ let_it_be(:group) { create(:group, :private, parent: group_parent) }
+ let_it_be(:group_child) { create(:group, :private, parent: group) }
+
+ let_it_be(:parent_group_user) { create(:user) }
+ let_it_be(:group_user) { create(:user) }
+
+ let_it_be(:project) { create(:project, :private, group: create(:group, :private)) }
+
+ let(:access_level) { Gitlab::Access::MAINTAINER }
+
+ subject(:service) { described_class.new(project, group) }
+
+ describe '#perform' do
+ context 'direct group members' do
+ before do
+ create(:group_member, access_level: access_level, group: group, user: group_user)
+ ProjectAuthorization.delete_all
+ end
+
+ it 'creates project authorization' do
+ expect { service.execute }.to(
+ change { ProjectAuthorization.count }.from(0).to(1))
+
+ project_authorization = ProjectAuthorization.where(
+ project_id: project.id,
+ user_id: group_user.id,
+ access_level: access_level)
+
+ expect(project_authorization).to exist
+ end
+ end
+
+ context 'inherited group members' do
+ before do
+ create(:group_member, access_level: access_level, group: group_parent, user: parent_group_user)
+ ProjectAuthorization.delete_all
+ end
+
+ it 'creates project authorization' do
+ expect { service.execute }.to(
+ change { ProjectAuthorization.count }.from(0).to(1))
+
+ project_authorization = ProjectAuthorization.where(
+ project_id: project.id,
+ user_id: parent_group_user.id,
+ access_level: access_level)
+ expect(project_authorization).to exist
+ end
+ end
+
+ context 'membership overrides' do
+ before do
+ create(:group_member, access_level: Gitlab::Access::REPORTER, group: group_parent, user: group_user)
+ create(:group_member, access_level: Gitlab::Access::DEVELOPER, group: group, user: group_user)
+ ProjectAuthorization.delete_all
+ end
+
+ it 'creates project authorization' do
+ expect { service.execute }.to(
+ change { ProjectAuthorization.count }.from(0).to(1))
+
+ project_authorization = ProjectAuthorization.where(
+ project_id: project.id,
+ user_id: group_user.id,
+ access_level: Gitlab::Access::DEVELOPER)
+ expect(project_authorization).to exist
+ end
+ end
+
+ context 'no group member' do
+ it 'does not create project authorization' do
+ expect { service.execute }.not_to(
+ change { ProjectAuthorization.count }.from(0))
+ end
+ end
+
+ context 'unapproved access requests' do
+ before do
+ create(:group_member, :guest, :access_request, user: group_user, group: group)
+ end
+
+ it 'does not create project authorization' do
+ expect { service.execute }.not_to(
+ change { ProjectAuthorization.count }.from(0))
+ end
+ end
+
+ context 'project has more users than BATCH_SIZE' do
+ let(:batch_size) { 2 }
+ let(:users) { create_list(:user, batch_size + 1 ) }
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", batch_size)
+
+ users.each do |user|
+ create(:group_member, access_level: access_level, group: group_parent, user: user)
+ end
+
+ ProjectAuthorization.delete_all
+ end
+
+ it 'bulk creates project authorizations in batches' do
+ users.each_slice(batch_size) do |batch|
+ attributes = batch.map do |user|
+ { user_id: user.id, project_id: project.id, access_level: access_level }
+ end
+
+ expect(ProjectAuthorization).to(
+ receive(:insert_all).with(array_including(attributes)).and_call_original)
+ end
+
+ expect { service.execute }.to(
+ change { ProjectAuthorization.count }.from(0).to(batch_size + 1))
+ end
+ end
+
+ context 'users have existing project authorizations' do
+ before do
+ create(:group_member, access_level: access_level, group: group, user: group_user)
+ ProjectAuthorization.delete_all
+
+ create(:project_authorization, user_id: group_user.id,
+ project_id: project.id,
+ access_level: existing_access_level)
+ end
+
+ context 'when access level is the same' do
+ let(:existing_access_level) { access_level }
+
+ it 'does not create project authorization' do
+ project_authorization = ProjectAuthorization.where(
+ project_id: project.id,
+ user_id: group_user.id,
+ access_level: existing_access_level)
+
+ expect(ProjectAuthorization).not_to receive(:insert_all)
+
+ expect { service.execute }.not_to(
+ change { project_authorization.reload.exists? }.from(true))
+ end
+ end
+
+ context 'when existing access level is lower' do
+ let(:existing_access_level) { Gitlab::Access::DEVELOPER }
+
+ it 'creates new project authorization' do
+ project_authorization = ProjectAuthorization.where(
+ project_id: project.id,
+ user_id: group_user.id,
+ access_level: access_level)
+
+ expect { service.execute }.to(
+ change { project_authorization.reload.exists? }.from(false).to(true))
+ end
+
+ it 'deletes previous project authorization' do
+ project_authorization = ProjectAuthorization.where(
+ project_id: project.id,
+ user_id: group_user.id,
+ access_level: existing_access_level)
+
+ expect { service.execute }.to(
+ change { project_authorization.reload.exists? }.from(true).to(false))
+ end
+ end
+
+ context 'when existing access level is higher' do
+ let(:existing_access_level) { Gitlab::Access::OWNER }
+
+ it 'does not create project authorization' do
+ project_authorization = ProjectAuthorization.where(
+ project_id: project.id,
+ user_id: group_user.id,
+ access_level: existing_access_level)
+
+ expect(ProjectAuthorization).not_to receive(:insert_all)
+
+ expect { service.execute }.not_to(
+ change { project_authorization.reload.exists? }.from(true))
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/authorized_project_update/recalculate_for_user_range_service_spec.rb b/spec/services/authorized_project_update/recalculate_for_user_range_service_spec.rb
index 28cbda6f4fd..a4637b6ba1c 100644
--- a/spec/services/authorized_project_update/recalculate_for_user_range_service_spec.rb
+++ b/spec/services/authorized_project_update/recalculate_for_user_range_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AuthorizedProjectUpdate::RecalculateForUserRangeService do
+RSpec.describe AuthorizedProjectUpdate::RecalculateForUserRangeService do
describe '#execute' do
let_it_be(:users) { create_list(:user, 2) }
diff --git a/spec/services/auto_merge/base_service_spec.rb b/spec/services/auto_merge/base_service_spec.rb
index e08e1d670bf..98fa6012089 100644
--- a/spec/services/auto_merge/base_service_spec.rb
+++ b/spec/services/auto_merge/base_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AutoMerge::BaseService do
+RSpec.describe AutoMerge::BaseService do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:service) { described_class.new(project, user, params) }
@@ -51,7 +51,7 @@ describe AutoMerge::BaseService do
expect(merge_request.merge_params['commit_message']).to eq("Merge branch 'patch-12' into 'master'")
expect(merge_request.merge_params['sha']).to eq('200fcc9c260f7219eaf0daba87d818f0922c5b18')
expect(merge_request.merge_params['should_remove_source_branch']).to eq(false)
- expect(merge_request.squash).to eq(false)
+ expect(merge_request.squash_on_merge?).to eq(false)
expect(merge_request.merge_params['squash_commit_message']).to eq('Update README.md')
end
end
diff --git a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
index b6e8d3c636a..3bf59f6a2d1 100644
--- a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
+++ b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AutoMerge::MergeWhenPipelineSucceedsService do
+RSpec.describe AutoMerge::MergeWhenPipelineSucceedsService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
@@ -69,6 +69,7 @@ describe AutoMerge::MergeWhenPipelineSucceedsService do
before do
allow(merge_request)
.to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
+ expect(MailScheduler::NotificationServiceWorker).to receive(:perform_async).with('merge_when_pipeline_succeeds', merge_request, user).once
service.execute(merge_request)
end
@@ -90,6 +91,18 @@ describe AutoMerge::MergeWhenPipelineSucceedsService do
end
end
+ context 'without feature enabled' do
+ it 'does not send notification' do
+ stub_feature_flags(mwps_notification: false)
+
+ allow(merge_request)
+ .to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
+ expect(MailScheduler::NotificationServiceWorker).not_to receive(:perform_async)
+
+ service.execute(merge_request)
+ end
+ end
+
context 'already approved' do
let(:service) { described_class.new(project, user, should_remove_source_branch: true) }
let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) }
@@ -106,6 +119,7 @@ describe AutoMerge::MergeWhenPipelineSucceedsService do
it 'updates the merge params' do
expect(SystemNoteService).not_to receive(:merge_when_pipeline_succeeds)
+ expect(MailScheduler::NotificationServiceWorker).not_to receive(:perform_async).with('merge_when_pipeline_succeeds', any_args)
service.execute(mr_merge_if_green_enabled)
expect(mr_merge_if_green_enabled.merge_params).to have_key('should_remove_source_branch')
diff --git a/spec/services/auto_merge_service_spec.rb b/spec/services/auto_merge_service_spec.rb
index bab69fb4aa3..eab95973e1b 100644
--- a/spec/services/auto_merge_service_spec.rb
+++ b/spec/services/auto_merge_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AutoMergeService do
+RSpec.describe AutoMergeService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:service) { described_class.new(project, user) }
diff --git a/spec/services/award_emojis/add_service_spec.rb b/spec/services/award_emojis/add_service_spec.rb
index 4bcb5fa039f..85c39015614 100644
--- a/spec/services/award_emojis/add_service_spec.rb
+++ b/spec/services/award_emojis/add_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AwardEmojis::AddService do
+RSpec.describe AwardEmojis::AddService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:awardable) { create(:note, project: project) }
diff --git a/spec/services/award_emojis/collect_user_emoji_service_spec.rb b/spec/services/award_emojis/collect_user_emoji_service_spec.rb
index a0dea31b403..bf5aa0eb9ef 100644
--- a/spec/services/award_emojis/collect_user_emoji_service_spec.rb
+++ b/spec/services/award_emojis/collect_user_emoji_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AwardEmojis::CollectUserEmojiService do
+RSpec.describe AwardEmojis::CollectUserEmojiService do
describe '#execute' do
it 'returns an Array containing the awarded emoji names' do
user = create(:user)
diff --git a/spec/services/award_emojis/destroy_service_spec.rb b/spec/services/award_emojis/destroy_service_spec.rb
index f411345560e..2aba078b638 100644
--- a/spec/services/award_emojis/destroy_service_spec.rb
+++ b/spec/services/award_emojis/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AwardEmojis::DestroyService do
+RSpec.describe AwardEmojis::DestroyService do
let_it_be(:user) { create(:user) }
let_it_be(:awardable) { create(:note) }
let_it_be(:project) { awardable.project }
diff --git a/spec/services/award_emojis/toggle_service_spec.rb b/spec/services/award_emojis/toggle_service_spec.rb
index 069bdfcb99f..a7feeed50c6 100644
--- a/spec/services/award_emojis/toggle_service_spec.rb
+++ b/spec/services/award_emojis/toggle_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AwardEmojis::ToggleService do
+RSpec.describe AwardEmojis::ToggleService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public) }
let_it_be(:awardable) { create(:note, project: project) }
diff --git a/spec/services/base_container_service_spec.rb b/spec/services/base_container_service_spec.rb
index 47cfb387e25..1de79eec702 100644
--- a/spec/services/base_container_service_spec.rb
+++ b/spec/services/base_container_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BaseContainerService do
+RSpec.describe BaseContainerService do
let(:project) { Project.new }
let(:user) { User.new }
diff --git a/spec/services/base_count_service_spec.rb b/spec/services/base_count_service_spec.rb
index 275bec9982d..18cab2e8e9a 100644
--- a/spec/services/base_count_service_spec.rb
+++ b/spec/services/base_count_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BaseCountService, :use_clean_rails_memory_store_caching do
+RSpec.describe BaseCountService, :use_clean_rails_memory_store_caching do
let(:service) { described_class.new }
describe '#relation_for_count' do
diff --git a/spec/services/boards/create_service_spec.rb b/spec/services/boards/create_service_spec.rb
index 7d4fb04c6c0..f6a9f0903ce 100644
--- a/spec/services/boards/create_service_spec.rb
+++ b/spec/services/boards/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::CreateService do
+RSpec.describe Boards::CreateService do
describe '#execute' do
context 'when board parent is a project' do
let(:parent) { create(:project) }
diff --git a/spec/services/boards/issues/create_service_spec.rb b/spec/services/boards/issues/create_service_spec.rb
index 3520630dd83..9a6b48c13bf 100644
--- a/spec/services/boards/issues/create_service_spec.rb
+++ b/spec/services/boards/issues/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::Issues::CreateService do
+RSpec.describe Boards::Issues::CreateService do
describe '#execute' do
let(:project) { create(:project) }
let(:board) { create(:board, project: project) }
diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb
index c46ab004af6..29b49db42f9 100644
--- a/spec/services/boards/issues/list_service_spec.rb
+++ b/spec/services/boards/issues/list_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::Issues::ListService do
+RSpec.describe Boards::Issues::ListService do
describe '#execute' do
context 'when parent is a project' do
let(:user) { create(:user) }
diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb
index b9ebbc30c1a..01a3ec72987 100644
--- a/spec/services/boards/issues/move_service_spec.rb
+++ b/spec/services/boards/issues/move_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::Issues::MoveService do
+RSpec.describe Boards::Issues::MoveService do
describe '#execute' do
context 'when parent is a project' do
let(:user) { create(:user) }
diff --git a/spec/services/boards/list_service_spec.rb b/spec/services/boards/list_service_spec.rb
index 4eb023907fa..7c94332a78d 100644
--- a/spec/services/boards/list_service_spec.rb
+++ b/spec/services/boards/list_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::ListService do
+RSpec.describe Boards::ListService do
describe '#execute' do
context 'when board parent is a project' do
let(:parent) { create(:project) }
diff --git a/spec/services/boards/lists/create_service_spec.rb b/spec/services/boards/lists/create_service_spec.rb
index 295ec2c8156..f3d4e62eeca 100644
--- a/spec/services/boards/lists/create_service_spec.rb
+++ b/spec/services/boards/lists/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::Lists::CreateService do
+RSpec.describe Boards::Lists::CreateService do
describe '#execute' do
shared_examples 'creating board lists' do
let(:user) { create(:user) }
diff --git a/spec/services/boards/lists/destroy_service_spec.rb b/spec/services/boards/lists/destroy_service_spec.rb
index b936ef3837f..4c512b96065 100644
--- a/spec/services/boards/lists/destroy_service_spec.rb
+++ b/spec/services/boards/lists/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::Lists::DestroyService do
+RSpec.describe Boards::Lists::DestroyService do
describe '#execute' do
context 'when board parent is a project' do
let(:project) { create(:project) }
diff --git a/spec/services/boards/lists/generate_service_spec.rb b/spec/services/boards/lists/generate_service_spec.rb
index 77b42392470..9597c8e0f54 100644
--- a/spec/services/boards/lists/generate_service_spec.rb
+++ b/spec/services/boards/lists/generate_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::Lists::GenerateService do
+RSpec.describe Boards::Lists::GenerateService do
describe '#execute' do
let(:project) { create(:project) }
let(:board) { create(:board, project: project) }
diff --git a/spec/services/boards/lists/list_service_spec.rb b/spec/services/boards/lists/list_service_spec.rb
index 2535f339495..3d71c467e96 100644
--- a/spec/services/boards/lists/list_service_spec.rb
+++ b/spec/services/boards/lists/list_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::Lists::ListService do
+RSpec.describe Boards::Lists::ListService do
let(:user) { create(:user) }
describe '#execute' do
diff --git a/spec/services/boards/lists/move_service_spec.rb b/spec/services/boards/lists/move_service_spec.rb
index f8fc70ef2d6..2861fc48b4d 100644
--- a/spec/services/boards/lists/move_service_spec.rb
+++ b/spec/services/boards/lists/move_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::Lists::MoveService do
+RSpec.describe Boards::Lists::MoveService do
describe '#execute' do
context 'when board parent is a project' do
let(:project) { create(:project) }
diff --git a/spec/services/boards/lists/update_service_spec.rb b/spec/services/boards/lists/update_service_spec.rb
index 243e0fc50ad..cdc7784469a 100644
--- a/spec/services/boards/lists/update_service_spec.rb
+++ b/spec/services/boards/lists/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::Lists::UpdateService do
+RSpec.describe Boards::Lists::UpdateService do
let(:user) { create(:user) }
let!(:list) { create(:list, board: board, position: 0) }
diff --git a/spec/services/boards/visits/create_service_spec.rb b/spec/services/boards/visits/create_service_spec.rb
index 203c287f396..a9a8754825b 100644
--- a/spec/services/boards/visits/create_service_spec.rb
+++ b/spec/services/boards/visits/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::Visits::CreateService do
+RSpec.describe Boards::Visits::CreateService do
describe '#execute' do
let(:user) { create(:user) }
diff --git a/spec/services/branches/create_service_spec.rb b/spec/services/branches/create_service_spec.rb
index 072a86d17fc..b682a3f26ec 100644
--- a/spec/services/branches/create_service_spec.rb
+++ b/spec/services/branches/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Branches::CreateService do
+RSpec.describe Branches::CreateService do
subject(:service) { described_class.new(project, user) }
let_it_be(:project) { create(:project_empty_repo) }
diff --git a/spec/services/branches/delete_merged_service_spec.rb b/spec/services/branches/delete_merged_service_spec.rb
index 5c87f156ec7..2cf0f53c8c3 100644
--- a/spec/services/branches/delete_merged_service_spec.rb
+++ b/spec/services/branches/delete_merged_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Branches::DeleteMergedService do
+RSpec.describe Branches::DeleteMergedService do
include ProjectForksHelper
subject(:service) { described_class.new(project, project.owner) }
diff --git a/spec/services/branches/delete_service_spec.rb b/spec/services/branches/delete_service_spec.rb
index 2219416d94d..f1e7c9340b1 100644
--- a/spec/services/branches/delete_service_spec.rb
+++ b/spec/services/branches/delete_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Branches::DeleteService do
+RSpec.describe Branches::DeleteService do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:user) { create(:user) }
@@ -10,6 +10,10 @@ describe Branches::DeleteService do
subject(:service) { described_class.new(project, user) }
shared_examples 'a deleted branch' do |branch_name|
+ before do
+ allow(Ci::RefDeleteUnlockArtifactsWorker).to receive(:perform_async)
+ end
+
it 'removes the branch' do
expect(branch_exists?(branch_name)).to be true
@@ -18,6 +22,12 @@ describe Branches::DeleteService do
expect(result.status).to eq :success
expect(branch_exists?(branch_name)).to be false
end
+
+ it 'calls the RefDeleteUnlockArtifactsWorker' do
+ expect(Ci::RefDeleteUnlockArtifactsWorker).to receive(:perform_async).with(project.id, user.id, "refs/heads/#{branch_name}")
+
+ service.execute(branch_name)
+ end
end
describe '#execute' do
diff --git a/spec/services/branches/diverging_commit_counts_service_spec.rb b/spec/services/branches/diverging_commit_counts_service_spec.rb
index 370da773ab2..34a2b81c831 100644
--- a/spec/services/branches/diverging_commit_counts_service_spec.rb
+++ b/spec/services/branches/diverging_commit_counts_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Branches::DivergingCommitCountsService do
+RSpec.describe Branches::DivergingCommitCountsService do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
diff --git a/spec/services/branches/validate_new_service_spec.rb b/spec/services/branches/validate_new_service_spec.rb
index 6d5078d3ccb..02127c8c10d 100644
--- a/spec/services/branches/validate_new_service_spec.rb
+++ b/spec/services/branches/validate_new_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Branches::ValidateNewService do
+RSpec.describe Branches::ValidateNewService do
let(:project) { create(:project, :repository) }
subject(:service) { described_class.new(project) }
diff --git a/spec/services/bulk_push_event_payload_service_spec.rb b/spec/services/bulk_push_event_payload_service_spec.rb
index 661c3540aa0..381c735c003 100644
--- a/spec/services/bulk_push_event_payload_service_spec.rb
+++ b/spec/services/bulk_push_event_payload_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BulkPushEventPayloadService do
+RSpec.describe BulkPushEventPayloadService do
let(:event) { create(:push_event) }
let(:push_data) do
diff --git a/spec/services/chat_names/authorize_user_service_spec.rb b/spec/services/chat_names/authorize_user_service_spec.rb
index 7f32948daad..b0bb741564d 100644
--- a/spec/services/chat_names/authorize_user_service_spec.rb
+++ b/spec/services/chat_names/authorize_user_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatNames::AuthorizeUserService do
+RSpec.describe ChatNames::AuthorizeUserService do
describe '#execute' do
subject { described_class.new(service, params) }
diff --git a/spec/services/chat_names/find_user_service_spec.rb b/spec/services/chat_names/find_user_service_spec.rb
index 9d26f98cd56..a29b243ad2c 100644
--- a/spec/services/chat_names/find_user_service_spec.rb
+++ b/spec/services/chat_names/find_user_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatNames::FindUserService, :clean_gitlab_redis_shared_state do
+RSpec.describe ChatNames::FindUserService, :clean_gitlab_redis_shared_state do
describe '#execute' do
let(:service) { create(:service) }
diff --git a/spec/services/ci/archive_trace_service_spec.rb b/spec/services/ci/archive_trace_service_spec.rb
index ba94013b574..07ea314debc 100644
--- a/spec/services/ci/archive_trace_service_spec.rb
+++ b/spec/services/ci/archive_trace_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::ArchiveTraceService, '#execute' do
+RSpec.describe Ci::ArchiveTraceService, '#execute' do
subject { described_class.new.execute(job, worker_name: ArchiveTraceWorker.name) }
context 'when job is finished' do
diff --git a/spec/services/ci/build_report_result_service_spec.rb b/spec/services/ci/build_report_result_service_spec.rb
index dbdfc774314..3c1ef5301fc 100644
--- a/spec/services/ci/build_report_result_service_spec.rb
+++ b/spec/services/ci/build_report_result_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildReportResultService do
+RSpec.describe Ci::BuildReportResultService do
describe "#execute" do
subject(:build_report_result) { described_class.new.execute(build) }
diff --git a/spec/services/ci/cancel_user_pipelines_service_spec.rb b/spec/services/ci/cancel_user_pipelines_service_spec.rb
index b18bf48a50a..12117051b64 100644
--- a/spec/services/ci/cancel_user_pipelines_service_spec.rb
+++ b/spec/services/ci/cancel_user_pipelines_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::CancelUserPipelinesService do
+RSpec.describe Ci::CancelUserPipelinesService do
describe '#execute' do
let(:user) { create(:user) }
diff --git a/spec/services/ci/compare_accessibility_reports_service_spec.rb b/spec/services/ci/compare_accessibility_reports_service_spec.rb
index aee1fd14bc5..6903a633eeb 100644
--- a/spec/services/ci/compare_accessibility_reports_service_spec.rb
+++ b/spec/services/ci/compare_accessibility_reports_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::CompareAccessibilityReportsService do
+RSpec.describe Ci::CompareAccessibilityReportsService do
let(:service) { described_class.new(project) }
let(:project) { create(:project, :repository) }
diff --git a/spec/services/ci/compare_test_reports_service_spec.rb b/spec/services/ci/compare_test_reports_service_spec.rb
index 46f4d2d42ff..7d31db73b6a 100644
--- a/spec/services/ci/compare_test_reports_service_spec.rb
+++ b/spec/services/ci/compare_test_reports_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::CompareTestReportsService do
+RSpec.describe Ci::CompareTestReportsService do
let(:service) { described_class.new(project) }
let(:project) { create(:project, :repository) }
diff --git a/spec/services/ci/create_cross_project_pipeline_service_spec.rb b/spec/services/ci/create_cross_project_pipeline_service_spec.rb
index 9e2497854bc..1aabdb85afd 100644
--- a/spec/services/ci/create_cross_project_pipeline_service_spec.rb
+++ b/spec/services/ci/create_cross_project_pipeline_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::CreateCrossProjectPipelineService, '#execute' do
+RSpec.describe Ci::CreateCrossProjectPipelineService, '#execute' do
let_it_be(:user) { create(:user) }
let(:upstream_project) { create(:project, :repository) }
let_it_be(:downstream_project) { create(:project, :repository) }
diff --git a/spec/services/ci/create_job_artifacts_service_spec.rb b/spec/services/ci/create_job_artifacts_service_spec.rb
index 4d49923a184..3f5cf079025 100644
--- a/spec/services/ci/create_job_artifacts_service_spec.rb
+++ b/spec/services/ci/create_job_artifacts_service_spec.rb
@@ -2,9 +2,9 @@
require 'spec_helper'
-describe Ci::CreateJobArtifactsService do
+RSpec.describe Ci::CreateJobArtifactsService do
let_it_be(:project) { create(:project) }
- let(:service) { described_class.new(project) }
+ let(:service) { described_class.new(job) }
let(:job) { create(:ci_build, project: project) }
let(:artifacts_sha256) { '0' * 64 }
let(:metadata_file) { nil }
@@ -17,7 +17,7 @@ describe Ci::CreateJobArtifactsService do
{
'artifact_type' => 'archive',
'artifact_format' => 'zip'
- }
+ }.with_indifferent_access
end
def file_to_upload(path, params = {})
@@ -28,27 +28,7 @@ describe Ci::CreateJobArtifactsService do
end
describe '#execute' do
- subject { service.execute(job, artifacts_file, params, metadata_file: metadata_file) }
-
- context 'locking' do
- let(:old_job) { create(:ci_build, pipeline: create(:ci_pipeline, project: job.project, ref: job.ref)) }
- let!(:latest_artifact) { create(:ci_job_artifact, job: old_job, locked: true) }
- let!(:other_artifact) { create(:ci_job_artifact, locked: true) }
-
- it 'locks the new artifact' do
- subject
-
- expect(Ci::JobArtifact.last).to have_attributes(locked: true)
- end
-
- it 'unlocks all other artifacts for the same ref' do
- expect { subject }.to change { latest_artifact.reload.locked }.from(true).to(false)
- end
-
- it 'does not unlock artifacts for other refs' do
- expect { subject }.not_to change { other_artifact.reload.locked }.from(true)
- end
- end
+ subject { service.execute(artifacts_file, params, metadata_file: metadata_file) }
context 'when artifacts file is uploaded' do
it 'saves artifact for the given type' do
@@ -150,7 +130,7 @@ describe Ci::CreateJobArtifactsService do
{
'artifact_type' => 'dotenv',
'artifact_format' => 'gzip'
- }
+ }.with_indifferent_access
end
it 'calls parse service' do
@@ -186,7 +166,7 @@ describe Ci::CreateJobArtifactsService do
{
'artifact_type' => 'cluster_applications',
'artifact_format' => 'gzip'
- }
+ }.with_indifferent_access
end
it 'calls cluster applications parse service' do
diff --git a/spec/services/ci/create_pipeline_service/cache_spec.rb b/spec/services/ci/create_pipeline_service/cache_spec.rb
index 4e0567132ff..614e46f1b1a 100644
--- a/spec/services/ci/create_pipeline_service/cache_spec.rb
+++ b/spec/services/ci/create_pipeline_service/cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::CreatePipelineService do
+RSpec.describe Ci::CreatePipelineService do
context 'cache' do
let(:user) { create(:admin) }
let(:ref) { 'refs/heads/master' }
diff --git a/spec/services/ci/create_pipeline_service/creation_errors_and_warnings_spec.rb b/spec/services/ci/create_pipeline_service/creation_errors_and_warnings_spec.rb
new file mode 100644
index 00000000000..16205529f1c
--- /dev/null
+++ b/spec/services/ci/create_pipeline_service/creation_errors_and_warnings_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::CreatePipelineService do
+ describe 'creation errors and warnings' do
+ let_it_be(:user) { create(:admin) }
+ let_it_be(:project) { create(:project, :repository, creator: user) }
+
+ let(:ref) { 'refs/heads/master' }
+ let(:source) { :push }
+ let(:service) { described_class.new(project, user, { ref: ref }) }
+ let(:pipeline) { service.execute(source) }
+
+ before do
+ stub_ci_pipeline_yaml_file(config)
+ stub_feature_flags(ci_raise_job_rules_without_workflow_rules_warning: true)
+ end
+
+ context 'when created successfully' do
+ context 'when warnings are raised' do
+ let(:config) do
+ <<~YAML
+ test:
+ script: rspec
+ rules:
+ - if: '$CI_COMMIT_BRANCH'
+ YAML
+ end
+
+ it 'contains only warnings' do
+ expect(pipeline.error_messages.map(&:content)).to be_empty
+
+ expect(pipeline.warning_messages.map(&:content)).to contain_exactly(
+ 'jobs:test uses `rules` without defining `workflow:rules`'
+ )
+ end
+
+ context 'when feature flag is disabled for the particular warning' do
+ before do
+ stub_feature_flags(ci_raise_job_rules_without_workflow_rules_warning: false)
+ end
+
+ it 'does not contain warnings' do
+ expect(pipeline.error_messages.map(&:content)).to be_empty
+
+ expect(pipeline.warning_messages.map(&:content)).to be_empty
+ end
+ end
+ end
+
+ context 'when no warnings are raised' do
+ let(:config) do
+ <<~YAML
+ test:
+ script: rspec
+ YAML
+ end
+
+ it 'contains no warnings' do
+ expect(pipeline.error_messages).to be_empty
+
+ expect(pipeline.warning_messages).to be_empty
+ end
+ end
+ end
+
+ context 'when failed to create the pipeline' do
+ context 'when warnings are raised' do
+ let(:config) do
+ <<~YAML
+ build:
+ stage: build
+ script: echo
+ needs: [test]
+ test:
+ stage: test
+ script: echo
+ rules:
+ - if: '$CI_COMMIT_BRANCH'
+ YAML
+ end
+
+ it 'contains both errors and warnings' do
+ error_message = 'build job: need test is not defined in prior stages'
+ warning_message = 'jobs:test uses `rules` without defining `workflow:rules`'
+
+ expect(pipeline.yaml_errors).to eq(error_message)
+ expect(pipeline.error_messages.map(&:content)).to contain_exactly(error_message)
+ expect(pipeline.errors.full_messages).to contain_exactly(error_message)
+
+ expect(pipeline.warning_messages.map(&:content)).to contain_exactly(warning_message)
+ end
+ end
+
+ context 'when no warnings are raised' do
+ let(:config) do
+ <<~YAML
+ invalid: yaml
+ YAML
+ end
+
+ it 'contains only errors' do
+ error_message = 'root config contains unknown keys: invalid'
+ expect(pipeline.yaml_errors).to eq(error_message)
+ expect(pipeline.error_messages.map(&:content)).to contain_exactly(error_message)
+ expect(pipeline.errors.full_messages).to contain_exactly(error_message)
+
+ expect(pipeline.warning_messages).to be_empty
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/create_pipeline_service/custom_config_content_spec.rb b/spec/services/ci/create_pipeline_service/custom_config_content_spec.rb
index 5980260a08a..122870e0f3a 100644
--- a/spec/services/ci/create_pipeline_service/custom_config_content_spec.rb
+++ b/spec/services/ci/create_pipeline_service/custom_config_content_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Ci::CreatePipelineService do
+RSpec.describe Ci::CreatePipelineService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:admin) }
let(:ref) { 'refs/heads/master' }
diff --git a/spec/services/ci/create_pipeline_service/needs_spec.rb b/spec/services/ci/create_pipeline_service/needs_spec.rb
index 17b9cf80cc1..915dc46d664 100644
--- a/spec/services/ci/create_pipeline_service/needs_spec.rb
+++ b/spec/services/ci/create_pipeline_service/needs_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::CreatePipelineService do
+RSpec.describe Ci::CreatePipelineService do
context 'needs' do
let_it_be(:user) { create(:admin) }
let_it_be(:project) { create(:project, :repository, creator: user) }
diff --git a/spec/services/ci/create_pipeline_service/parameter_content_spec.rb b/spec/services/ci/create_pipeline_service/parameter_content_spec.rb
new file mode 100644
index 00000000000..5157574ea04
--- /dev/null
+++ b/spec/services/ci/create_pipeline_service/parameter_content_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::CreatePipelineService do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:admin) }
+ let(:service) { described_class.new(project, user, { ref: 'refs/heads/master' }) }
+ let(:content) do
+ <<~EOY
+ ---
+ stages:
+ - dast
+
+ variables:
+ DAST_VERSION: 1
+ SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"
+
+ dast:
+ stage: dast
+ image:
+ name: "$SECURE_ANALYZERS_PREFIX/dast:$DAST_VERSION"
+ variables:
+ GIT_STRATEGY: none
+ script:
+ - /analyze
+ EOY
+ end
+
+ describe '#execute' do
+ context 'when source is a dangling build' do
+ subject { service.execute(:ondemand_dast_scan, content: content) }
+
+ context 'parameter config content' do
+ it 'creates a pipeline' do
+ expect(subject).to be_persisted
+ end
+
+ it 'creates builds with the correct names' do
+ expect(subject.builds.pluck(:name)).to match_array %w[dast]
+ end
+
+ it 'creates stages with the correct names' do
+ expect(subject.stages.pluck(:name)).to match_array %w[dast]
+ end
+
+ it 'sets the correct config source' do
+ expect(subject.config_source).to eq 'parameter_source'
+ end
+ end
+ end
+
+ context 'when source is not a dangling build' do
+ subject { service.execute(:web, content: content) }
+
+ it 'raises an exception' do
+ klass = Gitlab::Ci::Pipeline::Chain::Config::Content::Parameter::UnsupportedSourceError
+ expect { subject }.to raise_error(klass)
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb b/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb
index a76e83f2d60..016a5dfd18b 100644
--- a/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb
+++ b/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::CreatePipelineService, '#execute' do
+RSpec.describe Ci::CreatePipelineService, '#execute' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:ref_name) { 'master' }
diff --git a/spec/services/ci/create_pipeline_service/pre_post_stages_spec.rb b/spec/services/ci/create_pipeline_service/pre_post_stages_spec.rb
index 2b11b98f58c..00a2dd74968 100644
--- a/spec/services/ci/create_pipeline_service/pre_post_stages_spec.rb
+++ b/spec/services/ci/create_pipeline_service/pre_post_stages_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Ci::CreatePipelineService do
+RSpec.describe Ci::CreatePipelineService do
describe '.pre/.post stages' do
let_it_be(:user) { create(:admin) }
let_it_be(:project) { create(:project, :repository, creator: user) }
diff --git a/spec/services/ci/create_pipeline_service/rules_spec.rb b/spec/services/ci/create_pipeline_service/rules_spec.rb
index 713d230731b..1a1fa6e8f5d 100644
--- a/spec/services/ci/create_pipeline_service/rules_spec.rb
+++ b/spec/services/ci/create_pipeline_service/rules_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Ci::CreatePipelineService do
+RSpec.describe Ci::CreatePipelineService do
let(:user) { create(:admin) }
let(:ref) { 'refs/heads/master' }
let(:source) { :push }
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index b9456d5fcd4..9dc518be996 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::CreatePipelineService do
+RSpec.describe Ci::CreatePipelineService do
include ProjectForksHelper
let_it_be(:project, reload: true) { create(:project, :repository) }
@@ -80,7 +80,7 @@ describe Ci::CreatePipelineService do
it 'records pipeline size in a prometheus histogram' do
histogram = spy('pipeline size histogram')
- allow(Gitlab::Ci::Pipeline::Chain::Metrics)
+ allow(Gitlab::Ci::Pipeline::Metrics)
.to receive(:new).and_return(histogram)
execute_service
@@ -194,6 +194,7 @@ describe Ci::CreatePipelineService do
expect(head_pipeline).to be_persisted
expect(head_pipeline.yaml_errors).to be_present
+ expect(head_pipeline.messages).to be_present
expect(merge_request.reload.head_pipeline).to eq head_pipeline
end
end
@@ -511,7 +512,7 @@ describe Ci::CreatePipelineService do
it 'pull it from Auto-DevOps' do
pipeline = execute_service
expect(pipeline).to be_auto_devops_source
- expect(pipeline.builds.map(&:name)).to match_array(%w[test code_quality build])
+ expect(pipeline.builds.map(&:name)).to match_array(%w[build code_quality eslint-sast test])
end
end
@@ -1683,6 +1684,12 @@ describe Ci::CreatePipelineService do
expect(pipeline).to be_persisted
expect(pipeline.builds.pluck(:name)).to contain_exactly("build_a", "test_a")
end
+
+ it 'bulk inserts all needs' do
+ expect(Ci::BuildNeed).to receive(:bulk_insert!).and_call_original
+
+ expect(pipeline).to be_persisted
+ end
end
context 'when pipeline on feature is created' do
@@ -1695,6 +1702,7 @@ describe Ci::CreatePipelineService do
expect(pipeline).to be_persisted
expect(pipeline.builds).to be_empty
expect(pipeline.yaml_errors).to eq("test_a: needs 'build_a'")
+ expect(pipeline.messages.pluck(:content)).to contain_exactly("test_a: needs 'build_a'")
expect(pipeline.errors[:base]).to contain_exactly("test_a: needs 'build_a'")
end
end
@@ -1706,6 +1714,7 @@ describe Ci::CreatePipelineService do
expect(pipeline).not_to be_persisted
expect(pipeline.builds).to be_empty
expect(pipeline.yaml_errors).to be_nil
+ expect(pipeline.messages).not_to be_empty
expect(pipeline.errors[:base]).to contain_exactly("test_a: needs 'build_a'")
end
end
@@ -2205,6 +2214,83 @@ describe Ci::CreatePipelineService do
expect(find_job('job-7').when).to eq('on_failure')
end
end
+
+ context 'with deploy freeze period `if:` clause' do
+ # '0 23 * * 5' == "At 23:00 on Friday."", '0 7 * * 1' == "At 07:00 on Monday.""
+ let!(:freeze_period) { create(:ci_freeze_period, project: project, freeze_start: '0 23 * * 5', freeze_end: '0 7 * * 1') }
+
+ context 'with 2 jobs' do
+ let(:config) do
+ <<-EOY
+ stages:
+ - test
+ - deploy
+
+ test-job:
+ script:
+ - echo 'running TEST stage'
+
+ deploy-job:
+ stage: deploy
+ script:
+ - echo 'running DEPLOY stage'
+ rules:
+ - if: $CI_DEPLOY_FREEZE == null
+ EOY
+ end
+
+ context 'when outside freeze period' do
+ it 'creates two jobs' do
+ Timecop.freeze(2020, 4, 10, 22, 59) do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly('test-job', 'deploy-job')
+ end
+ end
+ end
+
+ context 'when inside freeze period' do
+ it 'creates one job' do
+ Timecop.freeze(2020, 4, 10, 23, 1) do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly('test-job')
+ end
+ end
+ end
+ end
+
+ context 'with 1 job' do
+ let(:config) do
+ <<-EOY
+ stages:
+ - deploy
+
+ deploy-job:
+ stage: deploy
+ script:
+ - echo 'running DEPLOY stage'
+ rules:
+ - if: $CI_DEPLOY_FREEZE == null
+ EOY
+ end
+
+ context 'when outside freeze period' do
+ it 'creates two jobs' do
+ Timecop.freeze(2020, 4, 10, 22, 59) do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly('deploy-job')
+ end
+ end
+ end
+
+ context 'when inside freeze period' do
+ it 'does not create the pipeline' do
+ Timecop.freeze(2020, 4, 10, 23, 1) do
+ expect(pipeline).not_to be_persisted
+ end
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/services/ci/create_web_ide_terminal_service_spec.rb b/spec/services/ci/create_web_ide_terminal_service_spec.rb
index 2cc67c7cd1d..c1c94e30018 100644
--- a/spec/services/ci/create_web_ide_terminal_service_spec.rb
+++ b/spec/services/ci/create_web_ide_terminal_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::CreateWebIdeTerminalService do
+RSpec.describe Ci::CreateWebIdeTerminalService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:ref) { 'master' }
diff --git a/spec/services/ci/daily_build_group_report_result_service_spec.rb b/spec/services/ci/daily_build_group_report_result_service_spec.rb
index f0b72b8fd86..7d181a5c2ba 100644
--- a/spec/services/ci/daily_build_group_report_result_service_spec.rb
+++ b/spec/services/ci/daily_build_group_report_result_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::DailyBuildGroupReportResultService, '#execute' do
+RSpec.describe Ci::DailyBuildGroupReportResultService, '#execute' do
let!(:pipeline) { create(:ci_pipeline, created_at: '2020-02-06 00:01:10') }
let!(:rspec_job) { create(:ci_build, pipeline: pipeline, name: '3/3 rspec', coverage: 80) }
let!(:karma_job) { create(:ci_build, pipeline: pipeline, name: '2/2 karma', coverage: 90) }
diff --git a/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb b/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb
index 4b9f12d8fdf..79443f16276 100644
--- a/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb
+++ b/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared_state do
+RSpec.describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared_state do
include ExclusiveLeaseHelpers
describe '.execute' do
@@ -14,7 +14,7 @@ describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared_state
context 'when artifact is expired' do
context 'when artifact is not locked' do
before do
- artifact.update!(locked: false)
+ artifact.job.pipeline.unlocked!
end
it 'destroys job artifact' do
@@ -24,7 +24,7 @@ describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared_state
context 'when artifact is locked' do
before do
- artifact.update!(locked: true)
+ artifact.job.pipeline.artifacts_locked!
end
it 'does not destroy job artifact' do
diff --git a/spec/services/ci/destroy_pipeline_service_spec.rb b/spec/services/ci/destroy_pipeline_service_spec.rb
index bff2b3179fb..23cbe683d2f 100644
--- a/spec/services/ci/destroy_pipeline_service_spec.rb
+++ b/spec/services/ci/destroy_pipeline_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Ci::DestroyPipelineService do
+RSpec.describe ::Ci::DestroyPipelineService do
let(:project) { create(:project, :repository) }
let!(:pipeline) { create(:ci_pipeline, :success, project: project, sha: project.commit.id) }
diff --git a/spec/services/ci/ensure_stage_service_spec.rb b/spec/services/ci/ensure_stage_service_spec.rb
index 8a270d77bae..3ede214cdd4 100644
--- a/spec/services/ci/ensure_stage_service_spec.rb
+++ b/spec/services/ci/ensure_stage_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::EnsureStageService, '#execute' do
+RSpec.describe Ci::EnsureStageService, '#execute' do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
diff --git a/spec/services/ci/expire_pipeline_cache_service_spec.rb b/spec/services/ci/expire_pipeline_cache_service_spec.rb
index 2962e9dd31e..b5d664947de 100644
--- a/spec/services/ci/expire_pipeline_cache_service_spec.rb
+++ b/spec/services/ci/expire_pipeline_cache_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::ExpirePipelineCacheService do
+RSpec.describe Ci::ExpirePipelineCacheService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
diff --git a/spec/services/ci/external_pull_requests/create_pipeline_service_spec.rb b/spec/services/ci/external_pull_requests/create_pipeline_service_spec.rb
index 5048f2b71b3..e2bdfae27f0 100644
--- a/spec/services/ci/external_pull_requests/create_pipeline_service_spec.rb
+++ b/spec/services/ci/external_pull_requests/create_pipeline_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::ExternalPullRequests::CreatePipelineService do
+RSpec.describe Ci::ExternalPullRequests::CreatePipelineService do
describe '#execute' do
let_it_be(:project) { create(:project, :auto_devops, :repository) }
let_it_be(:user) { create(:user) }
diff --git a/spec/services/ci/extract_sections_from_build_trace_service_spec.rb b/spec/services/ci/extract_sections_from_build_trace_service_spec.rb
index 03c67c611fe..c6ffcdcc6a8 100644
--- a/spec/services/ci/extract_sections_from_build_trace_service_spec.rb
+++ b/spec/services/ci/extract_sections_from_build_trace_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::ExtractSectionsFromBuildTraceService, '#execute' do
+RSpec.describe Ci::ExtractSectionsFromBuildTraceService, '#execute' do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:build) { create(:ci_build, project: project) }
diff --git a/spec/services/ci/find_exposed_artifacts_service_spec.rb b/spec/services/ci/find_exposed_artifacts_service_spec.rb
index 16e23253c34..287f5c4b929 100644
--- a/spec/services/ci/find_exposed_artifacts_service_spec.rb
+++ b/spec/services/ci/find_exposed_artifacts_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::FindExposedArtifactsService do
+RSpec.describe Ci::FindExposedArtifactsService do
include Gitlab::Routing
let(:metadata) do
diff --git a/spec/services/ci/generate_coverage_reports_service_spec.rb b/spec/services/ci/generate_coverage_reports_service_spec.rb
index b64b682a00b..a3ed2eec713 100644
--- a/spec/services/ci/generate_coverage_reports_service_spec.rb
+++ b/spec/services/ci/generate_coverage_reports_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::GenerateCoverageReportsService do
+RSpec.describe Ci::GenerateCoverageReportsService do
let(:service) { described_class.new(project) }
let(:project) { create(:project, :repository) }
diff --git a/spec/services/ci/generate_terraform_reports_service_spec.rb b/spec/services/ci/generate_terraform_reports_service_spec.rb
index 008ecf17b3e..25bf96035b2 100644
--- a/spec/services/ci/generate_terraform_reports_service_spec.rb
+++ b/spec/services/ci/generate_terraform_reports_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::GenerateTerraformReportsService do
+RSpec.describe Ci::GenerateTerraformReportsService do
let_it_be(:project) { create(:project, :repository) }
describe '#execute' do
@@ -33,19 +33,36 @@ describe Ci::GenerateTerraformReportsService do
end
context 'when head pipeline has corrupted terraform reports' do
- it 'returns status and error message' do
+ it 'returns a report with error messages' do
build = create(:ci_build, pipeline: merge_request.head_pipeline, project: project)
create(:ci_job_artifact, :terraform_with_corrupted_data, job: build, project: project)
result = subject.execute(nil, merge_request.head_pipeline)
expect(result).to match(
- status: :error,
- status_reason: 'An error occurred while fetching terraform reports.',
+ status: :parsed,
+ data: match(
+ a_hash_including(build.id.to_s => hash_including(
+ 'tf_report_error' => :invalid_json_format
+ ))
+ ),
key: an_instance_of(Array)
)
end
end
+
+ context 'when head pipeline is corrupted' do
+ it 'returns status and error message' do
+ result = subject.execute(nil, nil)
+
+ expect(result).to match(
+ a_hash_including(
+ status: :error,
+ status_reason: 'An error occurred while fetching terraform reports.'
+ )
+ )
+ end
+ end
end
describe '#latest?' do
diff --git a/spec/services/ci/parse_dotenv_artifact_service_spec.rb b/spec/services/ci/parse_dotenv_artifact_service_spec.rb
index fc4131d262b..a5f01187a83 100644
--- a/spec/services/ci/parse_dotenv_artifact_service_spec.rb
+++ b/spec/services/ci/parse_dotenv_artifact_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::ParseDotenvArtifactService do
+RSpec.describe Ci::ParseDotenvArtifactService do
let_it_be(:project) { create(:project) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline, project: project) }
diff --git a/spec/services/ci/pipeline_bridge_status_service_spec.rb b/spec/services/ci/pipeline_bridge_status_service_spec.rb
index 7e79d222349..584b23bb3aa 100644
--- a/spec/services/ci/pipeline_bridge_status_service_spec.rb
+++ b/spec/services/ci/pipeline_bridge_status_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelineBridgeStatusService do
+RSpec.describe Ci::PipelineBridgeStatusService do
let(:user) { build(:user) }
let_it_be(:project) { create(:project) }
let(:pipeline) { build(:ci_pipeline, project: project) }
diff --git a/spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb b/spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb
index de3c7713ac8..7868629d34d 100644
--- a/spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb
+++ b/spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelineProcessing::AtomicProcessingService::StatusCollection do
+RSpec.describe Ci::PipelineProcessing::AtomicProcessingService::StatusCollection do
using RSpec::Parameterized::TableSyntax
let_it_be(:pipeline) { create(:ci_pipeline) }
diff --git a/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb b/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb
index 3b66ecff196..a10a333b462 100644
--- a/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb
+++ b/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require_relative 'shared_processing_service.rb'
require_relative 'shared_processing_service_tests_with_yaml.rb'
-describe Ci::PipelineProcessing::AtomicProcessingService do
+RSpec.describe Ci::PipelineProcessing::AtomicProcessingService do
before do
stub_feature_flags(ci_atomic_processing: true)
diff --git a/spec/services/ci/pipeline_processing/legacy_processing_service_spec.rb b/spec/services/ci/pipeline_processing/legacy_processing_service_spec.rb
index fd491bf461b..569a6d62dc1 100644
--- a/spec/services/ci/pipeline_processing/legacy_processing_service_spec.rb
+++ b/spec/services/ci/pipeline_processing/legacy_processing_service_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require_relative 'shared_processing_service.rb'
require_relative 'shared_processing_service_tests_with_yaml.rb'
-describe Ci::PipelineProcessing::LegacyProcessingService do
+RSpec.describe Ci::PipelineProcessing::LegacyProcessingService do
before do
stub_feature_flags(ci_atomic_processing: false)
end
diff --git a/spec/services/ci/pipeline_processing/shared_processing_service.rb b/spec/services/ci/pipeline_processing/shared_processing_service.rb
index 29fa43001ae..224066885b6 100644
--- a/spec/services/ci/pipeline_processing/shared_processing_service.rb
+++ b/spec/services/ci/pipeline_processing/shared_processing_service.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'Pipeline Processing Service' do
+RSpec.shared_examples 'Pipeline Processing Service' do
let(:user) { create(:user) }
let(:project) { create(:project) }
diff --git a/spec/services/ci/pipeline_processing/shared_processing_service_tests_with_yaml.rb b/spec/services/ci/pipeline_processing/shared_processing_service_tests_with_yaml.rb
index 93f83f0ea3b..17d254ba48e 100644
--- a/spec/services/ci/pipeline_processing/shared_processing_service_tests_with_yaml.rb
+++ b/spec/services/ci/pipeline_processing/shared_processing_service_tests_with_yaml.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'Pipeline Processing Service Tests With Yaml' do
+RSpec.shared_context 'Pipeline Processing Service Tests With Yaml' do
where(:test_file_path) do
Dir.glob(Rails.root.join('spec/services/ci/pipeline_processing/test_cases/*.yml'))
end
diff --git a/spec/services/ci/pipeline_schedule_service_spec.rb b/spec/services/ci/pipeline_schedule_service_spec.rb
index 867ed0acc0d..65bbd13c5e7 100644
--- a/spec/services/ci/pipeline_schedule_service_spec.rb
+++ b/spec/services/ci/pipeline_schedule_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelineScheduleService do
+RSpec.describe Ci::PipelineScheduleService do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:service) { described_class.new(project, user) }
diff --git a/spec/services/ci/pipeline_trigger_service_spec.rb b/spec/services/ci/pipeline_trigger_service_spec.rb
index 44ce1ff699b..18fab9623ec 100644
--- a/spec/services/ci/pipeline_trigger_service_spec.rb
+++ b/spec/services/ci/pipeline_trigger_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelineTriggerService do
+RSpec.describe Ci::PipelineTriggerService do
let(:project) { create(:project, :repository) }
before do
diff --git a/spec/services/ci/play_build_service_spec.rb b/spec/services/ci/play_build_service_spec.rb
index cf39f3da4fe..c9ecbad3167 100644
--- a/spec/services/ci/play_build_service_spec.rb
+++ b/spec/services/ci/play_build_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PlayBuildService, '#execute' do
+RSpec.describe Ci::PlayBuildService, '#execute' do
let(:user) { create(:user, developer_projects: [project]) }
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
diff --git a/spec/services/ci/play_manual_stage_service_spec.rb b/spec/services/ci/play_manual_stage_service_spec.rb
index e2946111a13..e30ec8bfda5 100644
--- a/spec/services/ci/play_manual_stage_service_spec.rb
+++ b/spec/services/ci/play_manual_stage_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PlayManualStageService, '#execute' do
+RSpec.describe Ci::PlayManualStageService, '#execute' do
let(:current_user) { create(:user) }
let(:pipeline) { create(:ci_pipeline, user: current_user) }
let(:project) { pipeline.project }
diff --git a/spec/services/ci/prepare_build_service_spec.rb b/spec/services/ci/prepare_build_service_spec.rb
index 02928b58ff8..f75cb322fe9 100644
--- a/spec/services/ci/prepare_build_service_spec.rb
+++ b/spec/services/ci/prepare_build_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PrepareBuildService do
+RSpec.describe Ci::PrepareBuildService do
describe '#execute' do
let(:build) { create(:ci_build, :preparing) }
diff --git a/spec/services/ci/process_build_service_spec.rb b/spec/services/ci/process_build_service_spec.rb
index abc5c18a523..a6e8732f5ff 100644
--- a/spec/services/ci/process_build_service_spec.rb
+++ b/spec/services/ci/process_build_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Ci::ProcessBuildService, '#execute' do
+RSpec.describe Ci::ProcessBuildService, '#execute' do
let(:user) { create(:user) }
let(:project) { create(:project) }
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index 40ae1c4029b..a7889f0644d 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::ProcessPipelineService do
+RSpec.describe Ci::ProcessPipelineService do
let(:user) { create(:user) }
let(:project) { create(:project) }
@@ -10,38 +10,52 @@ describe Ci::ProcessPipelineService do
create(:ci_empty_pipeline, ref: 'master', project: project)
end
+ subject { described_class.new(pipeline) }
+
before do
stub_ci_pipeline_to_return_yaml_file
-
stub_not_protect_default_branch
project.add_developer(user)
end
- context 'updates a list of retried builds' do
- subject { described_class.retried.order(:id) }
+ describe 'processing events counter' do
+ let(:metrics) { double('pipeline metrics') }
+ let(:counter) { double('events counter') }
+
+ before do
+ allow(subject)
+ .to receive(:metrics).and_return(metrics)
+ allow(metrics)
+ .to receive(:pipeline_processing_events_counter)
+ .and_return(counter)
+ end
+
+ it 'increments processing events counter' do
+ expect(counter).to receive(:increment)
+
+ subject.execute
+ end
+ end
+ describe 'updating a list of retried builds' do
let!(:build_retried) { create_build('build') }
let!(:build) { create_build('build') }
let!(:test) { create_build('test') }
it 'returns unique statuses' do
- process_pipeline
+ subject.execute
expect(all_builds.latest).to contain_exactly(build, test)
expect(all_builds.retried).to contain_exactly(build_retried)
end
- end
-
- def process_pipeline
- described_class.new(pipeline).execute
- end
- def create_build(name, **opts)
- create(:ci_build, :created, pipeline: pipeline, name: name, **opts)
- end
+ def create_build(name, **opts)
+ create(:ci_build, :created, pipeline: pipeline, name: name, **opts)
+ end
- def all_builds
- pipeline.builds.order(:stage_idx, :id)
+ def all_builds
+ pipeline.builds.order(:stage_idx, :id)
+ end
end
end
diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb
index c0f854df9b7..921f5ba4c7e 100644
--- a/spec/services/ci/register_job_service_spec.rb
+++ b/spec/services/ci/register_job_service_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
module Ci
- describe RegisterJobService do
+ RSpec.describe RegisterJobService do
let_it_be(:group) { create(:group) }
let_it_be(:project, reload: true) { create(:project, group: group, shared_runners_enabled: false, group_runners_enabled: false) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
@@ -109,12 +109,14 @@ module Ci
end
context 'shared runner' do
- let(:build) { execute(shared_runner) }
+ let(:response) { described_class.new(shared_runner).execute }
+ let(:build) { response.build }
it { expect(build).to be_kind_of(Build) }
it { expect(build).to be_valid }
it { expect(build).to be_running }
it { expect(build.runner).to eq(shared_runner) }
+ it { expect(Gitlab::Json.parse(response.build_json)['id']).to eq(build.id) }
end
context 'specific runner' do
@@ -356,13 +358,8 @@ module Ci
end
context 'runner feature set is verified' do
- let!(:pending_job) { create(:ci_build, :pending, pipeline: pipeline) }
-
- before do
- expect_any_instance_of(Ci::Build).to receive(:runner_required_feature_names) do
- [:runner_required_feature]
- end
- end
+ let(:options) { { artifacts: { reports: { junit: "junit.xml" } } } }
+ let!(:pending_job) { create(:ci_build, :pending, pipeline: pipeline, options: options) }
subject { execute(specific_runner, params) }
@@ -378,7 +375,7 @@ module Ci
context 'when feature is supported by runner' do
let(:params) do
- { info: { features: { runner_required_feature: true } } }
+ { info: { features: { upload_multiple_artifacts: true } } }
end
it 'does pick job' do
diff --git a/spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb b/spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb
index 50d312647ae..6c69a7f3b11 100644
--- a/spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb
+++ b/spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::ResourceGroups::AssignResourceFromResourceGroupService do
+RSpec.describe Ci::ResourceGroups::AssignResourceFromResourceGroupService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let(:service) { described_class.new(project, user) }
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
index 90c53d4a346..5a245415b32 100644
--- a/spec/services/ci/retry_build_service_spec.rb
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::RetryBuildService do
+RSpec.describe Ci::RetryBuildService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:pipeline) do
@@ -33,13 +33,13 @@ describe Ci::RetryBuildService do
job_artifacts_sast job_artifacts_secret_detection job_artifacts_dependency_scanning
job_artifacts_container_scanning job_artifacts_dast
job_artifacts_license_management job_artifacts_license_scanning
- job_artifacts_performance job_artifacts_lsif
- job_artifacts_terraform job_artifacts_cluster_applications
+ job_artifacts_performance job_artifacts_browser_performance job_artifacts_load_performance
+ job_artifacts_lsif job_artifacts_terraform job_artifacts_cluster_applications
job_artifacts_codequality job_artifacts_metrics scheduled_at
job_variables waiting_for_resource_at job_artifacts_metrics_referee
job_artifacts_network_referee job_artifacts_dotenv
job_artifacts_cobertura needs job_artifacts_accessibility
- job_artifacts_requirements].freeze
+ job_artifacts_requirements job_artifacts_coverage_fuzzing].freeze
ignore_accessors =
%i[type lock_version target_url base_tags trace_sections
@@ -279,25 +279,16 @@ describe Ci::RetryBuildService do
end
end
- context 'when scheduling_type of build is nil' do
+ context 'when build has needs' do
before do
- build.update_columns(scheduling_type: nil)
+ create(:ci_build_need, build: build, name: 'build1')
+ create(:ci_build_need, build: build, name: 'build2')
end
- context 'when build has not needs' do
- it 'sets scheduling_type as :stage' do
- expect(new_build.scheduling_type).to eq('stage')
- end
- end
+ it 'bulk inserts all needs' do
+ expect(Ci::BuildNeed).to receive(:bulk_insert!).and_call_original
- context 'when build has needs' do
- before do
- create(:ci_build_need, build: build)
- end
-
- it 'sets scheduling_type as :dag' do
- expect(new_build.scheduling_type).to eq('dag')
- end
+ new_build
end
end
end
diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb
index 8e85e68d4fc..fa46d6c4d1d 100644
--- a/spec/services/ci/retry_pipeline_service_spec.rb
+++ b/spec/services/ci/retry_pipeline_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::RetryPipelineService, '#execute' do
+RSpec.describe Ci::RetryPipelineService, '#execute' do
include ProjectForksHelper
let(:user) { create(:user) }
diff --git a/spec/services/ci/run_scheduled_build_service_spec.rb b/spec/services/ci/run_scheduled_build_service_spec.rb
index 43d110cbc8f..27d25e88944 100644
--- a/spec/services/ci/run_scheduled_build_service_spec.rb
+++ b/spec/services/ci/run_scheduled_build_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::RunScheduledBuildService do
+RSpec.describe Ci::RunScheduledBuildService do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
diff --git a/spec/services/ci/stop_environments_service_spec.rb b/spec/services/ci/stop_environments_service_spec.rb
index ebbe6c37b87..5a0b7f23556 100644
--- a/spec/services/ci/stop_environments_service_spec.rb
+++ b/spec/services/ci/stop_environments_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::StopEnvironmentsService do
+RSpec.describe Ci::StopEnvironmentsService do
include CreateEnvironmentsHelpers
let(:project) { create(:project, :private, :repository) }
diff --git a/spec/services/ci/unlock_artifacts_service_spec.rb b/spec/services/ci/unlock_artifacts_service_spec.rb
new file mode 100644
index 00000000000..8d289a867ba
--- /dev/null
+++ b/spec/services/ci/unlock_artifacts_service_spec.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::UnlockArtifactsService do
+ describe '#execute' do
+ subject(:execute) { described_class.new(pipeline.project, pipeline.user).execute(ci_ref, before_pipeline) }
+
+ before do
+ stub_const("#{described_class}::BATCH_SIZE", 1)
+ end
+
+ [true, false].each do |tag|
+ context "when tag is #{tag}" do
+ let(:ref) { 'master' }
+ let(:ref_path) { tag ? "#{::Gitlab::Git::TAG_REF_PREFIX}#{ref}" : "#{::Gitlab::Git::BRANCH_REF_PREFIX}#{ref}" }
+ let(:ci_ref) { create(:ci_ref, ref_path: ref_path) }
+
+ let!(:old_unlocked_pipeline) { create(:ci_pipeline, ref: ref, tag: tag, project: ci_ref.project, locked: :unlocked) }
+ let!(:older_pipeline) { create(:ci_pipeline, ref: ref, tag: tag, project: ci_ref.project, locked: :artifacts_locked) }
+ let!(:older_ambiguous_pipeline) { create(:ci_pipeline, ref: ref, tag: !tag, project: ci_ref.project, locked: :artifacts_locked) }
+ let!(:pipeline) { create(:ci_pipeline, ref: ref, tag: tag, project: ci_ref.project, locked: :artifacts_locked) }
+ let!(:child_pipeline) { create(:ci_pipeline, ref: ref, tag: tag, project: ci_ref.project, locked: :artifacts_locked) }
+ let!(:newer_pipeline) { create(:ci_pipeline, ref: ref, tag: tag, project: ci_ref.project, locked: :artifacts_locked) }
+ let!(:other_ref_pipeline) { create(:ci_pipeline, ref: 'other_ref', tag: tag, project: ci_ref.project, locked: :artifacts_locked) }
+
+ before do
+ create(:ci_sources_pipeline,
+ source_job: create(:ci_build, pipeline: pipeline),
+ source_project: ci_ref.project,
+ pipeline: child_pipeline,
+ project: ci_ref.project)
+ end
+
+ context 'when running on a ref before a pipeline' do
+ let(:before_pipeline) { pipeline }
+
+ it 'unlocks artifacts from older pipelines' do
+ expect { execute }.to change { older_pipeline.reload.locked }.from('artifacts_locked').to('unlocked')
+ end
+
+ it 'does not unlock artifacts for tag or branch with same name as ref' do
+ expect { execute }.not_to change { older_ambiguous_pipeline.reload.locked }.from('artifacts_locked')
+ end
+
+ it 'does not unlock artifacts from newer pipelines' do
+ expect { execute }.not_to change { newer_pipeline.reload.locked }.from('artifacts_locked')
+ end
+
+ it 'does not lock artifacts from old unlocked pipelines' do
+ expect { execute }.not_to change { old_unlocked_pipeline.reload.locked }.from('unlocked')
+ end
+
+ it 'does not unlock artifacts from the same pipeline' do
+ expect { execute }.not_to change { pipeline.reload.locked }.from('artifacts_locked')
+ end
+
+ it 'does not unlock artifacts for other refs' do
+ expect { execute }.not_to change { other_ref_pipeline.reload.locked }.from('artifacts_locked')
+ end
+
+ it 'does not unlock artifacts for child pipeline' do
+ expect { execute }.not_to change { child_pipeline.reload.locked }.from('artifacts_locked')
+ end
+ end
+
+ context 'when running on just the ref' do
+ let(:before_pipeline) { nil }
+
+ it 'unlocks artifacts from older pipelines' do
+ expect { execute }.to change { older_pipeline.reload.locked }.from('artifacts_locked').to('unlocked')
+ end
+
+ it 'unlocks artifacts from newer pipelines' do
+ expect { execute }.to change { newer_pipeline.reload.locked }.from('artifacts_locked').to('unlocked')
+ end
+
+ it 'unlocks artifacts from the same pipeline' do
+ expect { execute }.to change { pipeline.reload.locked }.from('artifacts_locked').to('unlocked')
+ end
+
+ it 'does not unlock artifacts for tag or branch with same name as ref' do
+ expect { execute }.not_to change { older_ambiguous_pipeline.reload.locked }.from('artifacts_locked')
+ end
+
+ it 'does not lock artifacts from old unlocked pipelines' do
+ expect { execute }.not_to change { old_unlocked_pipeline.reload.locked }.from('unlocked')
+ end
+
+ it 'does not unlock artifacts for other refs' do
+ expect { execute }.not_to change { other_ref_pipeline.reload.locked }.from('artifacts_locked')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/update_build_queue_service_spec.rb b/spec/services/ci/update_build_queue_service_spec.rb
index 522dd1ba1c2..0f4c0fa5ecb 100644
--- a/spec/services/ci/update_build_queue_service_spec.rb
+++ b/spec/services/ci/update_build_queue_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::UpdateBuildQueueService do
+RSpec.describe Ci::UpdateBuildQueueService do
let(:project) { create(:project, :repository) }
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:pipeline) { create(:ci_pipeline, project: project) }
diff --git a/spec/services/ci/update_instance_variables_service_spec.rb b/spec/services/ci/update_instance_variables_service_spec.rb
index 93f6e5d3ea8..f235d006e34 100644
--- a/spec/services/ci/update_instance_variables_service_spec.rb
+++ b/spec/services/ci/update_instance_variables_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::UpdateInstanceVariablesService do
+RSpec.describe Ci::UpdateInstanceVariablesService do
let(:params) { { variables_attributes: variables_attributes } }
subject { described_class.new(params) }
diff --git a/spec/services/ci/update_runner_service_spec.rb b/spec/services/ci/update_runner_service_spec.rb
index abe575eebc8..cad9e893335 100644
--- a/spec/services/ci/update_runner_service_spec.rb
+++ b/spec/services/ci/update_runner_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::UpdateRunnerService do
+RSpec.describe Ci::UpdateRunnerService do
let(:runner) { create(:ci_runner) }
describe '#update' do
diff --git a/spec/services/ci/web_ide_config_service_spec.rb b/spec/services/ci/web_ide_config_service_spec.rb
index 7522103ccb7..437b468cec8 100644
--- a/spec/services/ci/web_ide_config_service_spec.rb
+++ b/spec/services/ci/web_ide_config_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::WebIdeConfigService do
+RSpec.describe Ci::WebIdeConfigService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:sha) { 'sha' }
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 84bca76e69b..605d9e67ab6 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
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::CheckIngressIpAddressService do
+RSpec.describe Clusters::Applications::CheckIngressIpAddressService do
include ExclusiveLeaseHelpers
let(:application) { create(:clusters_applications_ingress, :installed) }
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 4b8db405101..13f7cd62002 100644
--- a/spec/services/clusters/applications/check_installation_progress_service_spec.rb
+++ b/spec/services/clusters/applications/check_installation_progress_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
+RSpec.describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
RESCHEDULE_PHASES = Gitlab::Kubernetes::Pod::PHASES - [Gitlab::Kubernetes::Pod::SUCCEEDED, Gitlab::Kubernetes::Pod::FAILED].freeze
let(:application) { create(:clusters_applications_helm, :installing) }
diff --git a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
index 9dede1947f8..4b8893429cf 100644
--- a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
+++ b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::CheckUninstallProgressService do
+RSpec.describe Clusters::Applications::CheckUninstallProgressService do
reschedule_phases = Gitlab::Kubernetes::Pod::PHASES - [Gitlab::Kubernetes::Pod::SUCCEEDED, Gitlab::Kubernetes::Pod::FAILED].freeze
let(:application) { create(:clusters_applications_prometheus, :uninstalling) }
diff --git a/spec/services/clusters/applications/check_upgrade_progress_service_spec.rb b/spec/services/clusters/applications/check_upgrade_progress_service_spec.rb
index 29ee897454a..dbde8cec9b9 100644
--- a/spec/services/clusters/applications/check_upgrade_progress_service_spec.rb
+++ b/spec/services/clusters/applications/check_upgrade_progress_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::CheckUpgradeProgressService do
+RSpec.describe Clusters::Applications::CheckUpgradeProgressService do
reschedule_phashes = ::Gitlab::Kubernetes::Pod::PHASES -
[::Gitlab::Kubernetes::Pod::SUCCEEDED, ::Gitlab::Kubernetes::Pod::FAILED, ::Gitlab].freeze
diff --git a/spec/services/clusters/applications/create_service_spec.rb b/spec/services/clusters/applications/create_service_spec.rb
index 0b48af408e1..f93ae2c62f3 100644
--- a/spec/services/clusters/applications/create_service_spec.rb
+++ b/spec/services/clusters/applications/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::CreateService do
+RSpec.describe Clusters::Applications::CreateService do
include TestRequestHelpers
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
diff --git a/spec/services/clusters/applications/destroy_service_spec.rb b/spec/services/clusters/applications/destroy_service_spec.rb
index 8d9dc6a0f11..7306256e68e 100644
--- a/spec/services/clusters/applications/destroy_service_spec.rb
+++ b/spec/services/clusters/applications/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::DestroyService, '#execute' do
+RSpec.describe Clusters::Applications::DestroyService, '#execute' do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:user) { create(:user) }
let(:params) { { application: 'prometheus' } }
diff --git a/spec/services/clusters/applications/install_service_spec.rb b/spec/services/clusters/applications/install_service_spec.rb
index 2441cc595a3..d34b4dd943c 100644
--- a/spec/services/clusters/applications/install_service_spec.rb
+++ b/spec/services/clusters/applications/install_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::InstallService do
+RSpec.describe Clusters::Applications::InstallService do
describe '#execute' do
let(:application) { create(:clusters_applications_helm, :scheduled) }
let!(:install_command) { application.install_command }
diff --git a/spec/services/clusters/applications/patch_service_spec.rb b/spec/services/clusters/applications/patch_service_spec.rb
index dc9843a5116..281da62b80b 100644
--- a/spec/services/clusters/applications/patch_service_spec.rb
+++ b/spec/services/clusters/applications/patch_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::PatchService do
+RSpec.describe Clusters::Applications::PatchService do
describe '#execute' do
let(:application) { create(:clusters_applications_knative, :scheduled) }
let!(:update_command) { application.update_command }
diff --git a/spec/services/clusters/applications/prometheus_config_service_spec.rb b/spec/services/clusters/applications/prometheus_config_service_spec.rb
index b9032e665ec..7399f250248 100644
--- a/spec/services/clusters/applications/prometheus_config_service_spec.rb
+++ b/spec/services/clusters/applications/prometheus_config_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::PrometheusConfigService do
+RSpec.describe Clusters::Applications::PrometheusConfigService do
include Gitlab::Routing.url_helpers
let_it_be(:project) { create(:project) }
diff --git a/spec/services/clusters/applications/prometheus_health_check_service_spec.rb b/spec/services/clusters/applications/prometheus_health_check_service_spec.rb
index 5c4127e4938..fc5a80688e6 100644
--- a/spec/services/clusters/applications/prometheus_health_check_service_spec.rb
+++ b/spec/services/clusters/applications/prometheus_health_check_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::PrometheusHealthCheckService, '#execute' do
+RSpec.describe Clusters::Applications::PrometheusHealthCheckService, '#execute' do
let(:service) { described_class.new(cluster) }
subject { service.execute }
diff --git a/spec/services/clusters/applications/prometheus_update_service_spec.rb b/spec/services/clusters/applications/prometheus_update_service_spec.rb
index 078b01d2777..076ff0210c9 100644
--- a/spec/services/clusters/applications/prometheus_update_service_spec.rb
+++ b/spec/services/clusters/applications/prometheus_update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::PrometheusUpdateService do
+RSpec.describe Clusters::Applications::PrometheusUpdateService do
describe '#execute' do
let(:project) { create(:project) }
let(:environment) { create(:environment, project: project) }
diff --git a/spec/services/clusters/applications/schedule_update_service_spec.rb b/spec/services/clusters/applications/schedule_update_service_spec.rb
index eb1006ce8e0..f559fb1b7aa 100644
--- a/spec/services/clusters/applications/schedule_update_service_spec.rb
+++ b/spec/services/clusters/applications/schedule_update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::ScheduleUpdateService do
+RSpec.describe Clusters::Applications::ScheduleUpdateService do
describe '#execute' do
let(:project) { create(:project) }
diff --git a/spec/services/clusters/applications/uninstall_service_spec.rb b/spec/services/clusters/applications/uninstall_service_spec.rb
index 6d7f0478b20..50d7e82c47e 100644
--- a/spec/services/clusters/applications/uninstall_service_spec.rb
+++ b/spec/services/clusters/applications/uninstall_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::UninstallService, '#execute' do
+RSpec.describe Clusters::Applications::UninstallService, '#execute' do
let(:application) { create(:clusters_applications_prometheus, :scheduled) }
let(:service) { described_class.new(application) }
let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::API) }
diff --git a/spec/services/clusters/applications/update_service_spec.rb b/spec/services/clusters/applications/update_service_spec.rb
index 4676951faff..4c05a12a4a1 100644
--- a/spec/services/clusters/applications/update_service_spec.rb
+++ b/spec/services/clusters/applications/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::UpdateService do
+RSpec.describe Clusters::Applications::UpdateService do
include TestRequestHelpers
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
diff --git a/spec/services/clusters/applications/upgrade_service_spec.rb b/spec/services/clusters/applications/upgrade_service_spec.rb
index 86fb06375f1..22fbb7ca6e3 100644
--- a/spec/services/clusters/applications/upgrade_service_spec.rb
+++ b/spec/services/clusters/applications/upgrade_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::UpgradeService do
+RSpec.describe Clusters::Applications::UpgradeService do
describe '#execute' do
let(:application) { create(:clusters_applications_helm, :scheduled) }
let!(:install_command) { application.install_command }
diff --git a/spec/services/clusters/aws/authorize_role_service_spec.rb b/spec/services/clusters/aws/authorize_role_service_spec.rb
index 3ef332558a2..530268340b7 100644
--- a/spec/services/clusters/aws/authorize_role_service_spec.rb
+++ b/spec/services/clusters/aws/authorize_role_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Aws::AuthorizeRoleService do
+RSpec.describe Clusters::Aws::AuthorizeRoleService do
let(:user) { create(:user) }
let(:credentials) { instance_double(Aws::Credentials) }
let(:credentials_service) { instance_double(Clusters::Aws::FetchCredentialsService, execute: credentials) }
diff --git a/spec/services/clusters/aws/fetch_credentials_service_spec.rb b/spec/services/clusters/aws/fetch_credentials_service_spec.rb
index 9194947c67f..a0e63d96a5c 100644
--- a/spec/services/clusters/aws/fetch_credentials_service_spec.rb
+++ b/spec/services/clusters/aws/fetch_credentials_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Aws::FetchCredentialsService do
+RSpec.describe Clusters::Aws::FetchCredentialsService do
describe '#execute' do
let(:user) { create(:user) }
let(:provider) { create(:cluster_provider_aws, region: 'ap-southeast-2') }
diff --git a/spec/services/clusters/aws/finalize_creation_service_spec.rb b/spec/services/clusters/aws/finalize_creation_service_spec.rb
index 8d7341483e3..6b0cb86eff0 100644
--- a/spec/services/clusters/aws/finalize_creation_service_spec.rb
+++ b/spec/services/clusters/aws/finalize_creation_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Aws::FinalizeCreationService do
+RSpec.describe Clusters::Aws::FinalizeCreationService do
describe '#execute' do
let(:provider) { create(:cluster_provider_aws, :creating) }
let(:platform) { provider.cluster.platform_kubernetes }
diff --git a/spec/services/clusters/aws/provision_service_spec.rb b/spec/services/clusters/aws/provision_service_spec.rb
index 15571c64e13..529e1d26575 100644
--- a/spec/services/clusters/aws/provision_service_spec.rb
+++ b/spec/services/clusters/aws/provision_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Aws::ProvisionService do
+RSpec.describe Clusters::Aws::ProvisionService do
describe '#execute' do
let(:provider) { create(:cluster_provider_aws) }
diff --git a/spec/services/clusters/aws/verify_provision_status_service_spec.rb b/spec/services/clusters/aws/verify_provision_status_service_spec.rb
index b62b0875bf3..b9a58b97842 100644
--- a/spec/services/clusters/aws/verify_provision_status_service_spec.rb
+++ b/spec/services/clusters/aws/verify_provision_status_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Aws::VerifyProvisionStatusService do
+RSpec.describe Clusters::Aws::VerifyProvisionStatusService do
describe '#execute' do
let(:provider) { create(:cluster_provider_aws) }
diff --git a/spec/services/clusters/build_kubernetes_namespace_service_spec.rb b/spec/services/clusters/build_kubernetes_namespace_service_spec.rb
index 36c05469542..4ee933374f6 100644
--- a/spec/services/clusters/build_kubernetes_namespace_service_spec.rb
+++ b/spec/services/clusters/build_kubernetes_namespace_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::BuildKubernetesNamespaceService do
+RSpec.describe Clusters::BuildKubernetesNamespaceService do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:environment) { create(:environment) }
let(:project) { environment.project }
diff --git a/spec/services/clusters/build_service_spec.rb b/spec/services/clusters/build_service_spec.rb
index f3e852726f4..c7a64435d3b 100644
--- a/spec/services/clusters/build_service_spec.rb
+++ b/spec/services/clusters/build_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::BuildService do
+RSpec.describe Clusters::BuildService do
describe '#execute' do
subject { described_class.new(cluster_subject).execute }
diff --git a/spec/services/clusters/cleanup/app_service_spec.rb b/spec/services/clusters/cleanup/app_service_spec.rb
index 14bfca02fee..ba1be7448a4 100644
--- a/spec/services/clusters/cleanup/app_service_spec.rb
+++ b/spec/services/clusters/cleanup/app_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Cleanup::AppService do
+RSpec.describe Clusters::Cleanup::AppService do
describe '#execute' do
let!(:cluster) { create(:cluster, :project, :cleanup_uninstalling_applications, provider_type: :gcp) }
let(:service) { described_class.new(cluster) }
diff --git a/spec/services/clusters/cleanup/project_namespace_service_spec.rb b/spec/services/clusters/cleanup/project_namespace_service_spec.rb
index 22e29cc57d1..761ad8dd8c8 100644
--- a/spec/services/clusters/cleanup/project_namespace_service_spec.rb
+++ b/spec/services/clusters/cleanup/project_namespace_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Cleanup::ProjectNamespaceService do
+RSpec.describe Clusters::Cleanup::ProjectNamespaceService do
describe '#execute' do
subject { service.execute }
diff --git a/spec/services/clusters/cleanup/service_account_service_spec.rb b/spec/services/clusters/cleanup/service_account_service_spec.rb
index ecaf0da9fa3..6fe3d0c286e 100644
--- a/spec/services/clusters/cleanup/service_account_service_spec.rb
+++ b/spec/services/clusters/cleanup/service_account_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Cleanup::ServiceAccountService do
+RSpec.describe Clusters::Cleanup::ServiceAccountService do
describe '#execute' do
subject { service.execute }
diff --git a/spec/services/clusters/create_service_spec.rb b/spec/services/clusters/create_service_spec.rb
index 3dd25be2a3d..6e252bee7c0 100644
--- a/spec/services/clusters/create_service_spec.rb
+++ b/spec/services/clusters/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::CreateService do
+RSpec.describe Clusters::CreateService do
let(:access_token) { 'xxx' }
let(:project) { create(:project) }
let(:user) { create(:user) }
@@ -53,13 +53,54 @@ describe Clusters::CreateService do
include_context 'valid cluster create params'
let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) }
- it 'does not create a cluster' do
- expect(ClusterProvisionWorker).not_to receive(:perform_async)
- expect { subject }.to raise_error(ArgumentError).and change { Clusters::Cluster.count }.by(0)
+ it 'creates another cluster' do
+ expect(ClusterProvisionWorker).to receive(:perform_async)
+ expect { subject }.to change { Clusters::Cluster.count }.by(1)
end
end
end
+ context 'when another cluster exists' do
+ let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) }
+
+ context 'when correct params' do
+ let(:params) do
+ {
+ name: 'test-cluster',
+ provider_type: :gcp,
+ provider_gcp_attributes: {
+ gcp_project_id: 'gcp-project',
+ zone: 'us-central1-a',
+ num_nodes: 1,
+ machine_type: 'machine_type-a',
+ legacy_abac: 'true'
+ },
+ clusterable: project
+ }
+ end
+
+ include_examples 'create cluster service success'
+ end
+
+ context 'when invalid params' do
+ let(:params) do
+ {
+ name: 'test-cluster',
+ provider_type: :gcp,
+ provider_gcp_attributes: {
+ gcp_project_id: '!!!!!!!',
+ zone: 'us-central1-a',
+ num_nodes: 1,
+ machine_type: 'machine_type-a'
+ },
+ clusterable: project
+ }
+ end
+
+ include_examples 'create cluster service error'
+ end
+ end
+
context 'when params includes :management_project_id' do
subject(:cluster) { described_class.new(user, params).execute(access_token: access_token) }
diff --git a/spec/services/clusters/destroy_service_spec.rb b/spec/services/clusters/destroy_service_spec.rb
index 43ebf8f499e..76d9cc34b5d 100644
--- a/spec/services/clusters/destroy_service_spec.rb
+++ b/spec/services/clusters/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::DestroyService do
+RSpec.describe Clusters::DestroyService do
describe '#execute' do
subject { described_class.new(cluster.user, params).execute(cluster) }
diff --git a/spec/services/clusters/gcp/fetch_operation_service_spec.rb b/spec/services/clusters/gcp/fetch_operation_service_spec.rb
index 23da8004a7d..990cc745382 100644
--- a/spec/services/clusters/gcp/fetch_operation_service_spec.rb
+++ b/spec/services/clusters/gcp/fetch_operation_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Gcp::FetchOperationService do
+RSpec.describe Clusters::Gcp::FetchOperationService do
include GoogleApi::CloudPlatformHelpers
describe '#execute' do
diff --git a/spec/services/clusters/gcp/finalize_creation_service_spec.rb b/spec/services/clusters/gcp/finalize_creation_service_spec.rb
index 4d1548c9786..be362dc6e23 100644
--- a/spec/services/clusters/gcp/finalize_creation_service_spec.rb
+++ b/spec/services/clusters/gcp/finalize_creation_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Gcp::FinalizeCreationService, '#execute' do
+RSpec.describe Clusters::Gcp::FinalizeCreationService, '#execute' do
include GoogleApi::CloudPlatformHelpers
include KubernetesHelpers
diff --git a/spec/services/clusters/gcp/provision_service_spec.rb b/spec/services/clusters/gcp/provision_service_spec.rb
index dfd15690a1f..c5778db6001 100644
--- a/spec/services/clusters/gcp/provision_service_spec.rb
+++ b/spec/services/clusters/gcp/provision_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Gcp::ProvisionService do
+RSpec.describe Clusters::Gcp::ProvisionService do
include GoogleApi::CloudPlatformHelpers
describe '#execute' do
diff --git a/spec/services/clusters/gcp/verify_provision_status_service_spec.rb b/spec/services/clusters/gcp/verify_provision_status_service_spec.rb
index 584f9b8367f..ccb4b3b6c15 100644
--- a/spec/services/clusters/gcp/verify_provision_status_service_spec.rb
+++ b/spec/services/clusters/gcp/verify_provision_status_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Gcp::VerifyProvisionStatusService do
+RSpec.describe Clusters::Gcp::VerifyProvisionStatusService do
include GoogleApi::CloudPlatformHelpers
describe '#execute' do
diff --git a/spec/services/clusters/kubernetes/configure_istio_ingress_service_spec.rb b/spec/services/clusters/kubernetes/configure_istio_ingress_service_spec.rb
index e9f7f015293..b4402aadc88 100644
--- a/spec/services/clusters/kubernetes/configure_istio_ingress_service_spec.rb
+++ b/spec/services/clusters/kubernetes/configure_istio_ingress_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Kubernetes::ConfigureIstioIngressService, '#execute' do
+RSpec.describe Clusters::Kubernetes::ConfigureIstioIngressService, '#execute' do
include KubernetesHelpers
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
diff --git a/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb b/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb
index 6d8b1617c17..ee10c59390e 100644
--- a/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb
+++ b/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute' do
+RSpec.describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute' do
include KubernetesHelpers
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
diff --git a/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb b/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb
index 4bcd5c6933e..f3fa6c2c0bb 100644
--- a/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb
+++ b/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService do
+RSpec.describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService do
include KubernetesHelpers
let(:api_url) { 'http://111.111.111.111' }
diff --git a/spec/services/clusters/kubernetes/fetch_kubernetes_token_service_spec.rb b/spec/services/clusters/kubernetes/fetch_kubernetes_token_service_spec.rb
index fa4b6e497e5..c4daae9dbf0 100644
--- a/spec/services/clusters/kubernetes/fetch_kubernetes_token_service_spec.rb
+++ b/spec/services/clusters/kubernetes/fetch_kubernetes_token_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Kubernetes::FetchKubernetesTokenService do
+RSpec.describe Clusters::Kubernetes::FetchKubernetesTokenService do
include KubernetesHelpers
describe '#execute' do
diff --git a/spec/services/clusters/kubernetes_spec.rb b/spec/services/clusters/kubernetes_spec.rb
index 09cc304debe..12af63890fc 100644
--- a/spec/services/clusters/kubernetes_spec.rb
+++ b/spec/services/clusters/kubernetes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Kubernetes do
+RSpec.describe Clusters::Kubernetes do
it { is_expected.to be_const_defined(:GITLAB_SERVICE_ACCOUNT_NAME) }
it { is_expected.to be_const_defined(:GITLAB_SERVICE_ACCOUNT_NAMESPACE) }
it { is_expected.to be_const_defined(:GITLAB_ADMIN_TOKEN_NAME) }
diff --git a/spec/services/clusters/management/create_project_service_spec.rb b/spec/services/clusters/management/create_project_service_spec.rb
index b7764b7840c..5d8cc71faa4 100644
--- a/spec/services/clusters/management/create_project_service_spec.rb
+++ b/spec/services/clusters/management/create_project_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Management::CreateProjectService do
+RSpec.describe Clusters::Management::CreateProjectService do
let(:cluster) { create(:cluster, :project) }
let(:current_user) { create(:user) }
diff --git a/spec/services/clusters/management/validate_management_project_permissions_service_spec.rb b/spec/services/clusters/management/validate_management_project_permissions_service_spec.rb
index 1bcebe2e2ac..a21c378d3d1 100644
--- a/spec/services/clusters/management/validate_management_project_permissions_service_spec.rb
+++ b/spec/services/clusters/management/validate_management_project_permissions_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Management::ValidateManagementProjectPermissionsService do
+RSpec.describe Clusters::Management::ValidateManagementProjectPermissionsService do
describe '#execute' do
subject { described_class.new(user).execute(cluster, management_project_id) }
diff --git a/spec/services/clusters/parse_cluster_applications_artifact_service_spec.rb b/spec/services/clusters/parse_cluster_applications_artifact_service_spec.rb
index bb0b107eba6..3b155d95345 100644
--- a/spec/services/clusters/parse_cluster_applications_artifact_service_spec.rb
+++ b/spec/services/clusters/parse_cluster_applications_artifact_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::ParseClusterApplicationsArtifactService do
+RSpec.describe Clusters::ParseClusterApplicationsArtifactService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
@@ -120,90 +120,9 @@ describe Clusters::ParseClusterApplicationsArtifactService do
end
end
- context 'release is missing' do
- let(:fixture) { 'spec/fixtures/helm/helm_list_v2_prometheus_missing.json.gz' }
- let(:file) { fixture_file_upload(Rails.root.join(fixture)) }
- let(:artifact) { create(:ci_job_artifact, :cluster_applications, job: job, file: file) }
-
- context 'application does not exist' do
- it 'does not create or destroy an application' do
- expect do
- described_class.new(job, user).execute(artifact)
- end.not_to change(Clusters::Applications::Prometheus, :count)
- end
- end
-
- context 'application exists' do
- before do
- create(:clusters_applications_prometheus, :installed, cluster: cluster)
- end
-
- it 'marks the application as uninstalled' do
- described_class.new(job, user).execute(artifact)
-
- cluster.application_prometheus.reload
- expect(cluster.application_prometheus).to be_uninstalled
- end
- end
- end
-
- context 'release is deployed' do
- let(:fixture) { 'spec/fixtures/helm/helm_list_v2_prometheus_deployed.json.gz' }
- let(:file) { fixture_file_upload(Rails.root.join(fixture)) }
- let(:artifact) { create(:ci_job_artifact, :cluster_applications, job: job, file: file) }
-
- context 'application does not exist' do
- it 'creates an application and marks it as installed' do
- expect do
- described_class.new(job, user).execute(artifact)
- end.to change(Clusters::Applications::Prometheus, :count)
-
- expect(cluster.application_prometheus).to be_persisted
- expect(cluster.application_prometheus).to be_installed
- end
- end
-
- context 'application exists' do
- before do
- create(:clusters_applications_prometheus, :errored, cluster: cluster)
- end
-
- it 'marks the application as installed' do
- described_class.new(job, user).execute(artifact)
-
- expect(cluster.application_prometheus).to be_installed
- end
- end
- end
-
- context 'release is failed' do
- let(:fixture) { 'spec/fixtures/helm/helm_list_v2_prometheus_failed.json.gz' }
- let(:file) { fixture_file_upload(Rails.root.join(fixture)) }
- let(:artifact) { create(:ci_job_artifact, :cluster_applications, job: job, file: file) }
-
- context 'application does not exist' do
- it 'creates an application and marks it as errored' do
- expect do
- described_class.new(job, user).execute(artifact)
- end.to change(Clusters::Applications::Prometheus, :count)
-
- expect(cluster.application_prometheus).to be_persisted
- expect(cluster.application_prometheus).to be_errored
- expect(cluster.application_prometheus.status_reason).to eq('Helm release failed to install')
- end
- end
-
- context 'application exists' do
- before do
- create(:clusters_applications_prometheus, :installed, cluster: cluster)
- end
-
- it 'marks the application as errored' do
- described_class.new(job, user).execute(artifact)
-
- expect(cluster.application_prometheus).to be_errored
- expect(cluster.application_prometheus.status_reason).to eq('Helm release failed to install')
- end
+ Clusters::ParseClusterApplicationsArtifactService::RELEASE_NAMES.each do |release_name|
+ context release_name do
+ include_examples 'parse cluster applications artifact', release_name
end
end
end
diff --git a/spec/services/clusters/update_service_spec.rb b/spec/services/clusters/update_service_spec.rb
index 5a7726eded8..e496ccd5c23 100644
--- a/spec/services/clusters/update_service_spec.rb
+++ b/spec/services/clusters/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::UpdateService do
+RSpec.describe Clusters::UpdateService do
include KubernetesHelpers
describe '#execute' do
diff --git a/spec/services/cohorts_service_spec.rb b/spec/services/cohorts_service_spec.rb
index b2f82a1153c..dce8d4f80f2 100644
--- a/spec/services/cohorts_service_spec.rb
+++ b/spec/services/cohorts_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CohortsService do
+RSpec.describe CohortsService do
describe '#execute' do
def month_start(months_ago)
months_ago.months.ago.beginning_of_month.to_date
diff --git a/spec/services/commits/cherry_pick_service_spec.rb b/spec/services/commits/cherry_pick_service_spec.rb
index 3b797b8ac02..8fad5164b77 100644
--- a/spec/services/commits/cherry_pick_service_spec.rb
+++ b/spec/services/commits/cherry_pick_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Commits::CherryPickService do
+RSpec.describe Commits::CherryPickService do
let(:project) { create(:project, :repository) }
# * ddd0f15ae83993f5cb66a927a28673882e99100b (HEAD -> master, origin/master, origin/HEAD) Merge branch 'po-fix-test-en
# |\
diff --git a/spec/services/commits/commit_patch_service_spec.rb b/spec/services/commits/commit_patch_service_spec.rb
index f4fcec2fbc2..c8c0cbe23b2 100644
--- a/spec/services/commits/commit_patch_service_spec.rb
+++ b/spec/services/commits/commit_patch_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Commits::CommitPatchService do
+RSpec.describe Commits::CommitPatchService do
describe '#execute' do
let(:patches) do
patches_folder = Rails.root.join('spec/fixtures/patchfiles')
diff --git a/spec/services/commits/tag_service_spec.rb b/spec/services/commits/tag_service_spec.rb
index 82377a8dace..dd742ebe469 100644
--- a/spec/services/commits/tag_service_spec.rb
+++ b/spec/services/commits/tag_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Commits::TagService do
+RSpec.describe Commits::TagService do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
diff --git a/spec/services/compare_service_spec.rb b/spec/services/compare_service_spec.rb
index f6d8eb348d0..e96a7f2f4f4 100644
--- a/spec/services/compare_service_spec.rb
+++ b/spec/services/compare_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CompareService do
+RSpec.describe CompareService do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:service) { described_class.new(project, 'feature') }
diff --git a/spec/services/concerns/exclusive_lease_guard_spec.rb b/spec/services/concerns/exclusive_lease_guard_spec.rb
index a38facc7520..d54ba6abadd 100644
--- a/spec/services/concerns/exclusive_lease_guard_spec.rb
+++ b/spec/services/concerns/exclusive_lease_guard_spec.rb
@@ -51,7 +51,7 @@ RSpec.describe ExclusiveLeaseGuard, :clean_gitlab_redis_shared_state do
it 'does not call internal_method but logs error', :aggregate_failures do
expect(subject).not_to receive(:internal_method)
- expect(Gitlab::AppLogger).to receive(:error).with('Cannot obtain an exclusive lease. There must be another instance already in execution.')
+ expect(Gitlab::AppLogger).to receive(:error).with("Cannot obtain an exclusive lease for #{subject.class.name}. There must be another instance already in execution.")
subject.call
end
diff --git a/spec/services/concerns/merge_requests/assigns_merge_params_spec.rb b/spec/services/concerns/merge_requests/assigns_merge_params_spec.rb
index 9cf7f354191..5b1e8fca31b 100644
--- a/spec/services/concerns/merge_requests/assigns_merge_params_spec.rb
+++ b/spec/services/concerns/merge_requests/assigns_merge_params_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::AssignsMergeParams do
+RSpec.describe MergeRequests::AssignsMergeParams do
it 'raises an error when used from an instance that does not respond to #current_user' do
define_class = -> { Class.new { include MergeRequests::AssignsMergeParams }.new }
diff --git a/spec/services/container_expiration_policies/update_service_spec.rb b/spec/services/container_expiration_policies/update_service_spec.rb
index ec178f3830f..d4b6715ae86 100644
--- a/spec/services/container_expiration_policies/update_service_spec.rb
+++ b/spec/services/container_expiration_policies/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerExpirationPolicies::UpdateService do
+RSpec.describe ContainerExpirationPolicies::UpdateService do
using RSpec::Parameterized::TableSyntax
let_it_be(:project, reload: true) { create(:project) }
diff --git a/spec/services/container_expiration_policy_service_spec.rb b/spec/services/container_expiration_policy_service_spec.rb
index 97715b990ef..dfce51d73ad 100644
--- a/spec/services/container_expiration_policy_service_spec.rb
+++ b/spec/services/container_expiration_policy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerExpirationPolicyService do
+RSpec.describe ContainerExpirationPolicyService do
let_it_be(:user) { create(:user) }
let_it_be(:container_expiration_policy) { create(:container_expiration_policy, :runnable) }
let(:project) { container_expiration_policy.project }
diff --git a/spec/services/deploy_keys/collect_keys_service_spec.rb b/spec/services/deploy_keys/collect_keys_service_spec.rb
new file mode 100644
index 00000000000..3442e5e456a
--- /dev/null
+++ b/spec/services/deploy_keys/collect_keys_service_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe DeployKeys::CollectKeysService do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :private) }
+
+ subject { DeployKeys::CollectKeysService.new(project, user) }
+
+ before do
+ project&.add_developer(user)
+ end
+
+ context 'when no project is passed in' do
+ let(:project) { nil }
+
+ it 'returns an empty Array' do
+ expect(subject.execute).to be_empty
+ end
+ end
+
+ context 'when no user is passed in' do
+ let(:user) { nil }
+
+ it 'returns an empty Array' do
+ expect(subject.execute).to be_empty
+ end
+ end
+
+ context 'when a project is passed in' do
+ let_it_be(:deploy_keys_project) { create(:deploy_keys_project, :write_access, project: project) }
+ let_it_be(:deploy_key) { deploy_keys_project.deploy_key }
+
+ it 'only returns deploy keys with write access' do
+ create(:deploy_keys_project, project: project)
+
+ expect(subject.execute).to contain_exactly(deploy_key)
+ end
+
+ it 'returns deploy keys only for this project' do
+ other_project = create(:project)
+ create(:deploy_keys_project, :write_access, project: other_project)
+
+ expect(subject.execute).to contain_exactly(deploy_key)
+ end
+ end
+
+ context 'when the user cannot read the project' do
+ before do
+ project.members.delete_all
+ end
+
+ it 'returns an empty Array' do
+ expect(subject.execute).to be_empty
+ end
+ end
+end
diff --git a/spec/services/deploy_keys/create_service_spec.rb b/spec/services/deploy_keys/create_service_spec.rb
index a55f1561194..2e3318236f5 100644
--- a/spec/services/deploy_keys/create_service_spec.rb
+++ b/spec/services/deploy_keys/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeployKeys::CreateService do
+RSpec.describe DeployKeys::CreateService do
let(:user) { create(:user) }
let(:params) { attributes_for(:deploy_key) }
diff --git a/spec/services/deployments/after_create_service_spec.rb b/spec/services/deployments/after_create_service_spec.rb
index 5a69ffd8b9c..3287eed03b7 100644
--- a/spec/services/deployments/after_create_service_spec.rb
+++ b/spec/services/deployments/after_create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Deployments::AfterCreateService do
+RSpec.describe Deployments::AfterCreateService do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:options) { { name: 'production' } }
diff --git a/spec/services/deployments/create_service_spec.rb b/spec/services/deployments/create_service_spec.rb
index 6ab1f8635f7..d1f977c28d3 100644
--- a/spec/services/deployments/create_service_spec.rb
+++ b/spec/services/deployments/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Deployments::CreateService do
+RSpec.describe Deployments::CreateService do
let(:user) { create(:user) }
describe '#execute' do
diff --git a/spec/services/deployments/link_merge_requests_service_spec.rb b/spec/services/deployments/link_merge_requests_service_spec.rb
index aa2cecbf897..e2ac2273b8c 100644
--- a/spec/services/deployments/link_merge_requests_service_spec.rb
+++ b/spec/services/deployments/link_merge_requests_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Deployments::LinkMergeRequestsService do
+RSpec.describe Deployments::LinkMergeRequestsService do
let(:project) { create(:project, :repository) }
# * ddd0f15 Merge branch 'po-fix-test-env-path' into 'master'
diff --git a/spec/services/deployments/older_deployments_drop_service_spec.rb b/spec/services/deployments/older_deployments_drop_service_spec.rb
index 4c9bcf90533..6152a95cc3c 100644
--- a/spec/services/deployments/older_deployments_drop_service_spec.rb
+++ b/spec/services/deployments/older_deployments_drop_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Deployments::OlderDeploymentsDropService do
+RSpec.describe Deployments::OlderDeploymentsDropService do
let(:environment) { create(:environment) }
let(:deployment) { create(:deployment, environment: environment) }
let(:service) { described_class.new(deployment) }
diff --git a/spec/services/deployments/update_service_spec.rb b/spec/services/deployments/update_service_spec.rb
index 471e90de467..16b24d0dee8 100644
--- a/spec/services/deployments/update_service_spec.rb
+++ b/spec/services/deployments/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Deployments::UpdateService do
+RSpec.describe Deployments::UpdateService do
let(:deploy) { create(:deployment) }
describe '#execute' do
diff --git a/spec/services/design_management/delete_designs_service_spec.rb b/spec/services/design_management/delete_designs_service_spec.rb
index bf5d6b443e6..ace63b6e59c 100644
--- a/spec/services/design_management/delete_designs_service_spec.rb
+++ b/spec/services/design_management/delete_designs_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe DesignManagement::DeleteDesignsService do
+RSpec.describe DesignManagement::DeleteDesignsService do
include DesignManagementTestHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/services/design_management/design_user_notes_count_service_spec.rb b/spec/services/design_management/design_user_notes_count_service_spec.rb
index 62211a4dd0f..37806d3461c 100644
--- a/spec/services/design_management/design_user_notes_count_service_spec.rb
+++ b/spec/services/design_management/design_user_notes_count_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DesignManagement::DesignUserNotesCountService, :use_clean_rails_memory_store_caching do
+RSpec.describe DesignManagement::DesignUserNotesCountService, :use_clean_rails_memory_store_caching do
let_it_be(:design) { create(:design, :with_file) }
subject { described_class.new(design) }
diff --git a/spec/services/design_management/generate_image_versions_service_spec.rb b/spec/services/design_management/generate_image_versions_service_spec.rb
index cd021c8d7d3..631eec97e5a 100644
--- a/spec/services/design_management/generate_image_versions_service_spec.rb
+++ b/spec/services/design_management/generate_image_versions_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DesignManagement::GenerateImageVersionsService do
+RSpec.describe DesignManagement::GenerateImageVersionsService do
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:version) { create(:design, :with_lfs_file, issue: issue).versions.first }
diff --git a/spec/services/design_management/save_designs_service_spec.rb b/spec/services/design_management/save_designs_service_spec.rb
index 3be3ac9daca..24639632566 100644
--- a/spec/services/design_management/save_designs_service_spec.rb
+++ b/spec/services/design_management/save_designs_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe DesignManagement::SaveDesignsService do
+RSpec.describe DesignManagement::SaveDesignsService do
include DesignManagementTestHelpers
include ConcurrentHelpers
diff --git a/spec/services/discussions/capture_diff_note_position_service_spec.rb b/spec/services/discussions/capture_diff_note_position_service_spec.rb
index bc71e170e92..0913ddd8ef2 100644
--- a/spec/services/discussions/capture_diff_note_position_service_spec.rb
+++ b/spec/services/discussions/capture_diff_note_position_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Discussions::CaptureDiffNotePositionService do
+RSpec.describe Discussions::CaptureDiffNotePositionService do
subject { described_class.new(note.noteable, paths) }
context 'image note on diff' do
diff --git a/spec/services/discussions/capture_diff_note_positions_service_spec.rb b/spec/services/discussions/capture_diff_note_positions_service_spec.rb
index 7b1e207f3eb..dede5a4c354 100644
--- a/spec/services/discussions/capture_diff_note_positions_service_spec.rb
+++ b/spec/services/discussions/capture_diff_note_positions_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Discussions::CaptureDiffNotePositionsService do
+RSpec.describe Discussions::CaptureDiffNotePositionsService do
context 'when merge request has a discussion' do
let(:source_branch) { 'compare-with-merge-head-source' }
let(:target_branch) { 'compare-with-merge-head-target' }
diff --git a/spec/services/discussions/resolve_service_spec.rb b/spec/services/discussions/resolve_service_spec.rb
index 7461934b455..5ff0d535b46 100644
--- a/spec/services/discussions/resolve_service_spec.rb
+++ b/spec/services/discussions/resolve_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Discussions::ResolveService do
+RSpec.describe Discussions::ResolveService do
describe '#execute' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user, developer_projects: [project]) }
diff --git a/spec/services/discussions/update_diff_position_service_spec.rb b/spec/services/discussions/update_diff_position_service_spec.rb
index 60ec83e9062..85020e95c83 100644
--- a/spec/services/discussions/update_diff_position_service_spec.rb
+++ b/spec/services/discussions/update_diff_position_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Discussions::UpdateDiffPositionService do
+RSpec.describe Discussions::UpdateDiffPositionService do
let(:project) { create(:project, :repository) }
let(:current_user) { project.owner }
let(:create_commit) { project.commit("913c66a37b4a45b9769037c55c2d238bd0942d2e") }
diff --git a/spec/services/draft_notes/create_service_spec.rb b/spec/services/draft_notes/create_service_spec.rb
index 8f244ed386b..f0291067777 100644
--- a/spec/services/draft_notes/create_service_spec.rb
+++ b/spec/services/draft_notes/create_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe DraftNotes::CreateService do
+RSpec.describe DraftNotes::CreateService do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.target_project }
let(:user) { merge_request.author }
diff --git a/spec/services/draft_notes/destroy_service_spec.rb b/spec/services/draft_notes/destroy_service_spec.rb
index d0bf88dcdbe..f725f08f3c7 100644
--- a/spec/services/draft_notes/destroy_service_spec.rb
+++ b/spec/services/draft_notes/destroy_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe DraftNotes::DestroyService do
+RSpec.describe DraftNotes::DestroyService do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.target_project }
let(:user) { merge_request.author }
diff --git a/spec/services/draft_notes/publish_service_spec.rb b/spec/services/draft_notes/publish_service_spec.rb
index 4ebae2f9aa2..ae0c8113904 100644
--- a/spec/services/draft_notes/publish_service_spec.rb
+++ b/spec/services/draft_notes/publish_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe DraftNotes::PublishService do
+RSpec.describe DraftNotes::PublishService do
include RepoHelpers
let(:merge_request) { create(:merge_request) }
@@ -237,7 +237,8 @@ describe DraftNotes::PublishService do
it 'resolves the thread' do
publish(draft: draft_note)
- expect(note.discussion.resolved?).to be true
+ # discussion is memoized and reload doesn't clear the memoization
+ expect(Note.find(note.id).discussion.resolved?).to be true
end
it 'sends notifications if all threads are resolved' do
diff --git a/spec/services/emails/confirm_service_spec.rb b/spec/services/emails/confirm_service_spec.rb
index 973d2731b2f..935a673f548 100644
--- a/spec/services/emails/confirm_service_spec.rb
+++ b/spec/services/emails/confirm_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Emails::ConfirmService do
+RSpec.describe Emails::ConfirmService do
let(:user) { create(:user) }
subject(:service) { described_class.new(user) }
diff --git a/spec/services/emails/create_service_spec.rb b/spec/services/emails/create_service_spec.rb
index 23c2f53dca0..1396a1fce30 100644
--- a/spec/services/emails/create_service_spec.rb
+++ b/spec/services/emails/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Emails::CreateService do
+RSpec.describe Emails::CreateService do
let(:user) { create(:user) }
let(:opts) { { email: 'new@email.com', user: user } }
diff --git a/spec/services/emails/destroy_service_spec.rb b/spec/services/emails/destroy_service_spec.rb
index 9e14a13aa4f..f8407be41e7 100644
--- a/spec/services/emails/destroy_service_spec.rb
+++ b/spec/services/emails/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Emails::DestroyService do
+RSpec.describe Emails::DestroyService do
let!(:user) { create(:user) }
let!(:email) { create(:email, user: user) }
diff --git a/spec/services/environments/auto_stop_service_spec.rb b/spec/services/environments/auto_stop_service_spec.rb
index b34d15889d3..8e56c7e642c 100644
--- a/spec/services/environments/auto_stop_service_spec.rb
+++ b/spec/services/environments/auto_stop_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Environments::AutoStopService, :clean_gitlab_redis_shared_state do
+RSpec.describe Environments::AutoStopService, :clean_gitlab_redis_shared_state do
include CreateEnvironmentsHelpers
include ExclusiveLeaseHelpers
diff --git a/spec/services/environments/reset_auto_stop_service_spec.rb b/spec/services/environments/reset_auto_stop_service_spec.rb
index 53a20dd906e..cab1bf2cc26 100644
--- a/spec/services/environments/reset_auto_stop_service_spec.rb
+++ b/spec/services/environments/reset_auto_stop_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Environments::ResetAutoStopService do
+RSpec.describe Environments::ResetAutoStopService do
let_it_be(:project) { create(:project) }
let_it_be(:developer) { create(:user).tap { |user| project.add_developer(user) } }
let_it_be(:reporter) { create(:user).tap { |user| project.add_reporter(user) } }
diff --git a/spec/services/error_tracking/base_service_spec.rb b/spec/services/error_tracking/base_service_spec.rb
index 68deb2e2a73..ffbda37d417 100644
--- a/spec/services/error_tracking/base_service_spec.rb
+++ b/spec/services/error_tracking/base_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ErrorTracking::BaseService do
+RSpec.describe ErrorTracking::BaseService do
describe '#compose_response' do
let(:project) { double('project') }
let(:user) { double('user') }
diff --git a/spec/services/error_tracking/issue_details_service_spec.rb b/spec/services/error_tracking/issue_details_service_spec.rb
index 66b8988f8e3..1954640a512 100644
--- a/spec/services/error_tracking/issue_details_service_spec.rb
+++ b/spec/services/error_tracking/issue_details_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ErrorTracking::IssueDetailsService do
+RSpec.describe ErrorTracking::IssueDetailsService do
include_context 'sentry error tracking context'
subject { described_class.new(project, user, params) }
diff --git a/spec/services/error_tracking/issue_latest_event_service_spec.rb b/spec/services/error_tracking/issue_latest_event_service_spec.rb
index 078d7511850..b7560762ae4 100644
--- a/spec/services/error_tracking/issue_latest_event_service_spec.rb
+++ b/spec/services/error_tracking/issue_latest_event_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ErrorTracking::IssueLatestEventService do
+RSpec.describe ErrorTracking::IssueLatestEventService do
include_context 'sentry error tracking context'
subject { described_class.new(project, user) }
diff --git a/spec/services/error_tracking/issue_update_service_spec.rb b/spec/services/error_tracking/issue_update_service_spec.rb
index a13d42ec141..9ed24038ed8 100644
--- a/spec/services/error_tracking/issue_update_service_spec.rb
+++ b/spec/services/error_tracking/issue_update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ErrorTracking::IssueUpdateService do
+RSpec.describe ErrorTracking::IssueUpdateService do
include_context 'sentry error tracking context'
let(:arguments) { { issue_id: non_existing_record_id, status: 'resolved' } }
diff --git a/spec/services/error_tracking/list_issues_service_spec.rb b/spec/services/error_tracking/list_issues_service_spec.rb
index 5f6e071e10d..518f2a80826 100644
--- a/spec/services/error_tracking/list_issues_service_spec.rb
+++ b/spec/services/error_tracking/list_issues_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ErrorTracking::ListIssuesService do
+RSpec.describe ErrorTracking::ListIssuesService do
include_context 'sentry error tracking context'
let(:params) { { search_term: 'something', sort: 'last_seen', cursor: 'some-cursor' } }
diff --git a/spec/services/error_tracking/list_projects_service_spec.rb b/spec/services/error_tracking/list_projects_service_spec.rb
index 565610c64ac..8bc632349fa 100644
--- a/spec/services/error_tracking/list_projects_service_spec.rb
+++ b/spec/services/error_tracking/list_projects_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ErrorTracking::ListProjectsService do
+RSpec.describe ErrorTracking::ListProjectsService do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project) }
diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb
index 73c089334ed..d10ed7d6640 100644
--- a/spec/services/event_create_service_spec.rb
+++ b/spec/services/event_create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EventCreateService do
+RSpec.describe EventCreateService do
let(:service) { described_class.new }
let_it_be(:user, reload: true) { create :user }
@@ -16,7 +16,6 @@ describe EventCreateService do
it "creates new event" do
expect { service.open_issue(issue, issue.author) }.to change { Event.count }
- expect { service.open_issue(issue, issue.author) }.to change { ResourceStateEvent.count }
end
end
@@ -27,7 +26,6 @@ describe EventCreateService do
it "creates new event" do
expect { service.close_issue(issue, issue.author) }.to change { Event.count }
- expect { service.close_issue(issue, issue.author) }.to change { ResourceStateEvent.count }
end
end
@@ -38,7 +36,6 @@ describe EventCreateService do
it "creates new event" do
expect { service.reopen_issue(issue, issue.author) }.to change { Event.count }
- expect { service.reopen_issue(issue, issue.author) }.to change { ResourceStateEvent.count }
end
end
end
@@ -51,7 +48,6 @@ describe EventCreateService do
it "creates new event" do
expect { service.open_mr(merge_request, merge_request.author) }.to change { Event.count }
- expect { service.open_mr(merge_request, merge_request.author) }.to change { ResourceStateEvent.count }
end
end
@@ -62,7 +58,6 @@ describe EventCreateService do
it "creates new event" do
expect { service.close_mr(merge_request, merge_request.author) }.to change { Event.count }
- expect { service.close_mr(merge_request, merge_request.author) }.to change { ResourceStateEvent.count }
end
end
@@ -73,7 +68,6 @@ describe EventCreateService do
it "creates new event" do
expect { service.merge_mr(merge_request, merge_request.author) }.to change { Event.count }
- expect { service.merge_mr(merge_request, merge_request.author) }.to change { ResourceStateEvent.count }
end
end
@@ -84,7 +78,18 @@ describe EventCreateService do
it "creates new event" do
expect { service.reopen_mr(merge_request, merge_request.author) }.to change { Event.count }
- expect { service.reopen_mr(merge_request, merge_request.author) }.to change { ResourceStateEvent.count }
+ end
+ end
+
+ describe '#approve_mr' do
+ let(:merge_request) { create(:merge_request) }
+
+ it { expect(service.approve_mr(merge_request, user)).to be_truthy }
+
+ it 'creates new event' do
+ service.approve_mr(merge_request, user)
+
+ change { Event.approved_action.where(target: merge_request).count }.by(1)
end
end
end
@@ -161,7 +166,7 @@ describe EventCreateService do
end
end
- describe '#wiki_event' do
+ describe '#wiki_event', :clean_gitlab_redis_shared_state do
let_it_be(:user) { create(:user) }
let_it_be(:wiki_page) { create(:wiki_page) }
let_it_be(:meta) { create(:wiki_page_meta, :for_wiki_page, wiki_page: wiki_page) }
@@ -181,6 +186,16 @@ describe EventCreateService do
)
end
+ it 'records the event in the event counter' do
+ stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
+ counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
+ tracking_params = { event_action: counter_class::WIKI_ACTION, date_from: Date.yesterday, date_to: Date.today }
+
+ expect { event }
+ .to change { counter_class.count_unique_events(tracking_params) }
+ .from(0).to(1)
+ end
+
it 'is idempotent', :aggregate_failures do
expect { event }.to change(Event, :count).by(1)
duplicate = nil
@@ -188,16 +203,6 @@ describe EventCreateService do
expect(duplicate).to eq(event)
end
-
- context 'the feature is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it 'does not create the event' do
- expect { event }.not_to change(Event, :count)
- end
- end
end
end
@@ -229,6 +234,16 @@ describe EventCreateService do
subject { service.push(project, user, push_data) }
it_behaves_like 'service for creating a push event', PushEventPayloadService
+
+ it 'records the event in the event counter' do
+ stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
+ counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
+ tracking_params = { event_action: counter_class::PUSH_ACTION, date_from: Date.yesterday, date_to: Date.today }
+
+ expect { subject }
+ .to change { counter_class.count_unique_events(tracking_params) }
+ .from(0).to(1)
+ end
end
describe '#bulk_push', :clean_gitlab_redis_shared_state do
@@ -243,6 +258,16 @@ describe EventCreateService do
subject { service.bulk_push(project, user, push_data) }
it_behaves_like 'service for creating a push event', BulkPushEventPayloadService
+
+ it 'records the event in the event counter' do
+ stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
+ counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
+ tracking_params = { event_action: counter_class::PUSH_ACTION, date_from: Date.yesterday, date_to: Date.today }
+
+ expect { subject }
+ .to change { counter_class.count_unique_events(tracking_params) }
+ .from(0).to(1)
+ end
end
describe 'Project' do
@@ -261,31 +286,10 @@ describe EventCreateService do
end
end
- describe 'design events' do
+ describe 'design events', :clean_gitlab_redis_shared_state do
let_it_be(:design) { create(:design, project: project) }
let_it_be(:author) { user }
- shared_examples 'feature flag gated multiple event creation' do
- context 'the feature flag is off' do
- before do
- stub_feature_flags(design_activity_events: false)
- end
-
- specify { expect(result).to be_empty }
- specify { expect { result }.not_to change { Event.count } }
- specify { expect { result }.not_to exceed_query_limit(0) }
- end
-
- context 'the feature flag is enabled for a single project' do
- before do
- stub_feature_flags(design_activity_events: project)
- end
-
- specify { expect(result).not_to be_empty }
- specify { expect { result }.to change { Event.count }.by(1) }
- end
- end
-
describe '#save_designs' do
let_it_be(:updated) { create_list(:design, 5) }
let_it_be(:created) { create_list(:design, 3) }
@@ -310,8 +314,14 @@ describe EventCreateService do
expect(events.map(&:design)).to match_array(updated)
end
- it_behaves_like 'feature flag gated multiple event creation' do
- let(:project) { created.first.project }
+ it 'records the event in the event counter' do
+ stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
+ counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
+ tracking_params = { event_action: counter_class::DESIGN_ACTION, date_from: Date.yesterday, date_to: Date.today }
+
+ expect { result }
+ .to change { counter_class.count_unique_events(tracking_params) }
+ .from(0).to(1)
end
end
@@ -332,8 +342,14 @@ describe EventCreateService do
expect(events.map(&:design)).to match_array(designs)
end
- it_behaves_like 'feature flag gated multiple event creation' do
- let(:project) { designs.first.project }
+ it 'records the event in the event counter' do
+ stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
+ counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
+ tracking_params = { event_action: counter_class::DESIGN_ACTION, date_from: Date.yesterday, date_to: Date.today }
+
+ expect { result }
+ .to change { counter_class.count_unique_events(tracking_params) }
+ .from(0).to(1)
end
end
end
diff --git a/spec/services/events/render_service_spec.rb b/spec/services/events/render_service_spec.rb
index a623a05a56d..24a3b9abe14 100644
--- a/spec/services/events/render_service_spec.rb
+++ b/spec/services/events/render_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Events::RenderService do
+RSpec.describe Events::RenderService do
describe '#execute' do
let!(:note) { build(:note) }
let!(:event) { build(:event, target: note, project: note.project) }
diff --git a/spec/services/files/create_service_spec.rb b/spec/services/files/create_service_spec.rb
index 195f56a2909..3b3dbd1fcfe 100644
--- a/spec/services/files/create_service_spec.rb
+++ b/spec/services/files/create_service_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Files::CreateService do
+RSpec.describe Files::CreateService do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:user) { create(:user, :commit_email) }
diff --git a/spec/services/files/delete_service_spec.rb b/spec/services/files/delete_service_spec.rb
index b849def06fc..17e4645fde6 100644
--- a/spec/services/files/delete_service_spec.rb
+++ b/spec/services/files/delete_service_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Files::DeleteService do
+RSpec.describe Files::DeleteService do
subject { described_class.new(project, user, commit_params) }
let(:project) { create(:project, :repository) }
diff --git a/spec/services/files/multi_service_spec.rb b/spec/services/files/multi_service_spec.rb
index 0f51c72019e..6a5c7d2749d 100644
--- a/spec/services/files/multi_service_spec.rb
+++ b/spec/services/files/multi_service_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Files::MultiService do
+RSpec.describe Files::MultiService do
subject { described_class.new(project, user, commit_params) }
let(:project) { create(:project, :repository) }
diff --git a/spec/services/files/update_service_spec.rb b/spec/services/files/update_service_spec.rb
index 37869b176ef..84d78b4c2bc 100644
--- a/spec/services/files/update_service_spec.rb
+++ b/spec/services/files/update_service_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Files::UpdateService do
+RSpec.describe Files::UpdateService do
subject { described_class.new(project, user, commit_params) }
let(:project) { create(:project, :repository) }
diff --git a/spec/services/git/base_hooks_service_spec.rb b/spec/services/git/base_hooks_service_spec.rb
index 07ce560bd88..661c77b56bb 100644
--- a/spec/services/git/base_hooks_service_spec.rb
+++ b/spec/services/git/base_hooks_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Git::BaseHooksService do
+RSpec.describe Git::BaseHooksService do
include RepoHelpers
include GitHelpers
diff --git a/spec/services/git/branch_hooks_service_spec.rb b/spec/services/git/branch_hooks_service_spec.rb
index 908b9772c40..7f22af8bfc6 100644
--- a/spec/services/git/branch_hooks_service_spec.rb
+++ b/spec/services/git/branch_hooks_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Git::BranchHooksService do
+RSpec.describe Git::BranchHooksService do
include RepoHelpers
include ProjectForksHelper
diff --git a/spec/services/git/branch_push_service_spec.rb b/spec/services/git/branch_push_service_spec.rb
index 6ecc1a62ff3..6ccf2d03e4a 100644
--- a/spec/services/git/branch_push_service_spec.rb
+++ b/spec/services/git/branch_push_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Git::BranchPushService, services: true do
+RSpec.describe Git::BranchPushService, services: true do
include RepoHelpers
let_it_be(:user) { create(:user) }
@@ -635,6 +635,37 @@ describe Git::BranchPushService, services: true do
end
end
+ describe 'artifacts' do
+ context 'create branch' do
+ let(:oldrev) { blankrev }
+
+ it 'does nothing' do
+ expect(::Ci::RefDeleteUnlockArtifactsWorker).not_to receive(:perform_async)
+
+ execute_service(project, user, oldrev: oldrev, newrev: newrev, ref: ref)
+ end
+ end
+
+ context 'update branch' do
+ it 'does nothing' do
+ expect(::Ci::RefDeleteUnlockArtifactsWorker).not_to receive(:perform_async)
+
+ execute_service(project, user, oldrev: oldrev, newrev: newrev, ref: ref)
+ end
+ end
+
+ context 'delete branch' do
+ let(:newrev) { blankrev }
+
+ it 'unlocks artifacts' do
+ expect(::Ci::RefDeleteUnlockArtifactsWorker)
+ .to receive(:perform_async).with(project.id, user.id, "refs/heads/#{branch}")
+
+ execute_service(project, user, oldrev: oldrev, newrev: newrev, ref: ref)
+ end
+ end
+ end
+
describe 'Hooks' do
context 'run on a branch' do
it 'delegates to Git::BranchHooksService' do
diff --git a/spec/services/git/process_ref_changes_service_spec.rb b/spec/services/git/process_ref_changes_service_spec.rb
index 924e913a9ec..c2fb40a0ed0 100644
--- a/spec/services/git/process_ref_changes_service_spec.rb
+++ b/spec/services/git/process_ref_changes_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Git::ProcessRefChangesService do
+RSpec.describe Git::ProcessRefChangesService do
let(:project) { create(:project, :repository) }
let(:user) { project.owner }
let(:params) { { changes: git_changes } }
diff --git a/spec/services/git/tag_hooks_service_spec.rb b/spec/services/git/tag_hooks_service_spec.rb
index 094ccd8c9f0..4443c46a414 100644
--- a/spec/services/git/tag_hooks_service_spec.rb
+++ b/spec/services/git/tag_hooks_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Git::TagHooksService, :service do
+RSpec.describe Git::TagHooksService, :service do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
diff --git a/spec/services/git/tag_push_service_spec.rb b/spec/services/git/tag_push_service_spec.rb
index 9688041c08c..87dbf79a245 100644
--- a/spec/services/git/tag_push_service_spec.rb
+++ b/spec/services/git/tag_push_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Git::TagPushService do
+RSpec.describe Git::TagPushService do
include RepoHelpers
include GitHelpers
@@ -10,9 +10,11 @@ describe Git::TagPushService do
let(:project) { create(:project, :repository) }
let(:service) { described_class.new(project, user, change: { oldrev: oldrev, newrev: newrev, ref: ref }) }
- let(:oldrev) { Gitlab::Git::BLANK_SHA }
+ let(:blankrev) { Gitlab::Git::BLANK_SHA }
+ let(:oldrev) { blankrev }
let(:newrev) { "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" } # gitlab-test: git rev-parse refs/tags/v1.1.0
- let(:ref) { 'refs/tags/v1.1.0' }
+ let(:tag) { 'v1.1.0' }
+ let(:ref) { "refs/tags/#{tag}" }
describe "Push tags" do
subject do
@@ -58,4 +60,35 @@ describe Git::TagPushService do
end
end
end
+
+ describe 'artifacts' do
+ context 'create tag' do
+ let(:oldrev) { blankrev }
+
+ it 'does nothing' do
+ expect(::Ci::RefDeleteUnlockArtifactsWorker).not_to receive(:perform_async)
+
+ service.execute
+ end
+ end
+
+ context 'update tag' do
+ it 'does nothing' do
+ expect(::Ci::RefDeleteUnlockArtifactsWorker).not_to receive(:perform_async)
+
+ service.execute
+ end
+ end
+
+ context 'delete tag' do
+ let(:newrev) { blankrev }
+
+ it 'unlocks artifacts' do
+ expect(::Ci::RefDeleteUnlockArtifactsWorker)
+ .to receive(:perform_async).with(project.id, user.id, "refs/tags/#{tag}")
+
+ service.execute
+ end
+ end
+ end
end
diff --git a/spec/services/git/wiki_push_service/change_spec.rb b/spec/services/git/wiki_push_service/change_spec.rb
index 4da3f0fc738..3616bf62b20 100644
--- a/spec/services/git/wiki_push_service/change_spec.rb
+++ b/spec/services/git/wiki_push_service/change_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Git::WikiPushService::Change do
+RSpec.describe Git::WikiPushService::Change do
subject { described_class.new(project_wiki, change, raw_change) }
let(:project_wiki) { double('ProjectWiki') }
diff --git a/spec/services/git/wiki_push_service_spec.rb b/spec/services/git/wiki_push_service_spec.rb
index b2234c81c24..f338b7a5709 100644
--- a/spec/services/git/wiki_push_service_spec.rb
+++ b/spec/services/git/wiki_push_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Git::WikiPushService, services: true do
+RSpec.describe Git::WikiPushService, services: true do
include RepoHelpers
let_it_be(:key_id) { create(:key, user: current_user).shell_id }
@@ -247,14 +247,6 @@ describe Git::WikiPushService, services: true do
end
end
- context 'the wiki_events feature is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it_behaves_like 'a no-op push'
- end
-
context 'the wiki_events_on_git_push feature is disabled' do
before do
stub_feature_flags(wiki_events_on_git_push: false)
diff --git a/spec/services/gpg_keys/create_service_spec.rb b/spec/services/gpg_keys/create_service_spec.rb
index 8dfc9f19439..9ac56355b4b 100644
--- a/spec/services/gpg_keys/create_service_spec.rb
+++ b/spec/services/gpg_keys/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GpgKeys::CreateService do
+RSpec.describe GpgKeys::CreateService do
let(:user) { create(:user) }
let(:params) { attributes_for(:gpg_key) }
diff --git a/spec/services/gpg_keys/destroy_service_spec.rb b/spec/services/gpg_keys/destroy_service_spec.rb
new file mode 100644
index 00000000000..b9aa3e351c9
--- /dev/null
+++ b/spec/services/gpg_keys/destroy_service_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GpgKeys::DestroyService do
+ let(:user) { create(:user) }
+
+ subject { described_class.new(user) }
+
+ it 'destroys the GPG key' do
+ gpg_key = create(:gpg_key)
+
+ expect { subject.execute(gpg_key) }.to change(GpgKey, :count).by(-1)
+ end
+end
diff --git a/spec/services/grafana/proxy_service_spec.rb b/spec/services/grafana/proxy_service_spec.rb
index 8cb7210524a..7ddc31d45d9 100644
--- a/spec/services/grafana/proxy_service_spec.rb
+++ b/spec/services/grafana/proxy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Grafana::ProxyService do
+RSpec.describe Grafana::ProxyService do
include ReactiveCachingHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/services/gravatar_service_spec.rb b/spec/services/gravatar_service_spec.rb
index 9ce1df0f76f..a6418b02f78 100644
--- a/spec/services/gravatar_service_spec.rb
+++ b/spec/services/gravatar_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GravatarService do
+RSpec.describe GravatarService do
describe '#execute' do
let(:url) { 'http://example.com/avatar?hash=%{hash}&size=%{size}&email=%{email}&username=%{username}' }
diff --git a/spec/services/groups/auto_devops_service_spec.rb b/spec/services/groups/auto_devops_service_spec.rb
index 63fbdc70c1b..3d89ee96823 100644
--- a/spec/services/groups/auto_devops_service_spec.rb
+++ b/spec/services/groups/auto_devops_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Groups::AutoDevopsService, '#execute' do
+RSpec.describe Groups::AutoDevopsService, '#execute' do
let_it_be(:group) { create(:group) }
let_it_be(:user) { create(:user) }
let(:group_params) { { auto_devops_enabled: '0' } }
diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb
index c0e876cce33..fc877f45a39 100644
--- a/spec/services/groups/create_service_spec.rb
+++ b/spec/services/groups/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::CreateService, '#execute' do
+RSpec.describe Groups::CreateService, '#execute' do
let!(:user) { create(:user) }
let!(:group_params) { { path: "group_path", visibility_level: Gitlab::VisibilityLevel::PUBLIC } }
@@ -129,4 +129,13 @@ describe Groups::CreateService, '#execute' do
expect { subject }.to change { ChatTeam.count }.from(0).to(1)
end
end
+
+ describe 'creating a setting record' do
+ let(:service) { described_class.new(user, group_params) }
+
+ it 'create the settings record connected to the group' do
+ group = subject
+ expect(group.namespace_settings).to be_persisted
+ end
+ end
end
diff --git a/spec/services/groups/deploy_tokens/create_service_spec.rb b/spec/services/groups/deploy_tokens/create_service_spec.rb
index 20c609bc828..0c28075f998 100644
--- a/spec/services/groups/deploy_tokens/create_service_spec.rb
+++ b/spec/services/groups/deploy_tokens/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::DeployTokens::CreateService do
+RSpec.describe Groups::DeployTokens::CreateService do
it_behaves_like 'a deploy token creation service' do
let(:entity) { create(:group) }
let(:deploy_token_class) { GroupDeployToken }
diff --git a/spec/services/groups/deploy_tokens/destroy_service_spec.rb b/spec/services/groups/deploy_tokens/destroy_service_spec.rb
index d4ef5963558..28e60b12993 100644
--- a/spec/services/groups/deploy_tokens/destroy_service_spec.rb
+++ b/spec/services/groups/deploy_tokens/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::DeployTokens::DestroyService do
+RSpec.describe Groups::DeployTokens::DestroyService do
it_behaves_like 'a deploy token deletion service' do
let_it_be(:entity) { create(:group) }
let_it_be(:deploy_token_class) { GroupDeployToken }
diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb
index bf639153b99..31afdba8192 100644
--- a/spec/services/groups/destroy_service_spec.rb
+++ b/spec/services/groups/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::DestroyService do
+RSpec.describe Groups::DestroyService do
include DatabaseConnectionHelpers
let!(:user) { create(:user) }
diff --git a/spec/services/groups/group_links/create_service_spec.rb b/spec/services/groups/group_links/create_service_spec.rb
index 36faa69577e..bca03863d1e 100644
--- a/spec/services/groups/group_links/create_service_spec.rb
+++ b/spec/services/groups/group_links/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::GroupLinks::CreateService, '#execute' do
+RSpec.describe Groups::GroupLinks::CreateService, '#execute' do
let(:parent_group_user) { create(:user) }
let(:group_user) { create(:user) }
let(:child_group_user) { create(:user) }
diff --git a/spec/services/groups/group_links/destroy_service_spec.rb b/spec/services/groups/group_links/destroy_service_spec.rb
index 8989f024262..22fe8a1d58b 100644
--- a/spec/services/groups/group_links/destroy_service_spec.rb
+++ b/spec/services/groups/group_links/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::GroupLinks::DestroyService, '#execute' do
+RSpec.describe Groups::GroupLinks::DestroyService, '#execute' do
let(:user) { create(:user) }
let_it_be(:group) { create(:group, :private) }
diff --git a/spec/services/groups/group_links/update_service_spec.rb b/spec/services/groups/group_links/update_service_spec.rb
index 446364c9799..e4ff83d7926 100644
--- a/spec/services/groups/group_links/update_service_spec.rb
+++ b/spec/services/groups/group_links/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::GroupLinks::UpdateService, '#execute' do
+RSpec.describe Groups::GroupLinks::UpdateService, '#execute' do
let(:user) { create(:user) }
let_it_be(:group) { create(:group, :private) }
diff --git a/spec/services/groups/import_export/export_service_spec.rb b/spec/services/groups/import_export/export_service_spec.rb
index ea49b26cc7c..690bcb94556 100644
--- a/spec/services/groups/import_export/export_service_spec.rb
+++ b/spec/services/groups/import_export/export_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::ImportExport::ExportService do
+RSpec.describe Groups::ImportExport::ExportService do
describe '#async_execute' do
let(:user) { create(:user) }
let(:group) { create(:group) }
diff --git a/spec/services/groups/import_export/import_service_spec.rb b/spec/services/groups/import_export/import_service_spec.rb
index 1f7eaccbdbd..4aac602a6da 100644
--- a/spec/services/groups/import_export/import_service_spec.rb
+++ b/spec/services/groups/import_export/import_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::ImportExport::ImportService do
+RSpec.describe Groups::ImportExport::ImportService do
describe '#async_execute' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
diff --git a/spec/services/groups/nested_create_service_spec.rb b/spec/services/groups/nested_create_service_spec.rb
index b30392c1b12..a43c1d8d9c3 100644
--- a/spec/services/groups/nested_create_service_spec.rb
+++ b/spec/services/groups/nested_create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::NestedCreateService do
+RSpec.describe Groups::NestedCreateService do
let(:user) { create(:user) }
subject(:service) { described_class.new(user, params) }
diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb
index d7f6bececfe..fa254bba6a9 100644
--- a/spec/services/groups/transfer_service_spec.rb
+++ b/spec/services/groups/transfer_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::TransferService do
+RSpec.describe Groups::TransferService do
let(:user) { create(:user) }
let(:new_parent_group) { create(:group, :public) }
let!(:group_member) { create(:group_member, :owner, group: group, user: user) }
diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb
index b17d78505d1..25c79d9e600 100644
--- a/spec/services/groups/update_service_spec.rb
+++ b/spec/services/groups/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::UpdateService do
+RSpec.describe Groups::UpdateService do
let!(:user) { create(:user) }
let!(:private_group) { create(:group, :private) }
let!(:internal_group) { create(:group, :internal) }
diff --git a/spec/services/groups/update_shared_runners_service_spec.rb b/spec/services/groups/update_shared_runners_service_spec.rb
new file mode 100644
index 00000000000..9fd8477a455
--- /dev/null
+++ b/spec/services/groups/update_shared_runners_service_spec.rb
@@ -0,0 +1,230 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Groups::UpdateSharedRunnersService do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:params) { {} }
+
+ describe '#execute' do
+ subject { described_class.new(group, user, params).execute }
+
+ context 'when current_user is not the group owner' do
+ let_it_be(:group) { create(:group) }
+
+ let(:params) { { shared_runners_enabled: '0' } }
+
+ before do
+ group.add_maintainer(user)
+ end
+
+ it 'results error and does not call any method' do
+ expect(group).not_to receive(:enable_shared_runners!)
+ expect(group).not_to receive(:disable_shared_runners!)
+ expect(group).not_to receive(:allow_descendants_override_disabled_shared_runners!)
+ expect(group).not_to receive(:disallow_descendants_override_disabled_shared_runners!)
+
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:message]).to eq('Operation not allowed')
+ expect(subject[:http_status]).to eq(403)
+ end
+ end
+
+ context 'when current_user is the group owner' do
+ before do
+ group.add_owner(user)
+ end
+
+ context 'enable shared Runners' do
+ where(:desired_params) do
+ ['1', true]
+ end
+
+ with_them do
+ let(:params) { { shared_runners_enabled: desired_params } }
+
+ context 'group that its ancestors have shared runners disabled' do
+ let_it_be(:parent) { create(:group, :shared_runners_disabled) }
+ let_it_be(:group) { create(:group, :shared_runners_disabled, parent: parent) }
+
+ it 'results error' do
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:message]).to eq('Shared Runners disabled for the parent group')
+ end
+ end
+
+ context 'root group with shared runners disabled' do
+ let_it_be(:group) { create(:group, :shared_runners_disabled) }
+
+ it 'receives correct method and succeeds' do
+ expect(group).to receive(:enable_shared_runners!)
+ expect(group).not_to receive(:disable_shared_runners!)
+ expect(group).not_to receive(:allow_descendants_override_disabled_shared_runners!)
+ expect(group).not_to receive(:disallow_descendants_override_disabled_shared_runners!)
+
+ expect(subject[:status]).to eq(:success)
+ end
+ end
+ end
+ end
+
+ context 'disable shared Runners' do
+ let_it_be(:group) { create(:group) }
+
+ where(:desired_params) do
+ ['0', false]
+ end
+
+ with_them do
+ let(:params) { { shared_runners_enabled: desired_params } }
+
+ it 'receives correct method and succeeds' do
+ expect(group).to receive(:disable_shared_runners!)
+ expect(group).not_to receive(:enable_shared_runners!)
+ expect(group).not_to receive(:allow_descendants_override_disabled_shared_runners!)
+ expect(group).not_to receive(:disallow_descendants_override_disabled_shared_runners!)
+
+ expect(subject[:status]).to eq(:success)
+ end
+ end
+ end
+
+ context 'allow descendants to override' do
+ where(:desired_params) do
+ ['1', true]
+ end
+
+ with_them do
+ let(:params) { { allow_descendants_override_disabled_shared_runners: desired_params } }
+
+ context 'top level group' do
+ let_it_be(:group) { create(:group, :shared_runners_disabled) }
+
+ it 'receives correct method and succeeds' do
+ expect(group).to receive(:allow_descendants_override_disabled_shared_runners!)
+ expect(group).not_to receive(:disallow_descendants_override_disabled_shared_runners!)
+ expect(group).not_to receive(:enable_shared_runners!)
+ expect(group).not_to receive(:disable_shared_runners!)
+
+ expect(subject[:status]).to eq(:success)
+ end
+ end
+
+ context 'when parent does not allow' do
+ let_it_be(:parent) { create(:group, :shared_runners_disabled, allow_descendants_override_disabled_shared_runners: false ) }
+ let_it_be(:group) { create(:group, :shared_runners_disabled, allow_descendants_override_disabled_shared_runners: false, parent: parent) }
+
+ it 'results error' do
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:message]).to eq('Group level shared Runners not allowed')
+ end
+ end
+ end
+ end
+
+ context 'disallow descendants to override' do
+ where(:desired_params) do
+ ['0', false]
+ end
+
+ with_them do
+ let(:params) { { allow_descendants_override_disabled_shared_runners: desired_params } }
+
+ context 'top level group' do
+ let_it_be(:group) { create(:group, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners ) }
+
+ it 'receives correct method and succeeds' do
+ expect(group).to receive(:disallow_descendants_override_disabled_shared_runners!)
+ expect(group).not_to receive(:allow_descendants_override_disabled_shared_runners!)
+ expect(group).not_to receive(:enable_shared_runners!)
+ expect(group).not_to receive(:disable_shared_runners!)
+
+ expect(subject[:status]).to eq(:success)
+ end
+ end
+
+ context 'top level group that has shared Runners enabled' do
+ let_it_be(:group) { create(:group, shared_runners_enabled: true) }
+
+ it 'results error' do
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:message]).to eq('Shared Runners enabled')
+ end
+ end
+ end
+ end
+
+ context 'both params are present' do
+ context 'shared_runners_enabled: 1 and allow_descendants_override_disabled_shared_runners' do
+ let_it_be(:group) { create(:group, :shared_runners_disabled) }
+ let_it_be(:sub_group) { create(:group, :shared_runners_disabled, parent: group) }
+ let_it_be(:project) { create(:project, shared_runners_enabled: false, group: sub_group) }
+
+ where(:allow_descendants_override) do
+ ['1', true, '0', false]
+ end
+
+ with_them do
+ let(:params) { { shared_runners_enabled: '1', allow_descendants_override_disabled_shared_runners: allow_descendants_override } }
+
+ it 'results in an error because shared Runners are enabled' do
+ expect { subject }
+ .to not_change { group.reload.shared_runners_enabled }
+ .and not_change { sub_group.reload.shared_runners_enabled }
+ .and not_change { project.reload.shared_runners_enabled }
+ .and not_change { group.reload.allow_descendants_override_disabled_shared_runners }
+ .and not_change { sub_group.reload.allow_descendants_override_disabled_shared_runners }
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:message]).to eq('Cannot set shared_runners_enabled to true and allow_descendants_override_disabled_shared_runners')
+ end
+ end
+ end
+
+ context 'shared_runners_enabled: 0 and allow_descendants_override_disabled_shared_runners: 0' do
+ let_it_be(:group) { create(:group, :allow_descendants_override_disabled_shared_runners) }
+ let_it_be(:sub_group) { create(:group, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners, parent: group) }
+ let_it_be(:sub_group_2) { create(:group, parent: group) }
+ let_it_be(:project) { create(:project, group: group, shared_runners_enabled: true) }
+ let_it_be(:project_2) { create(:project, group: sub_group_2, shared_runners_enabled: true) }
+
+ let(:params) { { shared_runners_enabled: '0', allow_descendants_override_disabled_shared_runners: '0' } }
+
+ it 'disables shared Runners and disable allow_descendants_override_disabled_shared_runners' do
+ expect { subject }
+ .to change { group.reload.shared_runners_enabled }.from(true).to(false)
+ .and change { group.reload.allow_descendants_override_disabled_shared_runners }.from(true).to(false)
+ .and not_change { sub_group.reload.shared_runners_enabled }
+ .and change { sub_group.reload.allow_descendants_override_disabled_shared_runners }.from(true).to(false)
+ .and change { sub_group_2.reload.shared_runners_enabled }.from(true).to(false)
+ .and not_change { sub_group_2.reload.allow_descendants_override_disabled_shared_runners }
+ .and change { project.reload.shared_runners_enabled }.from(true).to(false)
+ .and change { project_2.reload.shared_runners_enabled }.from(true).to(false)
+ end
+ end
+
+ context 'shared_runners_enabled: 0 and allow_descendants_override_disabled_shared_runners: 1' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:sub_group) { create(:group, :shared_runners_disabled, parent: group) }
+ let_it_be(:sub_group_2) { create(:group, parent: group) }
+ let_it_be(:project) { create(:project, group: group, shared_runners_enabled: true) }
+ let_it_be(:project_2) { create(:project, group: sub_group_2, shared_runners_enabled: true) }
+
+ let(:params) { { shared_runners_enabled: '0', allow_descendants_override_disabled_shared_runners: '1' } }
+
+ it 'disables shared Runners and enable allow_descendants_override_disabled_shared_runners only for itself' do
+ expect { subject }
+ .to change { group.reload.shared_runners_enabled }.from(true).to(false)
+ .and change { group.reload.allow_descendants_override_disabled_shared_runners }.from(false).to(true)
+ .and not_change { sub_group.reload.shared_runners_enabled }
+ .and not_change { sub_group.reload.allow_descendants_override_disabled_shared_runners }
+ .and change { sub_group_2.reload.shared_runners_enabled }.from(true).to(false)
+ .and not_change { sub_group_2.reload.allow_descendants_override_disabled_shared_runners }
+ .and change { project.reload.shared_runners_enabled }.from(true).to(false)
+ .and change { project_2.reload.shared_runners_enabled }.from(true).to(false)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/import/bitbucket_server_service_spec.rb b/spec/services/import/bitbucket_server_service_spec.rb
new file mode 100644
index 00000000000..c548e87b040
--- /dev/null
+++ b/spec/services/import/bitbucket_server_service_spec.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Import::BitbucketServerService do
+ let_it_be(:user) { create(:user) }
+ let(:base_uri) { "https://test:7990" }
+ let(:token) { "asdasd12345" }
+ let(:secret) { "sekrettt" }
+ let(:project_key) { 'TES' }
+ let(:repo_slug) { 'vim' }
+ let(:repo) do
+ {
+ name: 'vim',
+ description: 'test',
+ visibility_level: Gitlab::VisibilityLevel::PUBLIC,
+ browse_url: 'http://repo.com/repo/repo',
+ clone_url: 'http://repo.com/repo/repo.git'
+ }
+ end
+
+ let(:client) { double(BitbucketServer::Client) }
+
+ let(:credentials) { { base_uri: base_uri, user: user, password: token } }
+ let(:params) { { bitbucket_server_url: base_uri, bitbucket_server_username: user, personal_access_token: token, bitbucket_server_project: project_key, bitbucket_server_repo: repo_slug } }
+
+ subject { described_class.new(client, user, params) }
+
+ before do
+ allow(subject).to receive(:authorized?).and_return(true)
+ end
+
+ context 'when no repo is found' do
+ before do
+ allow(subject).to receive(:authorized?).and_return(true)
+ allow(client).to receive(:repo).and_return(nil)
+ end
+
+ it 'returns an error' do
+ result = subject.execute(credentials)
+
+ expect(result).to include(
+ message: "Project #{project_key}/#{repo_slug} could not be found",
+ status: :error,
+ http_status: :unprocessable_entity
+ )
+ end
+ end
+
+ context 'when user is unauthorized' do
+ before do
+ allow(subject).to receive(:authorized?).and_return(false)
+ end
+
+ it 'returns an error' do
+ result = subject.execute(credentials)
+
+ expect(result).to include(
+ message: "You don't have permissions to create this project",
+ status: :error,
+ http_status: :unauthorized
+ )
+ end
+ end
+
+ context 'verify url' do
+ shared_examples 'denies local request' do
+ before do
+ allow(client).to receive(:repo).with(project_key, repo_slug).and_return(double(repo))
+ end
+
+ it 'does not allow requests' do
+ result = subject.execute(credentials)
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to include("Invalid URL:")
+ end
+ end
+
+ context 'when host is localhost' do
+ before do
+ allow(subject).to receive(:url).and_return('https://localhost:3000')
+ end
+
+ include_examples 'denies local request'
+ end
+
+ context 'when host is on local network' do
+ before do
+ allow(subject).to receive(:url).and_return('https://192.168.0.191')
+ end
+
+ include_examples 'denies local request'
+ end
+
+ context 'when host is ftp protocol' do
+ before do
+ allow(subject).to receive(:url).and_return('ftp://testing')
+ end
+
+ include_examples 'denies local request'
+ end
+ end
+
+ it 'raises an exception for unknown error causes' do
+ exception = StandardError.new('Not Implemented')
+
+ allow(client).to receive(:repo).and_raise(exception)
+
+ expect(Gitlab::Import::Logger).not_to receive(:error)
+
+ expect { subject.execute(credentials) }.to raise_error(exception)
+ end
+end
diff --git a/spec/services/import/github_service_spec.rb b/spec/services/import/github_service_spec.rb
index 461b17e0e33..266ff309662 100644
--- a/spec/services/import/github_service_spec.rb
+++ b/spec/services/import/github_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Import::GithubService do
+RSpec.describe Import::GithubService do
let_it_be(:user) { create(:user) }
let_it_be(:token) { 'complex-token' }
let_it_be(:access_params) { { github_access_token: 'github-complex-token' } }
diff --git a/spec/services/import_export_clean_up_service_spec.rb b/spec/services/import_export_clean_up_service_spec.rb
index 9f811f56f50..4101b13adf9 100644
--- a/spec/services/import_export_clean_up_service_spec.rb
+++ b/spec/services/import_export_clean_up_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ImportExportCleanUpService do
+RSpec.describe ImportExportCleanUpService do
describe '#execute' do
let(:service) { described_class.new }
diff --git a/spec/services/incident_management/create_incident_label_service_spec.rb b/spec/services/incident_management/create_incident_label_service_spec.rb
new file mode 100644
index 00000000000..2f11bcf397e
--- /dev/null
+++ b/spec/services/incident_management/create_incident_label_service_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe IncidentManagement::CreateIncidentLabelService do
+ let_it_be(:project) { create(:project, :private) }
+ let_it_be(:user) { User.alert_bot }
+ let(:service) { described_class.new(project, user) }
+
+ subject(:execute) { service.execute }
+
+ describe 'execute' do
+ let(:title) { described_class::LABEL_PROPERTIES[:title] }
+ let(:color) { described_class::LABEL_PROPERTIES[:color] }
+ let(:description) { described_class::LABEL_PROPERTIES[:description] }
+
+ shared_examples 'existing label' do
+ it 'returns the existing label' do
+ expect { execute }.not_to change(Label, :count)
+
+ expect(execute).to be_success
+ expect(execute.payload).to eq(label: label)
+ end
+ end
+
+ shared_examples 'new label' do
+ it 'creates a new label' do
+ expect { execute }.to change(Label, :count).by(1)
+
+ label = project.reload.labels.last
+ expect(execute).to be_success
+ expect(execute.payload).to eq(label: label)
+ expect(label.title).to eq(title)
+ expect(label.color).to eq(color)
+ expect(label.description).to eq(description)
+ end
+ end
+
+ context 'with predefined project label' do
+ it_behaves_like 'existing label' do
+ let!(:label) { create(:label, project: project, title: title) }
+ end
+ end
+
+ context 'with predefined group label' do
+ let(:project) { create(:project, group: group) }
+ let(:group) { create(:group) }
+
+ it_behaves_like 'existing label' do
+ let!(:label) { create(:group_label, group: group, title: title) }
+ end
+ end
+
+ context 'without label' do
+ it_behaves_like 'new label'
+ end
+ end
+end
diff --git a/spec/services/incident_management/create_issue_service_spec.rb b/spec/services/incident_management/create_issue_service_spec.rb
index 5a3721f00b8..dab9a149458 100644
--- a/spec/services/incident_management/create_issue_service_spec.rb
+++ b/spec/services/incident_management/create_issue_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IncidentManagement::CreateIssueService do
+RSpec.describe IncidentManagement::CreateIssueService do
let(:project) { create(:project, :repository, :private) }
let_it_be(:user) { User.alert_bot }
let(:service) { described_class.new(project, alert_payload) }
@@ -199,80 +199,7 @@ describe IncidentManagement::CreateIssueService do
end
describe "label `incident`" do
- let(:title) { 'incident' }
- let(:color) { '#CC0033' }
- let(:description) do
- <<~DESCRIPTION.chomp
- Denotes a disruption to IT services and \
- the associated issues require immediate attention
- DESCRIPTION
- end
-
- shared_examples 'existing label' do
- it 'adds the existing label' do
- expect { subject }.not_to change(Label, :count)
-
- expect(issue.labels).to eq([label])
- end
- end
-
- shared_examples 'new label' do
- it 'adds newly created label' do
- expect { subject }.to change(Label, :count).by(1)
-
- label = project.reload.labels.last
- expect(issue.labels).to eq([label])
- expect(label.title).to eq(title)
- expect(label.color).to eq(color)
- expect(label.description).to eq(description)
- end
- end
-
- context 'with predefined project label' do
- it_behaves_like 'existing label' do
- let!(:label) { create(:label, project: project, title: title) }
- end
- end
-
- context 'with predefined group label' do
- let(:project) { create(:project, group: group) }
- let(:group) { create(:group) }
-
- it_behaves_like 'existing label' do
- let!(:label) { create(:group_label, group: group, title: title) }
- end
- end
-
- context 'without label' do
- it_behaves_like 'new label'
- end
-
- context 'with duplicate labels', issue: 'https://gitlab.com/gitlab-org/gitlab-foss/issues/65042' do
- before do
- # Replicate race condition to create duplicates
- build(:label, project: project, title: title).save!(validate: false)
- build(:label, project: project, title: title).save!(validate: false)
- end
-
- it 'create an issue without labels' do
- # Verify we have duplicates
- expect(project.labels.size).to eq(2)
- expect(project.labels.map(&:title)).to all(eq(title))
-
- message = <<~MESSAGE.chomp
- Cannot create incident issue with labels ["#{title}"] for \
- "#{project.full_name}": Labels is invalid.
- Retrying without labels.
- MESSAGE
-
- expect(service)
- .to receive(:log_info)
- .with(message)
-
- expect(subject).to include(status: :success)
- expect(issue.labels).to be_empty
- end
- end
+ it_behaves_like 'create alert issue sets issue labels'
end
end
@@ -281,22 +208,12 @@ describe IncidentManagement::CreateIssueService do
setting.update!(create_issue: false)
end
- context 'when skip_settings_check is false (default)' do
- it 'returns an error' do
- expect(service)
- .to receive(:log_error)
- .with(error_message('setting disabled'))
+ it 'returns an error' do
+ expect(service)
+ .to receive(:log_error)
+ .with(error_message('setting disabled'))
- expect(subject).to eq(status: :error, message: 'setting disabled')
- end
- end
-
- context 'when skip_settings_check is true' do
- subject { service.execute(skip_settings_check: true) }
-
- it 'creates an issue' do
- expect { subject }.to change(Issue, :count).by(1)
- end
+ expect(subject).to eq(status: :error, message: 'setting disabled')
end
end
diff --git a/spec/services/incident_management/pager_duty/create_incident_issue_service_spec.rb b/spec/services/incident_management/pager_duty/create_incident_issue_service_spec.rb
new file mode 100644
index 00000000000..cf43ed2411d
--- /dev/null
+++ b/spec/services/incident_management/pager_duty/create_incident_issue_service_spec.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe IncidentManagement::PagerDuty::CreateIncidentIssueService do
+ let_it_be(:project, reload: true) { create(:project) }
+ let_it_be(:user) { User.alert_bot }
+ let(:webhook_payload) { Gitlab::Json.parse(fixture_file('pager_duty/webhook_incident_trigger.json')) }
+ let(:parsed_payload) { ::PagerDuty::WebhookPayloadParser.call(webhook_payload) }
+ let(:incident_payload) { parsed_payload.first['incident'] }
+
+ subject(:execute) { described_class.new(project, incident_payload).execute }
+
+ describe '#execute' do
+ context 'when pagerduty_webhook feature enabled' do
+ before do
+ stub_feature_flags(pagerduty_webhook: project)
+ end
+
+ context 'when PagerDuty webhook setting is active' do
+ let_it_be(:incident_management_setting) { create(:project_incident_management_setting, project: project, pagerduty_active: true) }
+
+ context 'when issue can be created' do
+ it 'creates a new issue' do
+ expect { execute }.to change(Issue, :count).by(1)
+ end
+
+ it 'responds with success' do
+ response = execute
+
+ expect(response).to be_success
+ expect(response.payload[:issue]).to be_kind_of(Issue)
+ end
+
+ it 'the issue author is Alert bot' do
+ expect(execute.payload[:issue].author).to eq(User.alert_bot)
+ end
+
+ it 'issue has a correct title' do
+ expect(execute.payload[:issue].title).to eq(incident_payload['title'])
+ end
+
+ it 'issue has a correct description' do
+ markdown_line_break = ' '
+
+ expect(execute.payload[:issue].description).to eq(
+ <<~MARKDOWN.chomp
+ **Incident:** [My new incident](https://webdemo.pagerduty.com/incidents/PRORDTY)#{markdown_line_break}
+ **Incident number:** 33#{markdown_line_break}
+ **Urgency:** high#{markdown_line_break}
+ **Status:** triggered#{markdown_line_break}
+ **Incident key:** #{markdown_line_break}
+ **Created at:** 26 September 2017, 3:14PM (UTC)#{markdown_line_break}
+ **Assignees:** [Laura Haley](https://webdemo.pagerduty.com/users/P553OPV)#{markdown_line_break}
+ **Impacted services:** [Production XDB Cluster](https://webdemo.pagerduty.com/services/PN49J75)
+ MARKDOWN
+ )
+ end
+ end
+
+ context 'when the payload does not contain a title' do
+ let(:incident_payload) { {} }
+
+ it 'does not create a GitLab issue' do
+ expect { execute }.not_to change(Issue, :count)
+ end
+
+ it 'responds with error' do
+ expect(execute).to be_error
+ expect(execute.message).to eq("Title can't be blank")
+ end
+ end
+ end
+
+ context 'when PagerDuty webhook setting is not active' do
+ let_it_be(:incident_management_setting) { create(:project_incident_management_setting, project: project, pagerduty_active: false) }
+
+ it 'does not create a GitLab issue' do
+ expect { execute }.not_to change(Issue, :count)
+ end
+
+ it 'responds with forbidden' do
+ expect(execute).to be_error
+ expect(execute.http_status).to eq(:forbidden)
+ end
+ end
+ end
+
+ context 'when pagerduty_webhook feature disabled' do
+ before do
+ stub_feature_flags(pagerduty_webhook: false)
+ end
+
+ it 'does not create a GitLab issue' do
+ expect { execute }.not_to change(Issue, :count)
+ end
+
+ it 'responds with forbidden' do
+ expect(execute).to be_error
+ expect(execute.http_status).to eq(:forbidden)
+ end
+ end
+ end
+end
diff --git a/spec/services/incident_management/pager_duty/process_webhook_service_spec.rb b/spec/services/incident_management/pager_duty/process_webhook_service_spec.rb
new file mode 100644
index 00000000000..11ce8388427
--- /dev/null
+++ b/spec/services/incident_management/pager_duty/process_webhook_service_spec.rb
@@ -0,0 +1,148 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe IncidentManagement::PagerDuty::ProcessWebhookService do
+ let_it_be(:project, reload: true) { create(:project) }
+
+ describe '#execute' do
+ shared_examples 'does not process incidents' do
+ it 'does not process incidents' do
+ expect(::IncidentManagement::PagerDuty::ProcessIncidentWorker).not_to receive(:perform_async)
+
+ execute
+ end
+ end
+
+ let(:webhook_payload) { Gitlab::Json.parse(fixture_file('pager_duty/webhook_incident_trigger.json')) }
+ let(:token) { nil }
+
+ subject(:execute) { described_class.new(project, nil, webhook_payload).execute(token) }
+
+ context 'when pagerduty_webhook feature is enabled' do
+ before do
+ stub_feature_flags(pagerduty_webhook: project)
+ end
+
+ context 'when PagerDuty webhook setting is active' do
+ let_it_be(:incident_management_setting) { create(:project_incident_management_setting, project: project, pagerduty_active: true) }
+
+ context 'when token is valid' do
+ let(:token) { incident_management_setting.pagerduty_token }
+
+ context 'when webhook payload has acceptable size' do
+ it 'responds with Accepted' do
+ result = execute
+
+ expect(result).to be_success
+ expect(result.http_status).to eq(:accepted)
+ end
+
+ it 'processes issues' do
+ incident_payload = ::PagerDuty::WebhookPayloadParser.call(webhook_payload).first['incident']
+
+ expect(::IncidentManagement::PagerDuty::ProcessIncidentWorker)
+ .to receive(:perform_async)
+ .with(project.id, incident_payload)
+ .once
+
+ execute
+ end
+ end
+
+ context 'when webhook payload is too big' do
+ let(:deep_size) { instance_double(Gitlab::Utils::DeepSize, valid?: false) }
+
+ before do
+ allow(Gitlab::Utils::DeepSize)
+ .to receive(:new)
+ .with(webhook_payload, max_size: described_class::PAGER_DUTY_PAYLOAD_SIZE_LIMIT)
+ .and_return(deep_size)
+ end
+
+ it 'responds with Bad Request' do
+ result = execute
+
+ expect(result).to be_error
+ expect(result.http_status).to eq(:bad_request)
+ end
+
+ it_behaves_like 'does not process incidents'
+ end
+
+ context 'when webhook payload is blank' do
+ let(:webhook_payload) { nil }
+
+ it 'responds with Accepted' do
+ result = execute
+
+ expect(result).to be_success
+ expect(result.http_status).to eq(:accepted)
+ end
+
+ it_behaves_like 'does not process incidents'
+ end
+ end
+
+ context 'when token is invalid' do
+ let(:token) { 'invalid-token' }
+
+ it 'responds with Unauthorized' do
+ result = execute
+
+ expect(result).to be_error
+ expect(result.http_status).to eq(:unauthorized)
+ end
+
+ it_behaves_like 'does not process incidents'
+ end
+ end
+
+ context 'when both tokens are nil' do
+ let_it_be(:incident_management_setting) { create(:project_incident_management_setting, project: project, pagerduty_active: false) }
+ let(:token) { nil }
+
+ before do
+ incident_management_setting.update_column(:pagerduty_active, true)
+ end
+
+ it 'responds with Unauthorized' do
+ result = execute
+
+ expect(result).to be_error
+ expect(result.http_status).to eq(:unauthorized)
+ end
+
+ it_behaves_like 'does not process incidents'
+ end
+
+ context 'when PagerDuty webhook setting is not active' do
+ let_it_be(:incident_management_setting) { create(:project_incident_management_setting, project: project, pagerduty_active: false) }
+
+ it 'responds with Forbidden' do
+ result = execute
+
+ expect(result).to be_error
+ expect(result.http_status).to eq(:forbidden)
+ end
+
+ it_behaves_like 'does not process incidents'
+ end
+ end
+
+ context 'when pagerduty_webhook feature is disabled' do
+ before do
+ stub_feature_flags(pagerduty_webhook: false)
+ end
+
+ it 'responds with Forbidden' do
+ result = execute
+
+ expect(result).to be_error
+ expect(result.http_status).to eq(:forbidden)
+ end
+
+ it_behaves_like 'does not process incidents'
+ end
+ end
+end
diff --git a/spec/services/integrations/test/project_service_spec.rb b/spec/services/integrations/test/project_service_spec.rb
index fdb43ca345a..dd603765d59 100644
--- a/spec/services/integrations/test/project_service_spec.rb
+++ b/spec/services/integrations/test/project_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Integrations::Test::ProjectService do
+RSpec.describe Integrations::Test::ProjectService do
let(:user) { double('user') }
describe '#execute' do
diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb
index c791c454d70..168a80a97c0 100644
--- a/spec/services/issuable/bulk_update_service_spec.rb
+++ b/spec/services/issuable/bulk_update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issuable::BulkUpdateService do
+RSpec.describe Issuable::BulkUpdateService do
let(:user) { create(:user) }
let(:project) { create(:project, :repository, namespace: user.namespace) }
@@ -18,8 +18,8 @@ describe Issuable::BulkUpdateService do
it 'succeeds' do
result = bulk_update(issuables, milestone_id: milestone.id)
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(issuables.count)
+ expect(result.success?).to be_truthy
+ expect(result.payload[:count]).to eq(issuables.count)
end
it 'updates the issuables milestone' do
@@ -121,8 +121,8 @@ describe Issuable::BulkUpdateService do
it 'succeeds and returns the correct number of issues updated' do
result = bulk_update(issues, state_event: 'close')
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(issues.count)
+ expect(result.success?).to be_truthy
+ expect(result.payload[:count]).to eq(issues.count)
end
it 'closes all the issues passed' do
@@ -139,8 +139,8 @@ describe Issuable::BulkUpdateService do
it 'succeeds and returns the correct number of issues updated' do
result = bulk_update(issues, state_event: 'reopen')
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(issues.count)
+ expect(result.success?).to be_truthy
+ expect(result.payload[:count]).to eq(issues.count)
end
it 'reopens all the issues passed' do
@@ -161,8 +161,8 @@ describe Issuable::BulkUpdateService do
result = bulk_update(merge_request, assignee_ids: [user.id, new_assignee.id])
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(1)
+ expect(result.success?).to be_truthy
+ expect(result.payload[:count]).to eq(1)
end
it 'updates the assignee to the user ID passed' do
@@ -199,8 +199,8 @@ describe Issuable::BulkUpdateService do
result = bulk_update(issue, assignee_ids: [new_assignee.id])
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(1)
+ expect(result.success?).to be_truthy
+ expect(result.payload[:count]).to eq(1)
end
it 'updates the assignee to the user ID passed' do
@@ -273,8 +273,8 @@ describe Issuable::BulkUpdateService do
issue2 = create(:issue, project: create(:project))
result = bulk_update([issue1, issue2], assignee_ids: [user.id])
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(1)
+ expect(result.success?).to be_truthy
+ expect(result.payload[:count]).to eq(1)
expect(issue1.reload.assignees).to eq([user])
expect(issue2.reload.assignees).to be_empty
@@ -332,8 +332,8 @@ describe Issuable::BulkUpdateService do
milestone = create(:milestone, group: group)
result = bulk_update([issue1, issue2, issue3], milestone_id: milestone.id)
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(2)
+ expect(result.success?).to be_truthy
+ expect(result.payload[:count]).to eq(2)
expect(issue1.reload.milestone).to eq(milestone)
expect(issue2.reload.milestone).to be_nil
diff --git a/spec/services/issuable/clone/attributes_rewriter_spec.rb b/spec/services/issuable/clone/attributes_rewriter_spec.rb
index fb520f828fa..372e6d480e3 100644
--- a/spec/services/issuable/clone/attributes_rewriter_spec.rb
+++ b/spec/services/issuable/clone/attributes_rewriter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issuable::Clone::AttributesRewriter do
+RSpec.describe Issuable::Clone::AttributesRewriter do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project1) { create(:project, :public, group: group) }
diff --git a/spec/services/issuable/clone/content_rewriter_spec.rb b/spec/services/issuable/clone/content_rewriter_spec.rb
index 3479c20862a..f39439b7c2f 100644
--- a/spec/services/issuable/clone/content_rewriter_spec.rb
+++ b/spec/services/issuable/clone/content_rewriter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issuable::Clone::ContentRewriter do
+RSpec.describe Issuable::Clone::ContentRewriter do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project1) { create(:project, :public, group: group) }
diff --git a/spec/services/issuable/common_system_notes_service_spec.rb b/spec/services/issuable/common_system_notes_service_spec.rb
index 771e7ca42c9..daf4f68208e 100644
--- a/spec/services/issuable/common_system_notes_service_spec.rb
+++ b/spec/services/issuable/common_system_notes_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issuable::CommonSystemNotesService do
+RSpec.describe Issuable::CommonSystemNotesService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
@@ -43,23 +43,23 @@ describe Issuable::CommonSystemNotesService do
it_behaves_like 'system note creation', {}, 'changed milestone'
end
- context 'with merge requests WIP note' do
- context 'adding WIP note' do
+ context 'with merge requests Draft note' do
+ context 'adding Draft note' do
let(:issuable) { create(:merge_request, title: "merge request") }
- it_behaves_like 'system note creation', { title: "WIP merge request" }, 'marked as a **Work In Progress**'
+ it_behaves_like 'system note creation', { title: "Draft: merge request" }, 'marked as a **Work In Progress**'
context 'and changing title' do
before do
- issuable.update_attribute(:title, "WIP changed title")
+ issuable.update_attribute(:title, "Draft: changed title")
end
- it_behaves_like 'WIP notes creation', 'marked'
+ it_behaves_like 'draft notes creation', 'marked'
end
end
- context 'removing WIP note' do
- let(:issuable) { create(:merge_request, title: "WIP merge request") }
+ context 'removing Draft note' do
+ let(:issuable) { create(:merge_request, title: "Draft: merge request") }
it_behaves_like 'system note creation', { title: "merge request" }, 'unmarked as a **Work In Progress**'
@@ -68,7 +68,7 @@ describe Issuable::CommonSystemNotesService do
issuable.update_attribute(:title, "changed title")
end
- it_behaves_like 'WIP notes creation', 'unmarked'
+ it_behaves_like 'draft notes creation', 'unmarked'
end
end
end
diff --git a/spec/services/issuable/destroy_service_spec.rb b/spec/services/issuable/destroy_service_spec.rb
index dd6a966c145..8d62932f986 100644
--- a/spec/services/issuable/destroy_service_spec.rb
+++ b/spec/services/issuable/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issuable::DestroyService do
+RSpec.describe Issuable::DestroyService do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
diff --git a/spec/services/issues/build_service_spec.rb b/spec/services/issues/build_service_spec.rb
index 140b78f9b7a..68b226b02da 100644
--- a/spec/services/issues/build_service_spec.rb
+++ b/spec/services/issues/build_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper.rb'
-describe Issues::BuildService do
+RSpec.describe Issues::BuildService do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index 78eba565de4..6678d831775 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issues::CloseService do
+RSpec.describe Issues::CloseService do
let(:project) { create(:project, :repository) }
let(:user) { create(:user, email: "user@example.com") }
let(:user2) { create(:user, email: "user2@example.com") }
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index bb02941576a..fdf2326b75e 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issues::CreateService do
+RSpec.describe Issues::CreateService do
let(:project) { create(:project) }
let(:user) { create(:user) }
@@ -284,7 +284,9 @@ describe Issues::CreateService do
end
end
- it_behaves_like 'new issuable record that supports quick actions'
+ it_behaves_like 'issuable record that supports quick actions' do
+ let(:issuable) { described_class.new(project, user, params).execute }
+ end
context 'Quick actions' do
context 'with assignee and milestone in params and command' do
diff --git a/spec/services/issues/duplicate_service_spec.rb b/spec/services/issues/duplicate_service_spec.rb
index 41a151b0ca1..78e030e6ac7 100644
--- a/spec/services/issues/duplicate_service_spec.rb
+++ b/spec/services/issues/duplicate_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issues::DuplicateService do
+RSpec.describe Issues::DuplicateService do
let(:user) { create(:user) }
let(:canonical_project) { create(:project) }
let(:duplicate_project) { create(:project) }
diff --git a/spec/services/issues/export_csv_service_spec.rb b/spec/services/issues/export_csv_service_spec.rb
index 419e29d92a8..76381fe525b 100644
--- a/spec/services/issues/export_csv_service_spec.rb
+++ b/spec/services/issues/export_csv_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issues::ExportCsvService do
+RSpec.describe Issues::ExportCsvService do
let_it_be(:user) { create(:user) }
let(:group) { create(:group) }
let(:project) { create(:project, :public, group: group) }
diff --git a/spec/services/issues/import_csv_service_spec.rb b/spec/services/issues/import_csv_service_spec.rb
index 92b88489af9..cc3e1d23a74 100644
--- a/spec/services/issues/import_csv_service_spec.rb
+++ b/spec/services/issues/import_csv_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issues::ImportCsvService do
+RSpec.describe Issues::ImportCsvService do
let(:project) { create(:project) }
let(:user) { create(:user) }
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
index a449541f459..8929907a179 100644
--- a/spec/services/issues/move_service_spec.rb
+++ b/spec/services/issues/move_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issues::MoveService do
+RSpec.describe Issues::MoveService do
let(:user) { create(:user) }
let(:author) { create(:user) }
let(:title) { 'Some issue' }
@@ -210,4 +210,49 @@ describe Issues::MoveService do
end
end
end
+
+ context 'updating sent notifications' do
+ let!(:old_issue_notification_1) { create(:sent_notification, project: old_issue.project, noteable: old_issue) }
+ let!(:old_issue_notification_2) { create(:sent_notification, project: old_issue.project, noteable: old_issue) }
+ let!(:other_issue_notification) { create(:sent_notification, project: old_issue.project) }
+
+ include_context 'user can move issue'
+
+ context 'when issue is from service desk' do
+ before do
+ allow(old_issue).to receive(:from_service_desk?).and_return(true)
+ end
+
+ it 'updates moved issue sent notifications' do
+ new_issue = move_service.execute(old_issue, new_project)
+
+ old_issue_notification_1.reload
+ old_issue_notification_2.reload
+ expect(old_issue_notification_1.project_id).to eq(new_issue.project_id)
+ expect(old_issue_notification_1.noteable_id).to eq(new_issue.id)
+ expect(old_issue_notification_2.project_id).to eq(new_issue.project_id)
+ expect(old_issue_notification_2.noteable_id).to eq(new_issue.id)
+ end
+
+ it 'does not update other issues sent notifications' do
+ expect do
+ move_service.execute(old_issue, new_project)
+ other_issue_notification.reload
+ end.not_to change { other_issue_notification.noteable_id }
+ end
+ end
+
+ context 'when issue is not from service desk' do
+ it 'does not update sent notifications' do
+ move_service.execute(old_issue, new_project)
+
+ old_issue_notification_1.reload
+ old_issue_notification_2.reload
+ expect(old_issue_notification_1.project_id).to eq(old_issue.project_id)
+ expect(old_issue_notification_1.noteable_id).to eq(old_issue.id)
+ expect(old_issue_notification_2.project_id).to eq(old_issue.project_id)
+ expect(old_issue_notification_2.noteable_id).to eq(old_issue.id)
+ end
+ end
+ end
end
diff --git a/spec/services/issues/referenced_merge_requests_service_spec.rb b/spec/services/issues/referenced_merge_requests_service_spec.rb
index 2c5af11d2e6..bf7a4c97e48 100644
--- a/spec/services/issues/referenced_merge_requests_service_spec.rb
+++ b/spec/services/issues/referenced_merge_requests_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper.rb'
-describe Issues::ReferencedMergeRequestsService do
+RSpec.describe Issues::ReferencedMergeRequestsService do
def create_referencing_mr(attributes = {})
create(:merge_request, attributes).tap do |merge_request|
create(:note, :system, project: project, noteable: issue, author: user, note: merge_request.to_reference(full: true))
diff --git a/spec/services/issues/related_branches_service_spec.rb b/spec/services/issues/related_branches_service_spec.rb
index 9f72e499414..d79132d98db 100644
--- a/spec/services/issues/related_branches_service_spec.rb
+++ b/spec/services/issues/related_branches_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issues::RelatedBranchesService do
+RSpec.describe Issues::RelatedBranchesService do
let_it_be(:developer) { create(:user) }
let_it_be(:issue) { create(:issue) }
let(:user) { developer }
diff --git a/spec/services/issues/reopen_service_spec.rb b/spec/services/issues/reopen_service_spec.rb
index ca878ee947a..f7416203259 100644
--- a/spec/services/issues/reopen_service_spec.rb
+++ b/spec/services/issues/reopen_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issues::ReopenService do
+RSpec.describe Issues::ReopenService do
let(:project) { create(:project) }
let(:issue) { create(:issue, :closed, project: project) }
diff --git a/spec/services/issues/reorder_service_spec.rb b/spec/services/issues/reorder_service_spec.rb
index 6d72d698b1d..b6ad488a48c 100644
--- a/spec/services/issues/reorder_service_spec.rb
+++ b/spec/services/issues/reorder_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issues::ReorderService do
+RSpec.describe Issues::ReorderService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:group) { create(:group) }
diff --git a/spec/services/issues/resolve_discussions_spec.rb b/spec/services/issues/resolve_discussions_spec.rb
index ec6624db6fc..a541d92feb2 100644
--- a/spec/services/issues/resolve_discussions_spec.rb
+++ b/spec/services/issues/resolve_discussions_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper.rb'
-describe Issues::ResolveDiscussions do
+RSpec.describe Issues::ResolveDiscussions do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 33ae2682d01..77bd540e22f 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issues::UpdateService, :mailer do
+RSpec.describe Issues::UpdateService, :mailer do
let_it_be(:user) { create(:user) }
let_it_be(:user2) { create(:user) }
let_it_be(:user3) { create(:user) }
@@ -866,5 +866,10 @@ describe Issues::UpdateService, :mailer do
end
end
end
+
+ it_behaves_like 'issuable record that supports quick actions' do
+ let(:existing_issue) { create(:issue, project: project) }
+ let(:issuable) { described_class.new(project, user, params).execute(existing_issue) }
+ end
end
end
diff --git a/spec/services/issues/zoom_link_service_spec.rb b/spec/services/issues/zoom_link_service_spec.rb
index 3fb1eae361a..56aec4fe564 100644
--- a/spec/services/issues/zoom_link_service_spec.rb
+++ b/spec/services/issues/zoom_link_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Issues::ZoomLinkService do
+RSpec.describe Issues::ZoomLinkService do
let_it_be(:user) { create(:user) }
let_it_be(:issue) { create(:issue) }
diff --git a/spec/services/jira/requests/projects_spec.rb b/spec/services/jira/requests/projects/list_service_spec.rb
index f7b9aa7c00c..51e67dd821d 100644
--- a/spec/services/jira/requests/projects_spec.rb
+++ b/spec/services/jira/requests/projects/list_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Jira::Requests::Projects do
+RSpec.describe Jira::Requests::Projects::ListService do
let(:jira_service) { create(:jira_service) }
let(:params) { {} }
@@ -32,14 +32,6 @@ describe Jira::Requests::Projects do
end
context 'with jira_service' do
- context 'when limit is invalid' do
- let(:params) { { limit: 0 } }
-
- it 'returns a paylod with no projects returned' do
- expect(subject.payload[:projects]).to be_empty
- end
- end
-
context 'when validations and params are ok' do
let(:client) { double(options: { site: 'https://jira.example.com' }) }
@@ -60,7 +52,7 @@ describe Jira::Requests::Projects do
context 'when the request does not return any values' do
before do
- expect(client).to receive(:get).and_return({ 'someKey' => 'value' })
+ expect(client).to receive(:get).and_return([])
end
it 'returns a paylod with no projects returned' do
@@ -74,19 +66,15 @@ describe Jira::Requests::Projects do
context 'when the request returns values' do
before do
- expect(client).to receive(:get).and_return(
- { 'values' => %w(project1 project2), 'isLast' => false }
- )
- expect(JIRA::Resource::Project).to receive(:build).with(client, 'project1').and_return('jira_project1')
- expect(JIRA::Resource::Project).to receive(:build).with(client, 'project2').and_return('jira_project2')
+ expect(client).to receive(:get).and_return([{ "key" => 'project1' }, { "key" => 'project2' }])
end
it 'returns a paylod with jira projets' do
payload = subject.payload
expect(subject.success?).to be_truthy
- expect(payload[:projects]).to eq(%w(jira_project1 jira_project2))
- expect(payload[:is_last]).to be_falsey
+ expect(payload[:projects].map(&:key)).to eq(%w(project1 project2))
+ expect(payload[:is_last]).to be_truthy
end
end
end
diff --git a/spec/services/jira_import/start_import_service_spec.rb b/spec/services/jira_import/start_import_service_spec.rb
index 9dc8cdb1475..a10928355ef 100644
--- a/spec/services/jira_import/start_import_service_spec.rb
+++ b/spec/services/jira_import/start_import_service_spec.rb
@@ -2,14 +2,21 @@
require 'spec_helper'
-describe JiraImport::StartImportService do
+RSpec.describe JiraImport::StartImportService do
include JiraServiceHelper
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project) }
let(:key) { 'KEY' }
+ let(:mapping) do
+ [
+ { jira_account_id: 'abc', gitlab_id: 12 },
+ { jira_account_id: 'def', gitlab_id: nil },
+ { jira_account_id: nil, gitlab_id: 1 }
+ ]
+ end
- subject { described_class.new(user, project, key).execute }
+ subject { described_class.new(user, project, key, mapping).execute }
context 'when an error is returned from the project validation' do
before do
@@ -37,7 +44,7 @@ describe JiraImport::StartImportService do
context 'when correct data provided' do
let(:fake_key) { 'some-key' }
- subject { described_class.new(user, project, fake_key).execute }
+ subject { described_class.new(user, project, fake_key, mapping).execute }
context 'when import is already running' do
let_it_be(:jira_import_state) { create(:jira_import_state, :started, project: project) }
@@ -62,35 +69,68 @@ describe JiraImport::StartImportService do
end
context 'when everything is ok' do
- it 'returns success response' do
- expect(subject).to be_a(ServiceResponse)
- expect(subject).to be_success
- end
+ context 'with complete mapping' do
+ before do
+ expect(Gitlab::JiraImport).to receive(:cache_users_mapping).with(project.id, { 'abc' => 12 })
+ end
- it 'schedules Jira import' do
- subject
+ it 'returns success response' do
+ expect(subject).to be_a(ServiceResponse)
+ expect(subject).to be_success
+ end
- expect(project.latest_jira_import).to be_scheduled
- end
+ it 'schedules Jira import' do
+ subject
- it 'creates Jira import data', :aggregate_failures do
- jira_import = subject.payload[:import_data]
+ expect(project.latest_jira_import).to be_scheduled
+ end
+
+ it 'creates Jira import data', :aggregate_failures do
+ jira_import = subject.payload[:import_data]
+
+ expect(jira_import.jira_project_xid).to eq(0)
+ expect(jira_import.jira_project_name).to eq(fake_key)
+ expect(jira_import.jira_project_key).to eq(fake_key)
+ expect(jira_import.user).to eq(user)
+ end
+
+ it 'creates Jira import label' do
+ expect { subject }.to change { Label.count }.by(1)
+ end
+
+ it 'creates Jira label title with correct number' do
+ jira_import = subject.payload[:import_data]
+ label_title = "jira-import::#{jira_import.jira_project_key}-1"
- expect(jira_import.jira_project_xid).to eq(0)
- expect(jira_import.jira_project_name).to eq(fake_key)
- expect(jira_import.jira_project_key).to eq(fake_key)
- expect(jira_import.user).to eq(user)
+ expect(jira_import.label.title).to eq(label_title)
+ end
end
- it 'creates Jira import label' do
- expect { subject }.to change { Label.count }.by(1)
+ context 'when mapping is nil' do
+ let(:mapping) { nil }
+
+ it 'returns success response' do
+ expect(Gitlab::JiraImport).not_to receive(:cache_users_mapping)
+
+ expect(subject).to be_a(ServiceResponse)
+ expect(subject).to be_success
+ end
end
- it 'creates Jira label title with correct number' do
- jira_import = subject.payload[:import_data]
- label_title = "jira-import::#{jira_import.jira_project_key}-1"
+ context 'when no mapping value is complete' do
+ let(:mapping) do
+ [
+ { jira_account_id: 'def', gitlab_id: nil },
+ { jira_account_id: nil, gitlab_id: 1 }
+ ]
+ end
- expect(jira_import.label.title).to eq(label_title)
+ it 'returns success response' do
+ expect(Gitlab::JiraImport).not_to receive(:cache_users_mapping)
+
+ expect(subject).to be_a(ServiceResponse)
+ expect(subject).to be_success
+ end
end
end
diff --git a/spec/services/jira_import/users_importer_spec.rb b/spec/services/jira_import/users_importer_spec.rb
index 28ce5f1b44b..64cdc70f612 100644
--- a/spec/services/jira_import/users_importer_spec.rb
+++ b/spec/services/jira_import/users_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe JiraImport::UsersImporter do
+RSpec.describe JiraImport::UsersImporter do
include JiraServiceHelper
let_it_be(:user) { create(:user) }
diff --git a/spec/services/jira_import/users_mapper_spec.rb b/spec/services/jira_import/users_mapper_spec.rb
index 75dbc41aa2e..e5e8279a6fb 100644
--- a/spec/services/jira_import/users_mapper_spec.rb
+++ b/spec/services/jira_import/users_mapper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe JiraImport::UsersMapper do
+RSpec.describe JiraImport::UsersMapper do
let_it_be(:project) { create(:project) }
subject { described_class.new(project, jira_users).execute }
@@ -29,9 +29,9 @@ describe JiraImport::UsersMapper do
# mapping is tracked in https://gitlab.com/gitlab-org/gitlab/-/issues/219023
let(:mapped_users) do
[
- { jira_account_id: 'abcd', jira_display_name: 'user1', jira_email: nil, gitlab_id: nil },
- { jira_account_id: 'efg', jira_display_name: nil, jira_email: nil, gitlab_id: nil },
- { jira_account_id: 'hij', jira_display_name: 'user3', jira_email: 'user3@example.com', gitlab_id: nil }
+ { jira_account_id: 'abcd', jira_display_name: 'user1', jira_email: nil, gitlab_id: nil, gitlab_username: nil, gitlab_name: nil },
+ { jira_account_id: 'efg', jira_display_name: nil, jira_email: nil, gitlab_id: nil, gitlab_username: nil, gitlab_name: nil },
+ { jira_account_id: 'hij', jira_display_name: 'user3', jira_email: 'user3@example.com', gitlab_id: nil, gitlab_username: nil, gitlab_name: nil }
]
end
diff --git a/spec/services/keys/create_service_spec.rb b/spec/services/keys/create_service_spec.rb
index 1f8b402cf08..1dbe383ad8e 100644
--- a/spec/services/keys/create_service_spec.rb
+++ b/spec/services/keys/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Keys::CreateService do
+RSpec.describe Keys::CreateService do
let(:user) { create(:user) }
let(:params) { attributes_for(:key) }
diff --git a/spec/services/keys/destroy_service_spec.rb b/spec/services/keys/destroy_service_spec.rb
index ca4bbd50c03..59ce4a941c7 100644
--- a/spec/services/keys/destroy_service_spec.rb
+++ b/spec/services/keys/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Keys::DestroyService do
+RSpec.describe Keys::DestroyService do
let(:user) { create(:user) }
subject { described_class.new(user) }
diff --git a/spec/services/keys/last_used_service_spec.rb b/spec/services/keys/last_used_service_spec.rb
index c675df39f4d..82b6b05975b 100644
--- a/spec/services/keys/last_used_service_spec.rb
+++ b/spec/services/keys/last_used_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Keys::LastUsedService do
+RSpec.describe Keys::LastUsedService do
describe '#execute', :clean_gitlab_redis_shared_state do
it 'updates the key when it has not been used recently' do
key = create(:key, last_used_at: 1.year.ago)
diff --git a/spec/services/labels/available_labels_service_spec.rb b/spec/services/labels/available_labels_service_spec.rb
index 56826257d6f..9912f2cf469 100644
--- a/spec/services/labels/available_labels_service_spec.rb
+++ b/spec/services/labels/available_labels_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Labels::AvailableLabelsService do
+RSpec.describe Labels::AvailableLabelsService do
let(:user) { create(:user) }
let(:project) { create(:project, :public, group: group) }
let(:group) { create(:group) }
diff --git a/spec/services/labels/create_service_spec.rb b/spec/services/labels/create_service_spec.rb
index f057c4401e7..7a31a5a7cae 100644
--- a/spec/services/labels/create_service_spec.rb
+++ b/spec/services/labels/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Labels::CreateService do
+RSpec.describe Labels::CreateService do
describe '#execute' do
let(:project) { create(:project) }
let(:group) { create(:group) }
diff --git a/spec/services/labels/find_or_create_service_spec.rb b/spec/services/labels/find_or_create_service_spec.rb
index 438d895392b..aa9eb0e6a0d 100644
--- a/spec/services/labels/find_or_create_service_spec.rb
+++ b/spec/services/labels/find_or_create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Labels::FindOrCreateService do
+RSpec.describe Labels::FindOrCreateService do
describe '#execute' do
let(:group) { create(:group) }
let(:project) { create(:project, namespace: group) }
diff --git a/spec/services/labels/promote_service_spec.rb b/spec/services/labels/promote_service_spec.rb
index d86281b751c..7674ec36331 100644
--- a/spec/services/labels/promote_service_spec.rb
+++ b/spec/services/labels/promote_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Labels::PromoteService do
+RSpec.describe Labels::PromoteService do
describe '#execute' do
let!(:user) { create(:user) }
diff --git a/spec/services/labels/transfer_service_spec.rb b/spec/services/labels/transfer_service_spec.rb
index a2a9c8dddf2..2c0c82ed976 100644
--- a/spec/services/labels/transfer_service_spec.rb
+++ b/spec/services/labels/transfer_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Labels::TransferService do
+RSpec.describe Labels::TransferService do
describe '#execute' do
let_it_be(:user) { create(:admin) }
diff --git a/spec/services/labels/update_service_spec.rb b/spec/services/labels/update_service_spec.rb
index 045e8af1135..af2403656af 100644
--- a/spec/services/labels/update_service_spec.rb
+++ b/spec/services/labels/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Labels::UpdateService do
+RSpec.describe Labels::UpdateService do
describe '#execute' do
let(:project) { create(:project) }
diff --git a/spec/services/lfs/file_transformer_spec.rb b/spec/services/lfs/file_transformer_spec.rb
index 13d9c369c42..e87c80b4c6c 100644
--- a/spec/services/lfs/file_transformer_spec.rb
+++ b/spec/services/lfs/file_transformer_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Lfs::FileTransformer do
+RSpec.describe Lfs::FileTransformer do
let(:project) { create(:project, :repository, :wiki_repo) }
let(:repository) { project.repository }
let(:file_content) { 'Test file content' }
diff --git a/spec/services/lfs/lock_file_service_spec.rb b/spec/services/lfs/lock_file_service_spec.rb
index 2bd62b96083..b3a121866c8 100644
--- a/spec/services/lfs/lock_file_service_spec.rb
+++ b/spec/services/lfs/lock_file_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Lfs::LockFileService do
+RSpec.describe Lfs::LockFileService do
let(:project) { create(:project) }
let(:current_user) { create(:user) }
diff --git a/spec/services/lfs/locks_finder_service_spec.rb b/spec/services/lfs/locks_finder_service_spec.rb
index fdc60e2c03f..1167212eb69 100644
--- a/spec/services/lfs/locks_finder_service_spec.rb
+++ b/spec/services/lfs/locks_finder_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Lfs::LocksFinderService do
+RSpec.describe Lfs::LocksFinderService do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:params) { {} }
diff --git a/spec/services/lfs/unlock_file_service_spec.rb b/spec/services/lfs/unlock_file_service_spec.rb
index 1334b074e84..7ab269f897a 100644
--- a/spec/services/lfs/unlock_file_service_spec.rb
+++ b/spec/services/lfs/unlock_file_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Lfs::UnlockFileService do
+RSpec.describe Lfs::UnlockFileService do
let(:project) { create(:project) }
let(:current_user) { create(:user) }
let(:lock_author) { create(:user) }
diff --git a/spec/services/members/approve_access_request_service_spec.rb b/spec/services/members/approve_access_request_service_spec.rb
index 5bbceac3dd0..e6a94fdaf84 100644
--- a/spec/services/members/approve_access_request_service_spec.rb
+++ b/spec/services/members/approve_access_request_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Members::ApproveAccessRequestService do
+RSpec.describe Members::ApproveAccessRequestService do
let(:project) { create(:project, :public) }
let(:group) { create(:group, :public) }
let(:current_user) { create(:user) }
diff --git a/spec/services/members/create_service_spec.rb b/spec/services/members/create_service_spec.rb
index 674fe0f666e..00b5ff59e48 100644
--- a/spec/services/members/create_service_spec.rb
+++ b/spec/services/members/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Members::CreateService do
+RSpec.describe Members::CreateService do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:project_user) { create(:user) }
diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb
index 73ac0bd7716..13e7b4c1006 100644
--- a/spec/services/members/destroy_service_spec.rb
+++ b/spec/services/members/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Members::DestroyService do
+RSpec.describe Members::DestroyService do
let(:current_user) { create(:user) }
let(:member_user) { create(:user) }
let(:group) { create(:group, :public) }
@@ -25,6 +25,7 @@ describe Members::DestroyService do
before do
type = member.is_a?(GroupMember) ? 'Group' : 'Project'
expect(TodosDestroyer::EntityLeaveWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, type)
+ expect(MembersDestroyer::UnassignIssuablesWorker).to receive(:perform_async).with(member.user_id, member.source_id, type) if opts[:unassign_issuables]
end
it 'destroys the member' do
@@ -56,12 +57,23 @@ describe Members::DestroyService do
expect(member_user.todos_pending_count).to be(1)
expect(member_user.todos_done_count).to be(1)
- described_class.new(current_user).execute(member, opts)
+ service = described_class.new(current_user)
+
+ if opts[:unassign_issuables]
+ expect(service).to receive(:enqueue_unassign_issuables).with(member)
+ end
+
+ service.execute(member, opts)
expect(member_user.assigned_open_merge_requests_count).to be(0)
expect(member_user.assigned_open_issues_count).to be(0)
expect(member_user.todos_pending_count).to be(0)
expect(member_user.todos_done_count).to be(0)
+
+ unless opts[:unassign_issuables]
+ expect(member_user.assigned_merge_requests.opened.count).to be(1)
+ expect(member_user.assigned_issues.opened.count).to be(1)
+ end
end
end
@@ -100,7 +112,7 @@ describe Members::DestroyService do
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
it_behaves_like 'a service destroying a member with access' do
- let(:opts) { { skip_authorization: true } }
+ let(:opts) { { skip_authorization: true, unassign_issuables: true } }
end
end
@@ -114,7 +126,7 @@ describe Members::DestroyService do
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
it_behaves_like 'a service destroying a member with access' do
- let(:opts) { { skip_authorization: true } }
+ let(:opts) { { skip_authorization: true, unassign_issuables: true } }
end
end
end
@@ -133,6 +145,31 @@ describe Members::DestroyService do
end
it_behaves_like 'a service destroying a member with access'
+
+ context 'unassign issuables' do
+ it_behaves_like 'a service destroying a member with access' do
+ let(:opts) { { unassign_issuables: true } }
+ end
+ end
+ end
+
+ context 'with a project bot member' do
+ let(:member) { group_project.members.find_by(user_id: member_user.id) }
+ let(:member_user) { create(:user, :project_bot) }
+
+ before do
+ group_project.add_maintainer(member_user)
+ end
+
+ context 'when the destroy_bot flag is true' do
+ it_behaves_like 'a service destroying a member with access' do
+ let(:opts) { { destroy_bot: true } }
+ end
+ end
+
+ context 'when the destroy_bot flag is not specified' do
+ it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
+ end
end
context 'with a group member' do
@@ -143,6 +180,12 @@ describe Members::DestroyService do
end
it_behaves_like 'a service destroying a member with access'
+
+ context 'unassign issuables' do
+ it_behaves_like 'a service destroying a member with access' do
+ let(:opts) { { unassign_issuables: true } }
+ end
+ end
end
end
end
diff --git a/spec/services/members/request_access_service_spec.rb b/spec/services/members/request_access_service_spec.rb
index a0f7ae91bdb..69eea2aea4b 100644
--- a/spec/services/members/request_access_service_spec.rb
+++ b/spec/services/members/request_access_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Members::RequestAccessService do
+RSpec.describe Members::RequestAccessService do
let(:user) { create(:user) }
shared_examples 'a service raising Gitlab::Access::AccessDeniedError' do
diff --git a/spec/services/members/unassign_issuables_service_spec.rb b/spec/services/members/unassign_issuables_service_spec.rb
new file mode 100644
index 00000000000..3f7ccb7bab3
--- /dev/null
+++ b/spec/services/members/unassign_issuables_service_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Members::UnassignIssuablesService do
+ let_it_be(:group) { create(:group, :private) }
+ let_it_be(:project) { create(:project, group: group) }
+ let_it_be(:user, reload: true) { create(:user) }
+ let_it_be(:assigned_issue1, reload: true) { create(:issue, project: project, assignees: [user]) }
+ let_it_be(:assigned_issue2, reload: true) { create(:issue, project: project, assignees: [user]) }
+
+ let!(:assigned_merge_request1) { create(:merge_request, :simple, :closed, target_project: project, source_project: project, assignees: [user], title: 'Test1') }
+ let!(:assigned_merge_request2) { create(:merge_request, :simple, :opened, target_project: project, source_project: project, assignees: [user], title: 'Test2') }
+
+ describe '#execute' do
+ RSpec.shared_examples 'un-assigning issuables' do |issue_count, mr_count, open_issue_count, open_mr_count|
+ it 'removes issuable assignments', :aggregate_failures do
+ expect(user.assigned_issues.count).to eq(issue_count)
+ expect(user.assigned_merge_requests.count).to eq(mr_count)
+
+ subject
+
+ expect(user.assigned_issues.count).to eq(0)
+ expect(user.assigned_merge_requests.count).to eq(0)
+ end
+
+ it 'invalidates user cache', :aggregate_failures, :clean_gitlab_redis_cache do
+ expect(user.assigned_open_merge_requests_count).to eq(open_mr_count)
+ expect(user.assigned_open_issues_count).to eq(open_issue_count)
+
+ subject
+
+ expect(user.assigned_open_merge_requests_count).to eq(0)
+ expect(user.assigned_open_issues_count).to eq(0)
+ end
+ end
+
+ context 'when a user leaves a project' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ subject { described_class.new(user, project).execute }
+
+ it_behaves_like 'un-assigning issuables', 2, 2, 2, 1
+ end
+
+ context 'when a user leaves a group' do
+ let_it_be(:project2) { create(:project, group: group) }
+
+ let_it_be(:assigned_issue3, reload: true) { create(:issue, project: project2, assignees: [user]) }
+ let_it_be(:assigned_issue4, reload: true) { create(:issue, project: project2, assignees: [user]) }
+
+ let!(:assigned_merge_request3) { create(:merge_request, :simple, :closed, target_project: project2, source_project: project2, assignees: [user], title: 'Test1') }
+ let!(:assigned_merge_request4) { create(:merge_request, :simple, :opened, target_project: project2, source_project: project2, assignees: [user], title: 'Test2') }
+
+ before do
+ group.add_maintainer(user)
+ end
+
+ subject { described_class.new(user, group).execute }
+
+ it_behaves_like 'un-assigning issuables', 4, 4, 4, 2
+ end
+ end
+end
diff --git a/spec/services/members/update_service_spec.rb b/spec/services/members/update_service_spec.rb
index a8b28127df2..f510916558b 100644
--- a/spec/services/members/update_service_spec.rb
+++ b/spec/services/members/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Members::UpdateService do
+RSpec.describe Members::UpdateService do
let(:project) { create(:project, :public) }
let(:group) { create(:group, :public) }
let(:current_user) { create(:user) }
diff --git a/spec/services/merge_requests/add_context_service_spec.rb b/spec/services/merge_requests/add_context_service_spec.rb
index d4e95c2f1ea..58ed91218d1 100644
--- a/spec/services/merge_requests/add_context_service_spec.rb
+++ b/spec/services/merge_requests/add_context_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::AddContextService do
+RSpec.describe MergeRequests::AddContextService do
let(:project) { create(:project, :repository) }
let(:admin) { create(:admin) }
let(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: admin) }
diff --git a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
index 0cec1e7be22..3c81ad6722d 100644
--- a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
+++ b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::AddTodoWhenBuildFailsService do
+RSpec.describe MergeRequests::AddTodoWhenBuildFailsService do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:sha) { '1234567890abcdef1234567890abcdef12345678' }
diff --git a/spec/services/merge_requests/after_create_service_spec.rb b/spec/services/merge_requests/after_create_service_spec.rb
index 4aefe5f7dae..840b7bc0a1c 100644
--- a/spec/services/merge_requests/after_create_service_spec.rb
+++ b/spec/services/merge_requests/after_create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::AfterCreateService do
+RSpec.describe MergeRequests::AfterCreateService do
let_it_be(:merge_request) { create(:merge_request) }
subject(:after_create_service) do
diff --git a/spec/services/merge_requests/approval_service_spec.rb b/spec/services/merge_requests/approval_service_spec.rb
new file mode 100644
index 00000000000..124501f17d5
--- /dev/null
+++ b/spec/services/merge_requests/approval_service_spec.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe MergeRequests::ApprovalService do
+ describe '#execute' do
+ let(:user) { create(:user) }
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.project }
+ let!(:todo) { create(:todo, user: user, project: project, target: merge_request) }
+
+ subject(:service) { described_class.new(project, user) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ context 'with invalid approval' do
+ before do
+ allow(merge_request.approvals).to receive(:new).and_return(double(save: false))
+ end
+
+ it 'does not create an approval note' do
+ expect(SystemNoteService).not_to receive(:approve_mr)
+
+ service.execute(merge_request)
+ end
+
+ it 'does not mark pending todos as done' do
+ service.execute(merge_request)
+
+ expect(todo.reload).to be_pending
+ end
+ end
+
+ context 'with valid approval' do
+ it 'creates an approval note and marks pending todos as done' do
+ expect(SystemNoteService).to receive(:approve_mr).with(merge_request, user)
+ expect(merge_request.approvals).to receive(:reset)
+
+ service.execute(merge_request)
+
+ expect(todo.reload).to be_done
+ end
+
+ it 'creates approve MR event' do
+ expect_next_instance_of(EventCreateService) do |instance|
+ expect(instance).to receive(:approve_mr)
+ .with(merge_request, user)
+ end
+
+ service.execute(merge_request)
+ end
+
+ context 'with remaining approvals' do
+ it 'fires an approval webhook' do
+ expect(service).to receive(:execute_hooks).with(merge_request, 'approved')
+
+ service.execute(merge_request)
+ end
+ end
+ end
+
+ context 'user cannot update the merge request' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'does not update approvals' do
+ expect { service.execute(merge_request) }.not_to change { merge_request.approvals.size }
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/assign_issues_service_spec.rb b/spec/services/merge_requests/assign_issues_service_spec.rb
index c0b57b9092d..6398e8c533e 100644
--- a/spec/services/merge_requests/assign_issues_service_spec.rb
+++ b/spec/services/merge_requests/assign_issues_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::AssignIssuesService do
+RSpec.describe MergeRequests::AssignIssuesService do
let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) }
let(:issue) { create(:issue, project: project) }
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
index 9b358839c06..f99be26927d 100644
--- a/spec/services/merge_requests/build_service_spec.rb
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe MergeRequests::BuildService do
+RSpec.describe MergeRequests::BuildService do
using RSpec::Parameterized::TableSyntax
include RepoHelpers
include ProjectForksHelper
@@ -189,8 +189,8 @@ describe MergeRequests::BuildService do
it_behaves_like 'allows the merge request to be created'
- it 'adds a WIP prefix to the merge request title' do
- expect(merge_request.title).to eq('WIP: Feature branch')
+ it 'adds a Draft prefix to the merge request title' do
+ expect(merge_request.title).to eq('Draft: Feature branch')
end
end
diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb
index 0e51de48fb1..e518e439a84 100644
--- a/spec/services/merge_requests/close_service_spec.rb
+++ b/spec/services/merge_requests/close_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::CloseService do
+RSpec.describe MergeRequests::CloseService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:guest) { create(:user) }
diff --git a/spec/services/merge_requests/conflicts/list_service_spec.rb b/spec/services/merge_requests/conflicts/list_service_spec.rb
index 13d69307084..14133731e37 100644
--- a/spec/services/merge_requests/conflicts/list_service_spec.rb
+++ b/spec/services/merge_requests/conflicts/list_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::Conflicts::ListService do
+RSpec.describe MergeRequests::Conflicts::ListService do
describe '#can_be_resolved_in_ui?' do
def create_merge_request(source_branch, target_branch = 'conflict-start')
create(:merge_request, source_branch: source_branch, target_branch: target_branch, merge_status: :unchecked) do |mr|
@@ -30,6 +30,7 @@ describe MergeRequests::Conflicts::ListService do
it 'returns a falsey value when one of the MR branches is missing' do
merge_request = create_merge_request('conflict-resolvable')
merge_request.project.repository.rm_branch(merge_request.author, 'conflict-resolvable')
+ merge_request.clear_memoized_source_branch_exists
expect(conflicts_service(merge_request).can_be_resolved_in_ui?).to be_falsey
end
diff --git a/spec/services/merge_requests/conflicts/resolve_service_spec.rb b/spec/services/merge_requests/conflicts/resolve_service_spec.rb
index 74f20094081..c4d50124ca9 100644
--- a/spec/services/merge_requests/conflicts/resolve_service_spec.rb
+++ b/spec/services/merge_requests/conflicts/resolve_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::Conflicts::ResolveService do
+RSpec.describe MergeRequests::Conflicts::ResolveService do
include ProjectForksHelper
let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/services/merge_requests/create_from_issue_service_spec.rb b/spec/services/merge_requests/create_from_issue_service_spec.rb
index fb1bb308170..fa70ad8c559 100644
--- a/spec/services/merge_requests/create_from_issue_service_spec.rb
+++ b/spec/services/merge_requests/create_from_issue_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::CreateFromIssueService do
+RSpec.describe MergeRequests::CreateFromIssueService do
include ProjectForksHelper
let(:project) { create(:project, :repository) }
@@ -163,10 +163,10 @@ describe MergeRequests::CreateFromIssueService do
expect(result[:merge_request].milestone_id).to eq(milestone_id)
end
- it 'sets the merge request title to: "WIP: Resolves "$issue-title"' do
+ it 'sets the merge request title to: "Draft: Resolves "$issue-title"' do
result = service.execute
- expect(result[:merge_request].title).to eq("WIP: Resolve \"#{issue.title}\"")
+ expect(result[:merge_request].title).to eq("Draft: Resolve \"#{issue.title}\"")
end
end
@@ -193,10 +193,10 @@ describe MergeRequests::CreateFromIssueService do
it_behaves_like 'a service that creates a merge request from an issue'
- it 'sets the merge request title to: "WIP: $issue-branch-name', :sidekiq_might_not_need_inline do
+ it 'sets the merge request title to: "Draft: $issue-branch-name', :sidekiq_might_not_need_inline do
result = service.execute
- expect(result[:merge_request].title).to eq("WIP: #{issue.to_branch_name.titleize.humanize}")
+ expect(result[:merge_request].title).to eq("Draft: #{issue.to_branch_name.titleize.humanize}")
end
end
end
diff --git a/spec/services/merge_requests/create_pipeline_service_spec.rb b/spec/services/merge_requests/create_pipeline_service_spec.rb
index 9eb28759061..db46bd37eea 100644
--- a/spec/services/merge_requests/create_pipeline_service_spec.rb
+++ b/spec/services/merge_requests/create_pipeline_service_spec.rb
@@ -2,10 +2,13 @@
require 'spec_helper'
-describe MergeRequests::CreatePipelineService do
+RSpec.describe MergeRequests::CreatePipelineService do
+ include ProjectForksHelper
+
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
- let(:service) { described_class.new(project, user, params) }
+ let(:service) { described_class.new(project, actor, params) }
+ let(:actor) { user }
let(:params) { {} }
before do
@@ -26,11 +29,13 @@ describe MergeRequests::CreatePipelineService do
let(:merge_request) do
create(:merge_request,
source_branch: 'feature',
- source_project: project,
+ source_project: source_project,
target_branch: 'master',
target_project: project)
end
+ let(:source_project) { project }
+
it 'creates a detached merge request pipeline' do
expect { subject }.to change { Ci::Pipeline.count }.by(1)
@@ -42,6 +47,50 @@ describe MergeRequests::CreatePipelineService do
expect(subject.source).to eq('merge_request_event')
end
+ context 'with fork merge request' do
+ let_it_be(:forked_project) { fork_project(project, nil, repository: true, target_project: create(:project, :private, :repository)) }
+ let(:source_project) { forked_project }
+
+ context 'when actor has permission to create pipelines in target project' do
+ let(:actor) { user }
+
+ it 'creates a pipeline in the target project' do
+ expect(subject.project).to eq(project)
+ end
+
+ context 'when ci_allow_to_create_merge_request_pipelines_in_target_project feature flag is disabled' do
+ before do
+ stub_feature_flags(ci_allow_to_create_merge_request_pipelines_in_target_project: false)
+ end
+
+ it 'creates a pipeline in the source project' do
+ expect(subject.project).to eq(source_project)
+ end
+ end
+ end
+
+ context 'when actor has permission to create pipelines in forked project' do
+ let(:actor) { fork_user }
+ let(:fork_user) { create(:user) }
+
+ before do
+ source_project.add_developer(fork_user)
+ end
+
+ it 'creates a pipeline in the source project' do
+ expect(subject.project).to eq(source_project)
+ end
+ end
+
+ context 'when actor does not have permission to create pipelines' do
+ let(:actor) { create(:user) }
+
+ it 'returns nothing' do
+ expect(subject.full_error_messages).to include('Insufficient permissions to create a new pipeline')
+ end
+ end
+ end
+
context 'when service is called multiple times' do
it 'creates a pipeline once' do
expect do
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index bb40c399b6e..a8661f027e8 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
+RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
include ProjectForksHelper
let(:project) { create(:project, :repository) }
@@ -216,11 +216,12 @@ describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
target_project.add_maintainer(user)
end
- it 'create legacy detached merge request pipeline for fork merge request' do
+ it 'create detached merge request pipeline for fork merge request' do
merge_request.reload
- expect(merge_request.actual_head_pipeline)
- .to be_legacy_detached_merge_request_pipeline
+ head_pipeline = merge_request.actual_head_pipeline
+ expect(head_pipeline).to be_detached_merge_request_pipeline
+ expect(head_pipeline.project).to eq(target_project)
end
end
@@ -339,13 +340,14 @@ describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
end
end
- it_behaves_like 'new issuable record that supports quick actions' do
+ it_behaves_like 'issuable record that supports quick actions' do
let(:default_params) do
{
source_branch: 'feature',
target_branch: 'master'
}
end
+ let(:issuable) { described_class.new(project, user, params).execute }
end
context 'Quick actions' do
diff --git a/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb b/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb
index 2adf808619d..377615bbc6f 100644
--- a/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb
+++ b/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::DeleteNonLatestDiffsService, :clean_gitlab_redis_shared_state do
+RSpec.describe MergeRequests::DeleteNonLatestDiffsService, :clean_gitlab_redis_shared_state do
let(:merge_request) { create(:merge_request) }
let!(:subject) { described_class.new(merge_request) }
diff --git a/spec/services/merge_requests/ff_merge_service_spec.rb b/spec/services/merge_requests/ff_merge_service_spec.rb
index 415b351e13a..c3da02273a4 100644
--- a/spec/services/merge_requests/ff_merge_service_spec.rb
+++ b/spec/services/merge_requests/ff_merge_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::FfMergeService do
+RSpec.describe MergeRequests::FfMergeService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:merge_request) do
diff --git a/spec/services/merge_requests/get_urls_service_spec.rb b/spec/services/merge_requests/get_urls_service_spec.rb
index 8cc627b64d9..053752626dc 100644
--- a/spec/services/merge_requests/get_urls_service_spec.rb
+++ b/spec/services/merge_requests/get_urls_service_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe MergeRequests::GetUrlsService do
+RSpec.describe MergeRequests::GetUrlsService do
include ProjectForksHelper
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/services/merge_requests/link_lfs_objects_service_spec.rb b/spec/services/merge_requests/link_lfs_objects_service_spec.rb
index f07cf13e4f2..c1765e3a2ab 100644
--- a/spec/services/merge_requests/link_lfs_objects_service_spec.rb
+++ b/spec/services/merge_requests/link_lfs_objects_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::LinkLfsObjectsService, :sidekiq_inline do
+RSpec.describe MergeRequests::LinkLfsObjectsService, :sidekiq_inline do
include ProjectForksHelper
include RepoHelpers
diff --git a/spec/services/merge_requests/merge_orchestration_service_spec.rb b/spec/services/merge_requests/merge_orchestration_service_spec.rb
index c50f20d7703..67dbb5a1a01 100644
--- a/spec/services/merge_requests/merge_orchestration_service_spec.rb
+++ b/spec/services/merge_requests/merge_orchestration_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::MergeOrchestrationService do
+RSpec.describe MergeRequests::MergeOrchestrationService do
let_it_be(:maintainer) { create(:user) }
let(:merge_params) { { sha: merge_request.diff_head_sha } }
let(:user) { maintainer }
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index 2274d917527..11e341994f7 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::MergeService do
+RSpec.describe MergeRequests::MergeService do
let_it_be(:user) { create(:user) }
let_it_be(:user2) { create(:user) }
let(:merge_request) { create(:merge_request, :simple, author: user2, assignees: [user2]) }
@@ -360,6 +360,25 @@ describe MergeRequests::MergeService do
expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message))
end
+ context 'when squashing is required' do
+ before do
+ merge_request.update!(source_branch: 'master', target_branch: 'feature')
+ merge_request.target_project.project_setting.squash_always!
+ end
+
+ it 'raises an error if squashing is not done' do
+ error_message = 'requires squashing commits'
+
+ service.execute(merge_request)
+
+ expect(merge_request).to be_open
+
+ expect(merge_request.merge_commit_sha).to be_nil
+ expect(merge_request.merge_error).to include(error_message)
+ expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message))
+ end
+ end
+
context 'when squashing' do
before do
merge_request.update!(source_branch: 'master', target_branch: 'feature')
diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb
index 596d46f3c43..b482e8d6724 100644
--- a/spec/services/merge_requests/merge_to_ref_service_spec.rb
+++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::MergeToRefService do
+RSpec.describe MergeRequests::MergeToRefService do
shared_examples_for 'MergeService for target ref' do
it 'target_ref has the same state of target branch' do
repo = merge_request.target_project.repository
diff --git a/spec/services/merge_requests/mergeability_check_service_spec.rb b/spec/services/merge_requests/mergeability_check_service_spec.rb
index 45519ddf3d3..543da46f883 100644
--- a/spec/services/merge_requests/mergeability_check_service_spec.rb
+++ b/spec/services/merge_requests/mergeability_check_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shared_state do
+RSpec.describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shared_state do
shared_examples_for 'unmergeable merge request' do
it 'updates or keeps merge status as cannot_be_merged' do
subject
diff --git a/spec/services/merge_requests/migrate_external_diffs_service_spec.rb b/spec/services/merge_requests/migrate_external_diffs_service_spec.rb
index 233b944624f..6ea8626ba73 100644
--- a/spec/services/merge_requests/migrate_external_diffs_service_spec.rb
+++ b/spec/services/merge_requests/migrate_external_diffs_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::MigrateExternalDiffsService do
+RSpec.describe MergeRequests::MigrateExternalDiffsService do
let(:merge_request) { create(:merge_request) }
let(:diff) { merge_request.merge_request_diff }
diff --git a/spec/services/merge_requests/post_merge_service_spec.rb b/spec/services/merge_requests/post_merge_service_spec.rb
index fff6ddf3928..a51a896ca96 100644
--- a/spec/services/merge_requests/post_merge_service_spec.rb
+++ b/spec/services/merge_requests/post_merge_service_spec.rb
@@ -2,11 +2,13 @@
require 'spec_helper'
-describe MergeRequests::PostMergeService do
+RSpec.describe MergeRequests::PostMergeService do
let(:user) { create(:user) }
let(:merge_request) { create(:merge_request, assignees: [user]) }
let(:project) { merge_request.project }
+ subject { described_class.new(project, user).execute(merge_request) }
+
before do
project.add_maintainer(user)
end
@@ -19,10 +21,7 @@ describe MergeRequests::PostMergeService do
project.open_merge_requests_count
merge_request.update!(state: 'merged')
- service = described_class.new(project, user, {})
-
- expect { service.execute(merge_request) }
- .to change { project.open_merge_requests_count }.from(1).to(0)
+ expect { subject }.to change { project.open_merge_requests_count }.from(1).to(0)
end
it 'updates metrics' do
@@ -35,7 +34,7 @@ describe MergeRequests::PostMergeService do
expect(metrics_service).to receive(:merge)
- described_class.new(project, user, {}).execute(merge_request)
+ subject
end
it 'deletes non-latest diffs' do
@@ -45,7 +44,7 @@ describe MergeRequests::PostMergeService do
.to receive(:new).with(merge_request)
.and_return(diff_removal_service)
- described_class.new(project, user, {}).execute(merge_request)
+ subject
expect(diff_removal_service).to have_received(:execute)
end
@@ -56,21 +55,63 @@ describe MergeRequests::PostMergeService do
issue = create(:issue, project: project)
allow(merge_request).to receive(:visible_closing_issues_for).and_return([issue])
- expect_next_instance_of(Issues::CloseService) do |service|
- allow(service).to receive(:execute).with(issue, commit: merge_request).and_raise(RuntimeError)
+ expect_next_instance_of(Issues::CloseService) do |close_service|
+ allow(close_service).to receive(:execute).with(issue, commit: merge_request).and_raise(RuntimeError)
end
- expect { described_class.new(project, user).execute(merge_request) }.to raise_error(RuntimeError)
+ expect { subject }.to raise_error(RuntimeError)
expect(merge_request.reload).to be_merged
end
it 'clean up environments for the merge request' do
- expect_next_instance_of(Ci::StopEnvironmentsService) do |service|
- expect(service).to receive(:execute_for_merge_request).with(merge_request)
+ expect_next_instance_of(Ci::StopEnvironmentsService) do |stop_environment_service|
+ expect(stop_environment_service).to receive(:execute_for_merge_request).with(merge_request)
end
- described_class.new(project, user).execute(merge_request)
+ subject
+ end
+
+ context 'when the merge request has review apps' do
+ it 'cancels all review app deployments' do
+ pipeline = create(:ci_pipeline,
+ source: :merge_request_event,
+ merge_request: merge_request,
+ project: project,
+ sha: merge_request.diff_head_sha,
+ merge_requests_as_head_pipeline: [merge_request])
+
+ review_env_a = create(:environment, project: project, state: :available, name: 'review/a')
+ review_env_b = create(:environment, project: project, state: :available, name: 'review/b')
+ review_env_c = create(:environment, project: project, state: :stopped, name: 'review/c')
+ deploy_env = create(:environment, project: project, state: :available, name: 'deploy')
+
+ review_job_a1 = create(:ci_build, :with_deployment, :start_review_app,
+ pipeline: pipeline, project: project, environment: review_env_a.name)
+ review_job_a2 = create(:ci_build, :with_deployment, :start_review_app,
+ pipeline: pipeline, project: project, environment: review_env_a.name)
+ finished_review_job_a = create(:ci_build, :with_deployment, :start_review_app,
+ pipeline: pipeline, project: project, status: :success, environment: review_env_a.name)
+ review_job_b1 = create(:ci_build, :with_deployment, :start_review_app,
+ pipeline: pipeline, project: project, environment: review_env_b.name)
+ review_job_b2 = create(:ci_build, :start_review_app,
+ pipeline: pipeline, project: project, environment: review_env_b.name)
+ review_job_c1 = create(:ci_build, :with_deployment, :start_review_app,
+ pipeline: pipeline, project: project, environment: review_env_c.name)
+ deploy_job = create(:ci_build, :with_deployment, :deploy_to_production,
+ pipeline: pipeline, project: project, environment: deploy_env.name)
+
+ subject
+
+ expect(review_job_a1.reload.canceled?).to be true
+ expect(review_job_a2.reload.canceled?).to be true
+ expect(finished_review_job_a.reload.status).to eq "success"
+ expect(finished_review_job_a.reload.canceled?).to be false
+ expect(review_job_b1.reload.canceled?).to be true
+ expect(review_job_b2.reload.canceled?).to be false
+ expect(review_job_c1.reload.canceled?).to be false
+ expect(deploy_job.reload.canceled?).to be false
+ end
end
end
end
diff --git a/spec/services/merge_requests/push_options_handler_service_spec.rb b/spec/services/merge_requests/push_options_handler_service_spec.rb
index 420c8513c72..55f92d6bd0a 100644
--- a/spec/services/merge_requests/push_options_handler_service_spec.rb
+++ b/spec/services/merge_requests/push_options_handler_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::PushOptionsHandlerService do
+RSpec.describe MergeRequests::PushOptionsHandlerService do
include ProjectForksHelper
let(:user) { create(:user) }
diff --git a/spec/services/merge_requests/pushed_branches_service_spec.rb b/spec/services/merge_requests/pushed_branches_service_spec.rb
index 7b5d505f4d9..6e9c77bd3b6 100644
--- a/spec/services/merge_requests/pushed_branches_service_spec.rb
+++ b/spec/services/merge_requests/pushed_branches_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::PushedBranchesService do
+RSpec.describe MergeRequests::PushedBranchesService do
let(:project) { create(:project) }
let!(:service) { described_class.new(project, nil, changes: pushed_branches) }
diff --git a/spec/services/merge_requests/rebase_service_spec.rb b/spec/services/merge_requests/rebase_service_spec.rb
index 69d555f838d..2e525f2ed01 100644
--- a/spec/services/merge_requests/rebase_service_spec.rb
+++ b/spec/services/merge_requests/rebase_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::RebaseService do
+RSpec.describe MergeRequests::RebaseService do
include ProjectForksHelper
let(:user) { create(:user) }
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index e60ff6eb98a..18c4cef7087 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::RefreshService do
+RSpec.describe MergeRequests::RefreshService do
include ProjectForksHelper
include ProjectHelpers
@@ -225,12 +225,13 @@ describe MergeRequests::RefreshService do
context 'when service runs on forked project' do
let(:project) { @fork_project }
- it 'creates legacy detached merge request pipeline for fork merge request', :sidekiq_might_not_need_inline do
+ it 'creates detached merge request pipeline for fork merge request', :sidekiq_inline do
expect { subject }
.to change { @fork_merge_request.pipelines_for_merge_request.count }.by(1)
- expect(@fork_merge_request.pipelines_for_merge_request.first)
- .to be_legacy_detached_merge_request_pipeline
+ merge_request_pipeline = @fork_merge_request.pipelines_for_merge_request.first
+ expect(merge_request_pipeline).to be_detached_merge_request_pipeline
+ expect(merge_request_pipeline.project).to eq(@project)
end
end
diff --git a/spec/services/merge_requests/reload_diffs_service_spec.rb b/spec/services/merge_requests/reload_diffs_service_spec.rb
index d2444af1b0f..3d5b65207e6 100644
--- a/spec/services/merge_requests/reload_diffs_service_spec.rb
+++ b/spec/services/merge_requests/reload_diffs_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::ReloadDiffsService, :use_clean_rails_memory_store_caching do
+RSpec.describe MergeRequests::ReloadDiffsService, :use_clean_rails_memory_store_caching do
let(:current_user) { create(:user) }
let(:merge_request) { create(:merge_request) }
let(:subject) { described_class.new(merge_request, current_user) }
@@ -34,10 +34,8 @@ describe MergeRequests::ReloadDiffsService, :use_clean_rails_memory_store_cachin
context 'cache clearing' do
it 'clears the cache for older diffs on the merge request' do
- old_diff = merge_request.merge_request_diff
- old_cache_key = old_diff.diffs_collection.cache_key
-
- expect_any_instance_of(Redis).to receive(:del).with(old_cache_key).and_call_original
+ expect_any_instance_of(Redis).to receive(:del).once.and_call_original
+ expect(Rails.cache).to receive(:delete).once.and_call_original
subject.execute
end
diff --git a/spec/services/merge_requests/remove_approval_service_spec.rb b/spec/services/merge_requests/remove_approval_service_spec.rb
new file mode 100644
index 00000000000..40da928e832
--- /dev/null
+++ b/spec/services/merge_requests/remove_approval_service_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe MergeRequests::RemoveApprovalService do
+ describe '#execute' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ let!(:existing_approval) { create(:approval, merge_request: merge_request) }
+
+ subject(:service) { described_class.new(project, user) }
+
+ def execute!
+ service.execute(merge_request)
+ end
+
+ before do
+ project.add_developer(user)
+ end
+
+ context 'with a user who has approved' do
+ let!(:approval) { create(:approval, user: user, merge_request: merge_request) }
+
+ it 'removes the approval' do
+ expect { execute! }.to change { merge_request.approvals.size }.from(2).to(1)
+ end
+
+ it 'creates an unapproval note and triggers web hook' do
+ expect(service).to receive(:execute_hooks).with(merge_request, 'unapproved')
+ expect(SystemNoteService).to receive(:unapprove_mr)
+
+ execute!
+ end
+ end
+
+ context 'with a user who has not approved' do
+ it 'does not create an unapproval note and triggers web hook' do
+ expect(service).not_to receive(:execute_hooks)
+ expect(SystemNoteService).not_to receive(:unapprove_mr)
+
+ execute!
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb
index 3807c44b01f..0066834180e 100644
--- a/spec/services/merge_requests/reopen_service_spec.rb
+++ b/spec/services/merge_requests/reopen_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::ReopenService do
+RSpec.describe MergeRequests::ReopenService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:guest) { create(:user) }
diff --git a/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb b/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb
index 29896db58ac..874cf66659a 100644
--- a/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb
+++ b/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::ResolvedDiscussionNotificationService do
+RSpec.describe MergeRequests::ResolvedDiscussionNotificationService do
let(:merge_request) { create(:merge_request) }
let(:user) { create(:user) }
let(:project) { merge_request.project }
diff --git a/spec/services/merge_requests/squash_service_spec.rb b/spec/services/merge_requests/squash_service_spec.rb
index a53314ed737..1ec1dc0f6eb 100644
--- a/spec/services/merge_requests/squash_service_spec.rb
+++ b/spec/services/merge_requests/squash_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::SquashService do
+RSpec.describe MergeRequests::SquashService do
include GitHelpers
let(:service) { described_class.new(project, user, { merge_request: merge_request }) }
@@ -131,6 +131,42 @@ describe MergeRequests::SquashService do
include_examples 'the squash succeeds'
end
+ context 'when squashing is disabled by default on the project' do
+ # Squashing is disabled by default, but it should still allow you
+ # to squash-and-merge if selected through the UI
+ let(:merge_request) { merge_request_with_only_new_files }
+
+ before do
+ merge_request.project.project_setting.squash_default_off!
+ end
+
+ include_examples 'the squash succeeds'
+ end
+
+ context 'when squashing is forbidden on the project' do
+ let(:merge_request) { merge_request_with_only_new_files }
+
+ before do
+ merge_request.project.project_setting.squash_never!
+ end
+
+ it 'raises a squash error' do
+ expect(service.execute).to match(
+ status: :error,
+ message: a_string_including('does not allow squashing commits when merge requests are accepted'))
+ end
+ end
+
+ context 'when squashing is enabled by default on the project' do
+ let(:merge_request) { merge_request_with_only_new_files }
+
+ before do
+ merge_request.project.project_setting.squash_always!
+ end
+
+ include_examples 'the squash succeeds'
+ end
+
context 'when squashing with files too large to display' do
let(:merge_request) { merge_request_with_large_files }
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 2b934b24757..c3433c8c9d2 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::UpdateService, :mailer do
+RSpec.describe MergeRequests::UpdateService, :mailer do
include ProjectForksHelper
let(:group) { create(:group, :public) }
@@ -737,5 +737,10 @@ describe MergeRequests::UpdateService, :mailer do
.to change { merge_request.reload.force_remove_source_branch? }.from(nil).to(true)
end
end
+
+ it_behaves_like 'issuable record that supports quick actions' do
+ let(:existing_merge_request) { create(:merge_request, source_project: project) }
+ let(:issuable) { described_class.new(project, user, params).execute(existing_merge_request) }
+ end
end
end
diff --git a/spec/services/metrics/dashboard/annotations/create_service_spec.rb b/spec/services/metrics/dashboard/annotations/create_service_spec.rb
index 7dabca3c860..c3fe7238047 100644
--- a/spec/services/metrics/dashboard/annotations/create_service_spec.rb
+++ b/spec/services/metrics/dashboard/annotations/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::Annotations::CreateService do
+RSpec.describe Metrics::Dashboard::Annotations::CreateService do
let_it_be(:user) { create(:user) }
let(:description) { 'test annotation' }
let(:dashboard_path) { 'config/prometheus/common_metrics.yml' }
diff --git a/spec/services/metrics/dashboard/annotations/delete_service_spec.rb b/spec/services/metrics/dashboard/annotations/delete_service_spec.rb
index 95825db6902..ec2bd3772bf 100644
--- a/spec/services/metrics/dashboard/annotations/delete_service_spec.rb
+++ b/spec/services/metrics/dashboard/annotations/delete_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::Annotations::DeleteService do
+RSpec.describe Metrics::Dashboard::Annotations::DeleteService do
let(:user) { create(:user) }
let(:service_instance) { described_class.new(user, annotation) }
diff --git a/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb b/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb
index 3d26ab2ede5..4a226fe386c 100644
--- a/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::CloneDashboardService, :use_clean_rails_memory_store_caching do
+RSpec.describe Metrics::Dashboard::CloneDashboardService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
let_it_be(:user) { create(:user) }
@@ -81,7 +81,22 @@ describe Metrics::Dashboard::CloneDashboardService, :use_clean_rails_memory_stor
allow(::Gitlab::Metrics::Dashboard::Processor).to receive(:new).and_return(double(process: file_content_hash))
end
- it_behaves_like 'valid dashboard cloning process', ::Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH, [::Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter, ::Gitlab::Metrics::Dashboard::Stages::CustomMetricsInserter, ::Gitlab::Metrics::Dashboard::Stages::Sorter]
+ it_behaves_like 'valid dashboard cloning process', ::Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH,
+ [
+ ::Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter,
+ ::Gitlab::Metrics::Dashboard::Stages::CustomMetricsInserter,
+ ::Gitlab::Metrics::Dashboard::Stages::Sorter
+ ]
+
+ it_behaves_like 'valid dashboard cloning process', ::Metrics::Dashboard::ClusterDashboardService::DASHBOARD_PATH,
+ [
+ ::Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter,
+ ::Gitlab::Metrics::Dashboard::Stages::Sorter
+ ]
+
+ it_behaves_like 'valid dashboard cloning process',
+ ::Metrics::Dashboard::SelfMonitoringDashboardService::DASHBOARD_PATH,
+ [::Gitlab::Metrics::Dashboard::Stages::CustomMetricsInserter]
context 'selected branch already exists' do
let(:branch) { 'existing_branch' }
diff --git a/spec/services/metrics/dashboard/cluster_dashboard_service_spec.rb b/spec/services/metrics/dashboard/cluster_dashboard_service_spec.rb
new file mode 100644
index 00000000000..f2e32d5eb35
--- /dev/null
+++ b/spec/services/metrics/dashboard/cluster_dashboard_service_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Metrics::Dashboard::ClusterDashboardService, :use_clean_rails_memory_store_caching do
+ include MetricsDashboardHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:cluster_project) { create(:cluster_project) }
+ let_it_be(:cluster) { cluster_project.cluster }
+ let_it_be(:project) { cluster_project.project }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ describe '.valid_params?' do
+ let(:params) { { cluster: cluster, embedded: 'false' } }
+
+ subject { described_class.valid_params?(params) }
+
+ it { is_expected.to be_truthy }
+
+ context 'with matching dashboard_path' do
+ let(:params) { { dashboard_path: ::Metrics::Dashboard::ClusterDashboardService::DASHBOARD_PATH } }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'missing cluster without dashboard_path' do
+ let(:params) { {} }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#get_dashboard' do
+ let(:service_params) { [project, user, { cluster: cluster, cluster_type: :project }] }
+ let(:service_call) { subject.get_dashboard }
+
+ subject { described_class.new(*service_params) }
+
+ it_behaves_like 'valid dashboard service response'
+ it_behaves_like 'caches the unprocessed dashboard for subsequent calls'
+ it_behaves_like 'refreshes cache when dashboard_version is changed'
+
+ it_behaves_like 'dashboard_version contains SHA256 hash of dashboard file content' do
+ let(:dashboard_path) { described_class::DASHBOARD_PATH }
+ let(:dashboard_version) { subject.send(:dashboard_version) }
+ end
+
+ context 'when called with a non-system dashboard' do
+ let(:dashboard_path) { 'garbage/dashboard/path' }
+
+ # We want to always return the cluster dashboard.
+ it_behaves_like 'valid dashboard service response'
+ end
+ end
+end
diff --git a/spec/services/metrics/dashboard/cluster_metrics_embed_service_spec.rb b/spec/services/metrics/dashboard/cluster_metrics_embed_service_spec.rb
new file mode 100644
index 00000000000..e80911d6265
--- /dev/null
+++ b/spec/services/metrics/dashboard/cluster_metrics_embed_service_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Metrics::Dashboard::ClusterMetricsEmbedService, :use_clean_rails_memory_store_caching do
+ include MetricsDashboardHelpers
+ using RSpec::Parameterized::TableSyntax
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:cluster_project) { create(:cluster_project) }
+ let_it_be(:cluster) { cluster_project.cluster }
+ let_it_be(:project) { cluster_project.project }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ describe '.valid_params?' do
+ let(:valid_params) { { cluster: 1, embedded: 'true', group: 'hello', title: 'world', y_label: 'countries' } }
+
+ subject { described_class }
+
+ it { expect(subject.valid_params?(valid_params)).to be_truthy }
+
+ context 'missing all params' do
+ let(:params) { {} }
+
+ it { expect(subject.valid_params?(params)).to be_falsy }
+ end
+
+ [:cluster, :embedded, :group, :title, :y_label].each do |param_key|
+ it 'returns false with missing param' do
+ params = valid_params.except(param_key)
+
+ expect(subject.valid_params?(params)).to be_falsy
+ end
+ end
+ end
+
+ describe '#get_dashboard' do
+ let(:service_params) do
+ [
+ project,
+ user,
+ {
+ cluster: cluster,
+ cluster_type: :project,
+ embedded: 'true',
+ group: 'Cluster Health',
+ title: 'CPU Usage',
+ y_label: 'CPU (cores)'
+ }
+ ]
+ end
+ let(:service_call) { described_class.new(*service_params).get_dashboard }
+ let(:panel_groups) { service_call[:dashboard][:panel_groups] }
+ let(:panel) { panel_groups.first[:panels].first }
+
+ it_behaves_like 'valid embedded dashboard service response'
+ it_behaves_like 'caches the unprocessed dashboard for subsequent calls'
+
+ it 'returns one panel' do
+ expect(panel_groups.size).to eq 1
+ expect(panel_groups.first[:panels].size).to eq 1
+ end
+
+ it 'returns panel by title and y_label' do
+ expect(panel[:title]).to eq(service_params.last[:title])
+ expect(panel[:y_label]).to eq(service_params.last[:y_label])
+ end
+ end
+end
diff --git a/spec/services/metrics/dashboard/custom_dashboard_service_spec.rb b/spec/services/metrics/dashboard/custom_dashboard_service_spec.rb
index 4966b83bbab..d4391ecb6b9 100644
--- a/spec/services/metrics/dashboard/custom_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/custom_dashboard_service_spec.rb
@@ -2,24 +2,31 @@
require 'spec_helper'
-describe Metrics::Dashboard::CustomDashboardService, :use_clean_rails_memory_store_caching do
+RSpec.describe Metrics::Dashboard::CustomDashboardService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:environment) { create(:environment, project: project) }
+ let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
+ let(:service_params) { [project, user, { environment: environment, dashboard_path: dashboard_path }] }
+
+ subject { described_class.new(*service_params) }
+
before do
project.add_maintainer(user)
end
+ describe '#raw_dashboard' do
+ let(:project) { project_with_dashboard(dashboard_path) }
+
+ it_behaves_like '#raw_dashboard raises error if dashboard loading fails'
+ end
+
describe '#get_dashboard' do
- let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
- let(:service_params) { [project, user, { environment: environment, dashboard_path: dashboard_path }] }
let(:service_call) { subject.get_dashboard }
- subject { described_class.new(*service_params) }
-
context 'when the dashboard does not exist' do
it_behaves_like 'misconfigured dashboard service response', :not_found
@@ -92,7 +99,8 @@ describe Metrics::Dashboard::CustomDashboardService, :use_clean_rails_memory_sto
path: dashboard_path,
display_name: 'test.yml',
default: false,
- system_dashboard: false
+ system_dashboard: false,
+ out_of_the_box_dashboard: false
}]
)
end
diff --git a/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb b/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb
index 1a9ddc87ab0..a5f7c2ab8ab 100644
--- a/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb
+++ b/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::CustomMetricEmbedService do
+RSpec.describe Metrics::Dashboard::CustomMetricEmbedService do
include MetricsDashboardHelpers
let_it_be(:project, reload: true) { build(:project) }
diff --git a/spec/services/metrics/dashboard/default_embed_service_spec.rb b/spec/services/metrics/dashboard/default_embed_service_spec.rb
index 8e32316433d..2ce10eac026 100644
--- a/spec/services/metrics/dashboard/default_embed_service_spec.rb
+++ b/spec/services/metrics/dashboard/default_embed_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::DefaultEmbedService, :use_clean_rails_memory_store_caching do
+RSpec.describe Metrics::Dashboard::DefaultEmbedService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
let_it_be(:project) { build(:project) }
diff --git a/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb b/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb
index ee75284b4ce..72b356be60f 100644
--- a/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb
+++ b/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::DynamicEmbedService, :use_clean_rails_memory_store_caching do
+RSpec.describe Metrics::Dashboard::DynamicEmbedService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
let_it_be(:project) { build(:project) }
diff --git a/spec/services/metrics/dashboard/gitlab_alert_embed_service_spec.rb b/spec/services/metrics/dashboard/gitlab_alert_embed_service_spec.rb
index a66150be42c..29c941826b5 100644
--- a/spec/services/metrics/dashboard/gitlab_alert_embed_service_spec.rb
+++ b/spec/services/metrics/dashboard/gitlab_alert_embed_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::GitlabAlertEmbedService do
+RSpec.describe Metrics::Dashboard::GitlabAlertEmbedService do
include MetricsDashboardHelpers
let_it_be(:alert) { create(:prometheus_alert) }
diff --git a/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb b/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb
index 3547e1f0f8c..ee3c55cb642 100644
--- a/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb
+++ b/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::GrafanaMetricEmbedService do
+RSpec.describe Metrics::Dashboard::GrafanaMetricEmbedService do
include MetricsDashboardHelpers
include ReactiveCachingHelpers
include GrafanaApiHelpers
@@ -182,7 +182,7 @@ describe Metrics::Dashboard::GrafanaMetricEmbedService do
end
end
-describe Metrics::Dashboard::GrafanaUidParser do
+RSpec.describe Metrics::Dashboard::GrafanaUidParser do
let_it_be(:grafana_integration) { create(:grafana_integration) }
let_it_be(:project) { grafana_integration.project }
@@ -213,7 +213,7 @@ describe Metrics::Dashboard::GrafanaUidParser do
end
end
-describe Metrics::Dashboard::DatasourceNameParser do
+RSpec.describe Metrics::Dashboard::DatasourceNameParser do
include GrafanaApiHelpers
let(:grafana_url) { valid_grafana_dashboard_link('https://gitlab.grafana.net') }
diff --git a/spec/services/metrics/dashboard/pod_dashboard_service_spec.rb b/spec/services/metrics/dashboard/pod_dashboard_service_spec.rb
index 1e62a5504a9..ae0e38a04b2 100644
--- a/spec/services/metrics/dashboard/pod_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/pod_dashboard_service_spec.rb
@@ -2,17 +2,26 @@
require 'spec_helper'
-describe Metrics::Dashboard::PodDashboardService, :use_clean_rails_memory_store_caching do
+RSpec.describe Metrics::Dashboard::PodDashboardService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:environment) { create(:environment, project: project) }
+ let(:dashboard_path) { described_class::DASHBOARD_PATH }
+ let(:service_params) { [project, user, { environment: environment, dashboard_path: dashboard_path }] }
+
before do
project.add_maintainer(user)
end
+ subject { described_class.new(*service_params) }
+
+ describe '#raw_dashboard' do
+ it_behaves_like '#raw_dashboard raises error if dashboard loading fails'
+ end
+
describe '.valid_params?' do
let(:params) { { dashboard_path: described_class::DASHBOARD_PATH } }
@@ -34,14 +43,15 @@ describe Metrics::Dashboard::PodDashboardService, :use_clean_rails_memory_store_
end
describe '#get_dashboard' do
- let(:dashboard_path) { described_class::DASHBOARD_PATH }
- let(:service_params) { [project, user, { environment: environment, dashboard_path: dashboard_path }] }
let(:service_call) { subject.get_dashboard }
- subject { described_class.new(*service_params) }
-
it_behaves_like 'valid dashboard service response'
it_behaves_like 'caches the unprocessed dashboard for subsequent calls'
+ it_behaves_like 'refreshes cache when dashboard_version is changed'
it_behaves_like 'updates gitlab_metrics_dashboard_processing_time_ms metric'
+
+ it_behaves_like 'dashboard_version contains SHA256 hash of dashboard file content' do
+ let(:dashboard_version) { subject.send(:dashboard_version) }
+ end
end
end
diff --git a/spec/services/metrics/dashboard/self_monitoring_dashboard_service_spec.rb b/spec/services/metrics/dashboard/self_monitoring_dashboard_service_spec.rb
index 6c9a380a470..aea3e41a013 100644
--- a/spec/services/metrics/dashboard/self_monitoring_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/self_monitoring_dashboard_service_spec.rb
@@ -2,20 +2,29 @@
require 'spec_helper'
-describe Metrics::Dashboard::SelfMonitoringDashboardService, :use_clean_rails_memory_store_caching do
+RSpec.describe Metrics::Dashboard::SelfMonitoringDashboardService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:environment) { create(:environment, project: project) }
+ let(:service_params) { [project, user, { environment: environment }] }
+
before do
project.add_maintainer(user)
stub_application_setting(self_monitoring_project_id: project.id)
end
+ subject do
+ described_class.new(service_params)
+ end
+
+ describe '#raw_dashboard' do
+ it_behaves_like '#raw_dashboard raises error if dashboard loading fails'
+ end
+
describe '#get_dashboard' do
- let(:service_params) { [project, user, { environment: environment }] }
let(:service_call) { subject.get_dashboard }
subject { described_class.new(*service_params) }
@@ -23,7 +32,13 @@ describe Metrics::Dashboard::SelfMonitoringDashboardService, :use_clean_rails_me
it_behaves_like 'valid dashboard service response'
it_behaves_like 'raises error for users with insufficient permissions'
it_behaves_like 'caches the unprocessed dashboard for subsequent calls'
+ it_behaves_like 'refreshes cache when dashboard_version is changed'
it_behaves_like 'updates gitlab_metrics_dashboard_processing_time_ms metric'
+
+ it_behaves_like 'dashboard_version contains SHA256 hash of dashboard file content' do
+ let(:dashboard_path) { described_class::DASHBOARD_PATH }
+ let(:dashboard_version) { subject.send(:dashboard_version) }
+ end
end
describe '.all_dashboard_paths' do
@@ -35,7 +50,8 @@ describe Metrics::Dashboard::SelfMonitoringDashboardService, :use_clean_rails_me
path: described_class::DASHBOARD_PATH,
display_name: described_class::DASHBOARD_NAME,
default: true,
- system_dashboard: false
+ system_dashboard: false,
+ out_of_the_box_dashboard: true
}]
)
end
diff --git a/spec/services/metrics/dashboard/system_dashboard_service_spec.rb b/spec/services/metrics/dashboard/system_dashboard_service_spec.rb
index 7d58501ae3f..ced7c29b507 100644
--- a/spec/services/metrics/dashboard/system_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/system_dashboard_service_spec.rb
@@ -2,29 +2,39 @@
require 'spec_helper'
-describe Metrics::Dashboard::SystemDashboardService, :use_clean_rails_memory_store_caching do
+RSpec.describe Metrics::Dashboard::SystemDashboardService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:environment) { create(:environment, project: project) }
+ let(:dashboard_path) { described_class::DASHBOARD_PATH }
+ let(:service_params) { [project, user, { environment: environment, dashboard_path: dashboard_path }] }
+
+ subject { described_class.new(*service_params) }
+
before do
project.add_maintainer(user)
end
+ describe '#raw_dashboard' do
+ it_behaves_like '#raw_dashboard raises error if dashboard loading fails'
+ end
+
describe '#get_dashboard' do
- let(:dashboard_path) { described_class::DASHBOARD_PATH }
- let(:service_params) { [project, user, { environment: environment, dashboard_path: dashboard_path }] }
let(:service_call) { subject.get_dashboard }
- subject { described_class.new(*service_params) }
-
it_behaves_like 'valid dashboard service response'
it_behaves_like 'raises error for users with insufficient permissions'
it_behaves_like 'caches the unprocessed dashboard for subsequent calls'
+ it_behaves_like 'refreshes cache when dashboard_version is changed'
it_behaves_like 'updates gitlab_metrics_dashboard_processing_time_ms metric'
+ it_behaves_like 'dashboard_version contains SHA256 hash of dashboard file content' do
+ let(:dashboard_version) { subject.send(:dashboard_version) }
+ end
+
context 'when called with a non-system dashboard' do
let(:dashboard_path) { 'garbage/dashboard/path' }
@@ -42,7 +52,8 @@ describe Metrics::Dashboard::SystemDashboardService, :use_clean_rails_memory_sto
path: described_class::DASHBOARD_PATH,
display_name: described_class::DASHBOARD_NAME,
default: true,
- system_dashboard: true
+ system_dashboard: true,
+ out_of_the_box_dashboard: true
}]
)
end
diff --git a/spec/services/metrics/dashboard/transient_embed_service_spec.rb b/spec/services/metrics/dashboard/transient_embed_service_spec.rb
index 125fff7c23c..3fd0c97d909 100644
--- a/spec/services/metrics/dashboard/transient_embed_service_spec.rb
+++ b/spec/services/metrics/dashboard/transient_embed_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::TransientEmbedService, :use_clean_rails_memory_store_caching do
+RSpec.describe Metrics::Dashboard::TransientEmbedService, :use_clean_rails_memory_store_caching do
let_it_be(:project) { build(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:environment) { create(:environment, project: project) }
diff --git a/spec/services/metrics/dashboard/update_dashboard_service_spec.rb b/spec/services/metrics/dashboard/update_dashboard_service_spec.rb
index fce027688d9..148005480ea 100644
--- a/spec/services/metrics/dashboard/update_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/update_dashboard_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::UpdateDashboardService, :use_clean_rails_memory_store_caching do
+RSpec.describe Metrics::Dashboard::UpdateDashboardService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
let_it_be(:user) { create(:user) }
diff --git a/spec/services/metrics/sample_metrics_service_spec.rb b/spec/services/metrics/sample_metrics_service_spec.rb
index 3b4f7cb8062..b94345500f0 100644
--- a/spec/services/metrics/sample_metrics_service_spec.rb
+++ b/spec/services/metrics/sample_metrics_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::SampleMetricsService do
+RSpec.describe Metrics::SampleMetricsService do
describe 'query' do
let(:range_start) { '2019-12-02T23:31:45.000Z' }
let(:range_end) { '2019-12-03T00:01:45.000Z' }
diff --git a/spec/services/metrics/users_starred_dashboards/create_service_spec.rb b/spec/services/metrics/users_starred_dashboards/create_service_spec.rb
index eac4965ba44..910b556b8dd 100644
--- a/spec/services/metrics/users_starred_dashboards/create_service_spec.rb
+++ b/spec/services/metrics/users_starred_dashboards/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::UsersStarredDashboards::CreateService do
+RSpec.describe Metrics::UsersStarredDashboards::CreateService do
let_it_be(:user) { create(:user) }
let(:dashboard_path) { 'config/prometheus/common_metrics.yml' }
let(:service_instance) { described_class.new(user, project, dashboard_path) }
diff --git a/spec/services/metrics/users_starred_dashboards/delete_service_spec.rb b/spec/services/metrics/users_starred_dashboards/delete_service_spec.rb
index 68a2fef5931..5cdffe681eb 100644
--- a/spec/services/metrics/users_starred_dashboards/delete_service_spec.rb
+++ b/spec/services/metrics/users_starred_dashboards/delete_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::UsersStarredDashboards::DeleteService do
+RSpec.describe Metrics::UsersStarredDashboards::DeleteService do
subject(:service_instance) { described_class.new(user, project, dashboard_path) }
let_it_be(:user) { create(:user) }
diff --git a/spec/services/milestones/close_service_spec.rb b/spec/services/milestones/close_service_spec.rb
index 55e705063b2..53751b40667 100644
--- a/spec/services/milestones/close_service_spec.rb
+++ b/spec/services/milestones/close_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Milestones::CloseService do
+RSpec.describe Milestones::CloseService do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:milestone) { create(:milestone, title: "Milestone v1.2", project: project) }
diff --git a/spec/services/milestones/closed_issues_count_service_spec.rb b/spec/services/milestones/closed_issues_count_service_spec.rb
index b86eede2e22..a3865d08972 100644
--- a/spec/services/milestones/closed_issues_count_service_spec.rb
+++ b/spec/services/milestones/closed_issues_count_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Milestones::ClosedIssuesCountService, :use_clean_rails_memory_store_caching do
+RSpec.describe Milestones::ClosedIssuesCountService, :use_clean_rails_memory_store_caching do
let(:project) { create(:project) }
let(:milestone) { create(:milestone, project: project) }
diff --git a/spec/services/milestones/create_service_spec.rb b/spec/services/milestones/create_service_spec.rb
index 97f6e947539..93ca4ff653f 100644
--- a/spec/services/milestones/create_service_spec.rb
+++ b/spec/services/milestones/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Milestones::CreateService do
+RSpec.describe Milestones::CreateService do
let(:project) { create(:project) }
let(:user) { create(:user) }
diff --git a/spec/services/milestones/destroy_service_spec.rb b/spec/services/milestones/destroy_service_spec.rb
index 4f16421c39f..66c5c504c64 100644
--- a/spec/services/milestones/destroy_service_spec.rb
+++ b/spec/services/milestones/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Milestones::DestroyService do
+RSpec.describe Milestones::DestroyService do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:milestone) { create(:milestone, title: 'Milestone v1.0', project: project) }
diff --git a/spec/services/milestones/find_or_create_service_spec.rb b/spec/services/milestones/find_or_create_service_spec.rb
index ae3def30982..1bcaf578441 100644
--- a/spec/services/milestones/find_or_create_service_spec.rb
+++ b/spec/services/milestones/find_or_create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Milestones::FindOrCreateService do
+RSpec.describe Milestones::FindOrCreateService do
describe '#execute' do
subject(:service) { described_class.new(project, user, params) }
diff --git a/spec/services/milestones/issues_count_service_spec.rb b/spec/services/milestones/issues_count_service_spec.rb
index 22aea884424..c944055e4e7 100644
--- a/spec/services/milestones/issues_count_service_spec.rb
+++ b/spec/services/milestones/issues_count_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Milestones::IssuesCountService, :use_clean_rails_memory_store_caching do
+RSpec.describe Milestones::IssuesCountService, :use_clean_rails_memory_store_caching do
let(:project) { create(:project) }
let(:milestone) { create(:milestone, project: project) }
diff --git a/spec/services/milestones/promote_service_spec.rb b/spec/services/milestones/promote_service_spec.rb
index fa893b86cdb..f0a34241c74 100644
--- a/spec/services/milestones/promote_service_spec.rb
+++ b/spec/services/milestones/promote_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Milestones::PromoteService do
+RSpec.describe Milestones::PromoteService do
let(:group) { create(:group) }
let(:project) { create(:project, namespace: group) }
let(:user) { create(:user) }
diff --git a/spec/services/milestones/transfer_service_spec.rb b/spec/services/milestones/transfer_service_spec.rb
index 9f94d2d320b..4a626fe688a 100644
--- a/spec/services/milestones/transfer_service_spec.rb
+++ b/spec/services/milestones/transfer_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Milestones::TransferService do
+RSpec.describe Milestones::TransferService do
describe '#execute' do
subject(:service) { described_class.new(user, old_group, project) }
diff --git a/spec/services/milestones/update_service_spec.rb b/spec/services/milestones/update_service_spec.rb
index 3b91442c0ba..85fd89c11ac 100644
--- a/spec/services/milestones/update_service_spec.rb
+++ b/spec/services/milestones/update_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Milestones::UpdateService do
+RSpec.describe Milestones::UpdateService do
let(:project) { create(:project) }
let(:user) { build(:user) }
let(:milestone) { create(:milestone, project: project) }
diff --git a/spec/services/namespaces/check_storage_size_service_spec.rb b/spec/services/namespaces/check_storage_size_service_spec.rb
deleted file mode 100644
index e192f897cf9..00000000000
--- a/spec/services/namespaces/check_storage_size_service_spec.rb
+++ /dev/null
@@ -1,165 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Namespaces::CheckStorageSizeService, '#execute' do
- let(:namespace) { build_stubbed(:namespace) }
- let(:user) { build(:user, namespace: namespace) }
- let(:service) { described_class.new(namespace, user) }
- let(:current_size) { 150.megabytes }
- let(:limit) { 100.megabytes }
-
- subject(:response) { service.execute }
-
- before do
- allow(namespace).to receive(:root_ancestor).and_return(namespace)
-
- root_storage_size = instance_double("RootStorageSize",
- current_size: current_size,
- limit: limit,
- usage_ratio: limit == 0 ? 0 : current_size.to_f / limit.to_f,
- above_size_limit?: current_size > limit
- )
-
- expect(Namespace::RootStorageSize).to receive(:new).and_return(root_storage_size)
- end
-
- context 'feature flag' do
- it 'is successful when disabled' do
- stub_feature_flags(namespace_storage_limit: false)
-
- expect(response).to be_success
- end
-
- it 'errors when enabled' do
- stub_feature_flags(namespace_storage_limit: true)
-
- expect(response).to be_error
- end
-
- it 'is successful when feature flag is activated for another namespace' do
- stub_feature_flags(namespace_storage_limit: build(:namespace))
-
- expect(response).to be_success
- end
-
- it 'errors when feature flag is activated for the current namespace' do
- stub_feature_flags(namespace_storage_limit: namespace)
-
- expect(response).to be_error
- expect(response.message).to be_present
- end
- end
-
- context 'when limit is set to 0' do
- let(:limit) { 0 }
-
- it 'is successful and has no payload' do
- expect(response).to be_success
- expect(response.payload).to be_empty
- end
- end
-
- context 'when current size is below threshold' do
- let(:current_size) { 10.megabytes }
-
- it 'is successful and has no payload' do
- expect(response).to be_success
- expect(response.payload).to be_empty
- end
- end
-
- context 'when not admin of the namespace' do
- let(:other_namespace) { build_stubbed(:namespace) }
-
- subject(:response) { described_class.new(other_namespace, user).execute }
-
- before do
- allow(other_namespace).to receive(:root_ancestor).and_return(other_namespace)
- end
-
- it 'errors and has no payload' do
- expect(response).to be_error
- expect(response.payload).to be_empty
- end
- end
-
- context 'when providing the child namespace' do
- let(:namespace) { build_stubbed(:group) }
- let(:child_namespace) { build_stubbed(:group, parent: namespace) }
-
- subject(:response) { described_class.new(child_namespace, user).execute }
-
- before do
- allow(child_namespace).to receive(:root_ancestor).and_return(namespace)
- namespace.add_owner(user)
- end
-
- it 'uses the root namespace' do
- expect(response).to be_error
- end
- end
-
- describe 'payload alert_level' do
- subject { service.execute.payload[:alert_level] }
-
- context 'when above info threshold' do
- let(:current_size) { 50.megabytes }
-
- it { is_expected.to eq(:info) }
- end
-
- context 'when above warning threshold' do
- let(:current_size) { 75.megabytes }
-
- it { is_expected.to eq(:warning) }
- end
-
- context 'when above alert threshold' do
- let(:current_size) { 95.megabytes }
-
- it { is_expected.to eq(:alert) }
- end
-
- context 'when above error threshold' do
- let(:current_size) { 100.megabytes }
-
- it { is_expected.to eq(:error) }
- end
- end
-
- describe 'payload explanation_message' do
- subject(:response) { service.execute.payload[:explanation_message] }
-
- context 'when above limit' do
- let(:current_size) { 110.megabytes }
-
- it 'returns message with read-only warning' do
- expect(response).to include("#{namespace.name} is now read-only")
- end
- end
-
- context 'when below limit' do
- let(:current_size) { 60.megabytes }
-
- it { is_expected.to include('If you reach 100% storage capacity') }
- end
- end
-
- describe 'payload usage_message' do
- let(:current_size) { 60.megabytes }
-
- subject(:response) { service.execute.payload[:usage_message] }
-
- it 'returns current usage information' do
- expect(response).to include("60 MB of 100 MB")
- expect(response).to include("60%")
- end
- end
-
- describe 'payload root_namespace' do
- subject(:response) { service.execute.payload[:root_namespace] }
-
- it { is_expected.to eq(namespace) }
- end
-end
diff --git a/spec/services/namespaces/statistics_refresher_service_spec.rb b/spec/services/namespaces/statistics_refresher_service_spec.rb
index 1fa0a794edd..d3379e843ec 100644
--- a/spec/services/namespaces/statistics_refresher_service_spec.rb
+++ b/spec/services/namespaces/statistics_refresher_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Namespaces::StatisticsRefresherService, '#execute' do
+RSpec.describe Namespaces::StatisticsRefresherService, '#execute' do
let(:group) { create(:group) }
let(:projects) { create_list(:project, 5, namespace: group) }
let(:service) { described_class.new }
diff --git a/spec/services/note_summary_spec.rb b/spec/services/note_summary_spec.rb
index 038e0cdb703..38174748b19 100644
--- a/spec/services/note_summary_spec.rb
+++ b/spec/services/note_summary_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NoteSummary do
+RSpec.describe NoteSummary do
let(:project) { build(:project) }
let(:noteable) { build(:issue) }
let(:user) { build(:user) }
diff --git a/spec/services/notes/build_service_spec.rb b/spec/services/notes/build_service_spec.rb
index 984658cbd19..90548cf9a99 100644
--- a/spec/services/notes/build_service_spec.rb
+++ b/spec/services/notes/build_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Notes::BuildService do
+RSpec.describe Notes::BuildService do
let(:note) { create(:discussion_note_on_issue) }
let(:project) { note.project }
let(:author) { note.author }
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index 39d6fd26e31..fd824621db7 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -2,12 +2,12 @@
require 'spec_helper'
-describe Notes::CreateService do
+RSpec.describe Notes::CreateService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:user) { create(:user) }
let(:opts) do
- { note: 'Awesome comment', noteable_type: 'Issue', noteable_id: issue.id }
+ { note: 'Awesome comment', noteable_type: 'Issue', noteable_id: issue.id, confidential: true }
end
describe '#execute' do
diff --git a/spec/services/notes/destroy_service_spec.rb b/spec/services/notes/destroy_service_spec.rb
index 258e5c68265..d1076f77cec 100644
--- a/spec/services/notes/destroy_service_spec.rb
+++ b/spec/services/notes/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Notes::DestroyService do
+RSpec.describe Notes::DestroyService do
let_it_be(:project) { create(:project, :public) }
let_it_be(:issue) { create(:issue, project: project) }
let(:user) { issue.author }
diff --git a/spec/services/notes/post_process_service_spec.rb b/spec/services/notes/post_process_service_spec.rb
index d564cacd2d8..07ef08d36c4 100644
--- a/spec/services/notes/post_process_service_spec.rb
+++ b/spec/services/notes/post_process_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Notes::PostProcessService do
+RSpec.describe Notes::PostProcessService do
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project) }
let(:user) { create(:user) }
diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb
index 7eea2a7afc6..d20824efaaa 100644
--- a/spec/services/notes/quick_actions_service_spec.rb
+++ b/spec/services/notes/quick_actions_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Notes::QuickActionsService do
+RSpec.describe Notes::QuickActionsService do
shared_context 'note on noteable' do
let(:project) { create(:project, :repository) }
let(:maintainer) { create(:user).tap { |u| project.add_maintainer(u) } }
diff --git a/spec/services/notes/render_service_spec.rb b/spec/services/notes/render_service_spec.rb
index ad69721d876..09cd7dc572b 100644
--- a/spec/services/notes/render_service_spec.rb
+++ b/spec/services/notes/render_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Notes::RenderService do
+RSpec.describe Notes::RenderService do
describe '#execute' do
it 'renders a Note' do
note = double(:note)
diff --git a/spec/services/notes/resolve_service_spec.rb b/spec/services/notes/resolve_service_spec.rb
index c98384c226e..1c5b308aed1 100644
--- a/spec/services/notes/resolve_service_spec.rb
+++ b/spec/services/notes/resolve_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Notes::ResolveService do
+RSpec.describe Notes::ResolveService do
let(:merge_request) { create(:merge_request) }
let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: merge_request.project) }
let(:user) { merge_request.author }
diff --git a/spec/services/notes/update_service_spec.rb b/spec/services/notes/update_service_spec.rb
index ab28e08ec83..70dea99de4a 100644
--- a/spec/services/notes/update_service_spec.rb
+++ b/spec/services/notes/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Notes::UpdateService do
+RSpec.describe Notes::UpdateService do
let(:group) { create(:group, :public) }
let(:project) { create(:project, :public, group: group) }
let(:private_group) { create(:group, :private) }
@@ -59,6 +59,45 @@ describe Notes::UpdateService do
end
end
+ context 'setting confidentiality' do
+ let(:opts) { { confidential: true } }
+
+ context 'simple note' do
+ it 'updates the confidentiality' do
+ expect { update_note(opts) }.to change { note.reload.confidential }.from(nil).to(true)
+ end
+ end
+
+ context 'discussion notes' do
+ let(:note) { create(:discussion_note, project: project, noteable: issue, author: user, note: "Old note #{user2.to_reference}") }
+ let!(:response_note_1) { create(:discussion_note, project: project, noteable: issue, in_reply_to: note) }
+ let!(:response_note_2) { create(:discussion_note, project: project, noteable: issue, in_reply_to: note, confidential: false) }
+ let!(:other_note) { create(:note, project: project, noteable: issue) }
+
+ context 'when updating the root note' do
+ it 'updates the confidentiality of the root note and all the responses' do
+ update_note(opts)
+
+ expect(note.reload.confidential).to be_truthy
+ expect(response_note_1.reload.confidential).to be_truthy
+ expect(response_note_2.reload.confidential).to be_truthy
+ expect(other_note.reload.confidential).to be_falsey
+ end
+ end
+
+ context 'when updating one of the response notes' do
+ it 'updates only the confidentiality of the note that is being updated' do
+ Notes::UpdateService.new(project, user, opts).execute(response_note_1)
+
+ expect(note.reload.confidential).to be_falsey
+ expect(response_note_1.reload.confidential).to be_truthy
+ expect(response_note_2.reload.confidential).to be_falsey
+ expect(other_note.reload.confidential).to be_falsey
+ end
+ end
+ end
+ end
+
context 'todos' do
shared_examples 'does not update todos' do
it 'keep todos' do
diff --git a/spec/services/notification_recipients/build_service_spec.rb b/spec/services/notification_recipients/build_service_spec.rb
index e203093623d..5c8add250c2 100644
--- a/spec/services/notification_recipients/build_service_spec.rb
+++ b/spec/services/notification_recipients/build_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NotificationRecipients::BuildService do
+RSpec.describe NotificationRecipients::BuildService do
let(:service) { described_class }
let(:assignee) { create(:user) }
let(:project) { create(:project, :public) }
diff --git a/spec/services/notification_recipients/builder/default_spec.rb b/spec/services/notification_recipients/builder/default_spec.rb
index 307ca40248e..d25410235c2 100644
--- a/spec/services/notification_recipients/builder/default_spec.rb
+++ b/spec/services/notification_recipients/builder/default_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NotificationRecipients::Builder::Default do
+RSpec.describe NotificationRecipients::Builder::Default do
describe '#build!' do
let_it_be(:group) { create(:group, :public) }
let_it_be(:project) { create(:project, :public, group: group).tap { |p| p.add_developer(project_watcher) } }
diff --git a/spec/services/notification_recipients/builder/new_note_spec.rb b/spec/services/notification_recipients/builder/new_note_spec.rb
index f88e8b2dfb0..7d2a4f682c5 100644
--- a/spec/services/notification_recipients/builder/new_note_spec.rb
+++ b/spec/services/notification_recipients/builder/new_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NotificationRecipients::Builder::NewNote do
+RSpec.describe NotificationRecipients::Builder::NewNote do
describe '#notification_recipients' do
let_it_be(:group) { create(:group, :public) }
let_it_be(:project) { create(:project, :public, group: group) }
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 3c1c3e2dfc3..2fe7a46de4b 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NotificationService, :mailer do
+RSpec.describe NotificationService, :mailer do
include EmailSpec::Matchers
include ExternalAuthorizationServiceHelpers
include NotificationHelpers
@@ -343,6 +343,79 @@ describe NotificationService, :mailer do
end
end
+ context 'on service desk issue' do
+ before do
+ allow(Notify).to receive(:service_desk_new_note_email)
+ .with(Integer, Integer).and_return(mailer)
+
+ allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true }
+ allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
+ end
+
+ let(:subject) { NotificationService.new }
+ let(:mailer) { double(deliver_later: true) }
+
+ def should_email!
+ expect(Notify).to receive(:service_desk_new_note_email)
+ .with(issue.id, note.id)
+ end
+
+ def should_not_email!
+ expect(Notify).not_to receive(:service_desk_new_note_email)
+ end
+
+ def execute!
+ subject.new_note(note)
+ end
+
+ def self.it_should_email!
+ it 'sends the email' do
+ should_email!
+ execute!
+ end
+ end
+
+ def self.it_should_not_email!
+ it 'doesn\'t send the email' do
+ should_not_email!
+ execute!
+ end
+ end
+
+ let(:issue) { create(:issue, author: User.support_bot) }
+ let(:project) { issue.project }
+ let(:note) { create(:note, noteable: issue, project: project) }
+
+ context 'a non-service-desk issue' do
+ it_should_not_email!
+ end
+
+ context 'a service-desk issue' do
+ before do
+ issue.update!(service_desk_reply_to: 'service.desk@example.com')
+ project.update!(service_desk_enabled: true)
+ end
+
+ it_should_email!
+
+ context 'where the project has disabled the feature' do
+ before do
+ project.update(service_desk_enabled: false)
+ end
+
+ it_should_not_email!
+ end
+
+ context 'when the support bot has unsubscribed' do
+ before do
+ issue.unsubscribe(User.support_bot, project)
+ end
+
+ it_should_not_email!
+ end
+ end
+ end
+
describe 'new note on issue in project that belongs to a group' do
before do
note.project.namespace_id = group.id
@@ -1950,6 +2023,26 @@ describe NotificationService, :mailer do
let(:notification_trigger) { notification.resolve_all_discussions(merge_request, @u_disabled) }
end
end
+
+ describe '#merge_when_pipeline_succeeds' do
+ it 'send notification that merge will happen when pipeline succeeds' do
+ notification.merge_when_pipeline_succeeds(merge_request, assignee)
+ should_email(merge_request.author)
+ should_email(@u_watcher)
+ should_email(@subscriber)
+ end
+
+ it_behaves_like 'participating notifications' do
+ let(:participant) { create(:user, username: 'user-participant') }
+ let(:issuable) { merge_request }
+ let(:notification_trigger) { notification.merge_when_pipeline_succeeds(merge_request, @u_disabled) }
+ end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.merge_when_pipeline_succeeds(merge_request, @u_disabled) }
+ end
+ end
end
describe 'Projects', :deliver_mails_inline do
diff --git a/spec/services/packages/composer/composer_json_service_spec.rb b/spec/services/packages/composer/composer_json_service_spec.rb
new file mode 100644
index 00000000000..3996fcea679
--- /dev/null
+++ b/spec/services/packages/composer/composer_json_service_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Composer::ComposerJsonService do
+ describe '#execute' do
+ let(:branch) { project.repository.find_branch('master') }
+ let(:target) { branch.target }
+
+ subject { described_class.new(project, target).execute }
+
+ context 'with an existing file' do
+ let(:project) { create(:project, :custom_repo, files: { 'composer.json' => json } ) }
+
+ context 'with a valid file' do
+ let(:json) { '{ "name": "package-name"}' }
+
+ it 'returns the parsed json' do
+ expect(subject).to eq({ 'name' => 'package-name' })
+ end
+ end
+
+ context 'with an invalid file' do
+ let(:json) { '{ name": "package-name"}' }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(/Invalid/)
+ end
+ end
+ end
+
+ context 'without the composer.json file' do
+ let(:project) { create(:project, :repository) }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(/not found/)
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/composer/create_package_service_spec.rb b/spec/services/packages/composer/create_package_service_spec.rb
new file mode 100644
index 00000000000..3f9da31cf6e
--- /dev/null
+++ b/spec/services/packages/composer/create_package_service_spec.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Composer::CreatePackageService do
+ include PackagesManagerApiSpecHelpers
+
+ let_it_be(:package_name) { 'composer-package-name' }
+ let_it_be(:json) { { name: package_name }.to_json }
+ let_it_be(:project) { create(:project, :custom_repo, files: { 'composer.json' => json } ) }
+ let_it_be(:user) { create(:user) }
+ let(:params) do
+ {
+ branch: branch,
+ tag: tag
+ }
+ end
+
+ describe '#execute' do
+ let(:tag) { nil }
+ let(:branch) { nil }
+
+ subject { described_class.new(project, user, params).execute }
+
+ let(:created_package) { Packages::Package.composer.last }
+
+ context 'without an existing package' do
+ context 'with a branch' do
+ let(:branch) { project.repository.find_branch('master') }
+
+ it 'creates the package' do
+ expect { subject }
+ .to change { Packages::Package.composer.count }.by(1)
+ .and change { Packages::Composer::Metadatum.count }.by(1)
+
+ expect(created_package.name).to eq package_name
+ expect(created_package.version).to eq 'dev-master'
+ expect(created_package.composer_metadatum.target_sha).to eq branch.target
+ expect(created_package.composer_metadatum.composer_json.to_json).to eq json
+ end
+ end
+
+ context 'with a tag' do
+ let(:tag) { project.repository.find_tag('v1.2.3') }
+
+ before do
+ project.repository.add_tag(user, 'v1.2.3', 'master')
+ end
+
+ it 'creates the package' do
+ expect { subject }
+ .to change { Packages::Package.composer.count }.by(1)
+ .and change { Packages::Composer::Metadatum.count }.by(1)
+
+ expect(created_package.name).to eq package_name
+ expect(created_package.version).to eq '1.2.3'
+ end
+ end
+ end
+
+ context 'with an existing package' do
+ let(:branch) { project.repository.find_branch('master') }
+
+ context 'belonging to the same project' do
+ before do
+ described_class.new(project, user, params).execute
+ end
+
+ it 'does not create a new package' do
+ expect { subject }
+ .to change { Packages::Package.composer.count }.by(0)
+ .and change { Packages::Composer::Metadatum.count }.by(0)
+ end
+ end
+
+ context 'belonging to another project' do
+ let(:other_project) { create(:project) }
+ let!(:other_package) { create(:composer_package, name: package_name, version: 'dev-master', project: other_project) }
+
+ it 'fails with an error' do
+ expect { subject }
+ .to raise_error(/is already taken/)
+ end
+ end
+
+ context 'same name but of different type' do
+ let(:other_project) { create(:project) }
+ let!(:other_package) { create(:package, name: package_name, version: 'dev-master', project: other_project) }
+
+ it 'creates the package' do
+ expect { subject }
+ .to change { Packages::Package.composer.count }.by(1)
+ .and change { Packages::Composer::Metadatum.count }.by(1)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/composer/version_parser_service_spec.rb b/spec/services/packages/composer/version_parser_service_spec.rb
new file mode 100644
index 00000000000..904c75ab0a1
--- /dev/null
+++ b/spec/services/packages/composer/version_parser_service_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Composer::VersionParserService do
+ let_it_be(:params) { {} }
+
+ describe '#execute' do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { described_class.new(tag_name: tagname, branch_name: branchname).execute }
+
+ where(:tagname, :branchname, :expected_version) do
+ nil | 'master' | 'dev-master'
+ nil | 'my-feature' | 'dev-my-feature'
+ nil | 'v1' | '1.x-dev'
+ nil | 'v1.x' | '1.x-dev'
+ nil | 'v1.7.x' | '1.7.x-dev'
+ nil | 'v1.7' | '1.7.x-dev'
+ nil | '1.7.x' | '1.7.x-dev'
+ 'v1.0.0' | nil | '1.0.0'
+ 'v1.0' | nil | '1.0'
+ '1.0' | nil | '1.0'
+ '1.0.2' | nil | '1.0.2'
+ '1.0.2-beta2' | nil | '1.0.2-beta2'
+ end
+
+ with_them do
+ it { is_expected.to eq expected_version }
+ end
+ end
+end
diff --git a/spec/services/packages/conan/create_package_file_service_spec.rb b/spec/services/packages/conan/create_package_file_service_spec.rb
new file mode 100644
index 00000000000..0e9cbba5fc1
--- /dev/null
+++ b/spec/services/packages/conan/create_package_file_service_spec.rb
@@ -0,0 +1,130 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Conan::CreatePackageFileService do
+ include WorkhorseHelpers
+
+ let_it_be(:package) { create(:conan_package) }
+
+ describe '#execute' do
+ let(:file_name) { 'foo.tgz' }
+
+ subject { described_class.new(package, file, params) }
+
+ shared_examples 'a valid package_file' do
+ let(:params) do
+ {
+ file_name: file_name,
+ 'file.md5': '12345',
+ 'file.sha1': '54321',
+ 'file.size': '128',
+ 'file.type': 'txt',
+ recipe_revision: '0',
+ package_revision: '0',
+ conan_package_reference: '123456789',
+ conan_file_type: :package_file
+ }.with_indifferent_access
+ end
+
+ it 'creates a new package file' do
+ package_file = subject.execute
+
+ expect(package_file).to be_valid
+ expect(package_file.file_name).to eq(file_name)
+ expect(package_file.file_md5).to eq('12345')
+ expect(package_file.size).to eq(128)
+ expect(package_file.conan_file_metadatum).to be_valid
+ expect(package_file.conan_file_metadatum.recipe_revision).to eq('0')
+ expect(package_file.conan_file_metadatum.package_revision).to eq('0')
+ expect(package_file.conan_file_metadatum.conan_package_reference).to eq('123456789')
+ expect(package_file.conan_file_metadatum.conan_file_type).to eq('package_file')
+ expect(package_file.file.read).to eq('content')
+ end
+ end
+
+ shared_examples 'a valid recipe_file' do
+ let(:params) do
+ {
+ file_name: file_name,
+ 'file.md5': '12345',
+ 'file.sha1': '54321',
+ 'file.size': '128',
+ 'file.type': 'txt',
+ recipe_revision: '0',
+ conan_file_type: :recipe_file
+ }.with_indifferent_access
+ end
+
+ it 'creates a new recipe file' do
+ package_file = subject.execute
+
+ expect(package_file).to be_valid
+ expect(package_file.file_name).to eq(file_name)
+ expect(package_file.file_md5).to eq('12345')
+ expect(package_file.size).to eq(128)
+ expect(package_file.conan_file_metadatum).to be_valid
+ expect(package_file.conan_file_metadatum.recipe_revision).to eq('0')
+ expect(package_file.conan_file_metadatum.package_revision).to be_nil
+ expect(package_file.conan_file_metadatum.conan_package_reference).to be_nil
+ expect(package_file.conan_file_metadatum.conan_file_type).to eq('recipe_file')
+ expect(package_file.file.read).to eq('content')
+ end
+ end
+
+ context 'with temp file' do
+ let!(:file) do
+ upload_path = ::Packages::PackageFileUploader.workhorse_local_upload_path
+ file_path = upload_path + '/' + file_name
+
+ FileUtils.mkdir_p(upload_path)
+ File.write(file_path, 'content')
+
+ UploadedFile.new(file_path, filename: File.basename(file_path))
+ end
+
+ before do
+ allow_any_instance_of(Packages::PackageFileUploader).to receive(:size).and_return(128)
+ end
+
+ it_behaves_like 'a valid package_file'
+ it_behaves_like 'a valid recipe_file'
+ end
+
+ context 'with remote file' do
+ let!(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: true)
+ end
+
+ before do
+ allow_any_instance_of(Packages::PackageFileUploader).to receive(:size).and_return(128)
+ end
+
+ let(:tmp_object) do
+ fog_connection.directories.new(key: 'packages').files.create(
+ key: "tmp/uploads/#{file_name}",
+ body: 'content'
+ )
+ end
+
+ let(:file) { fog_to_uploaded_file(tmp_object) }
+
+ it_behaves_like 'a valid package_file'
+ it_behaves_like 'a valid recipe_file'
+ end
+
+ context 'file is missing' do
+ let(:file) { nil }
+ let(:params) do
+ {
+ file_name: file_name,
+ recipe_revision: '0',
+ conan_file_type: :recipe_file
+ }
+ end
+
+ it 'raises an error' do
+ expect { subject.execute }.to raise_error(ActiveRecord::RecordInvalid)
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/conan/create_package_service_spec.rb b/spec/services/packages/conan/create_package_service_spec.rb
new file mode 100644
index 00000000000..f8068f6e57b
--- /dev/null
+++ b/spec/services/packages/conan/create_package_service_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Conan::CreatePackageService do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+
+ subject { described_class.new(project, user, params) }
+
+ describe '#execute' do
+ context 'valid params' do
+ let(:params) do
+ {
+ package_name: 'my-pkg',
+ package_version: '1.0.0',
+ package_username: ::Packages::Conan::Metadatum.package_username_from(full_path: project.full_path),
+ package_channel: 'stable'
+ }
+ end
+
+ it 'creates a new package' do
+ package = subject.execute
+
+ expect(package).to be_valid
+ expect(package.name).to eq(params[:package_name])
+ expect(package.version).to eq(params[:package_version])
+ expect(package.package_type).to eq('conan')
+ expect(package.conan_metadatum.package_username).to eq(params[:package_username])
+ expect(package.conan_metadatum.package_channel).to eq(params[:package_channel])
+ end
+ end
+
+ context 'invalid params' do
+ let(:params) do
+ {
+ package_name: 'my-pkg',
+ package_version: '1.0.0',
+ package_username: 'foo/bar',
+ package_channel: 'stable'
+ }
+ end
+
+ it 'fails' do
+ expect { subject.execute }.to raise_exception(ActiveRecord::RecordInvalid)
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/conan/search_service_spec.rb b/spec/services/packages/conan/search_service_spec.rb
new file mode 100644
index 00000000000..39d284ee088
--- /dev/null
+++ b/spec/services/packages/conan/search_service_spec.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Conan::SearchService do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public) }
+ let!(:conan_package) { create(:conan_package, project: project) }
+ let!(:conan_package2) { create(:conan_package, project: project) }
+
+ subject { described_class.new(user, query: query) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ describe '#execute' do
+ context 'with wildcard' do
+ let(:partial_name) { conan_package.name.first[0, 3] }
+ let(:query) { "#{partial_name}*" }
+
+ it 'makes a wildcard query' do
+ result = subject.execute
+
+ expect(result.status).to eq :success
+ expect(result.payload).to eq(results: [conan_package.conan_recipe, conan_package2.conan_recipe])
+ end
+ end
+
+ context 'with only wildcard' do
+ let(:query) { '*' }
+
+ it 'returns empty' do
+ result = subject.execute
+
+ expect(result.status).to eq :success
+ expect(result.payload).to eq(results: [])
+ end
+ end
+
+ context 'with no wildcard' do
+ let(:query) { conan_package.name }
+
+ it 'makes a search using the beginning of the recipe' do
+ result = subject.execute
+
+ expect(result.status).to eq :success
+ expect(result.payload).to eq(results: [conan_package.conan_recipe])
+ end
+ end
+
+ context 'with full recipe match' do
+ let(:query) { conan_package.conan_recipe }
+
+ it 'makes an exact search' do
+ result = subject.execute
+
+ expect(result.status).to eq :success
+ expect(result.payload).to eq(results: [conan_package.conan_recipe])
+ end
+ end
+
+ context 'with malicious query' do
+ let(:query) { 'DROP TABLE foo;' }
+
+ it 'returns empty' do
+ result = subject.execute
+
+ expect(result.status).to eq :success
+ expect(result.payload).to eq(results: [])
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/create_dependency_service_spec.rb b/spec/services/packages/create_dependency_service_spec.rb
new file mode 100644
index 00000000000..00e5e5c6d96
--- /dev/null
+++ b/spec/services/packages/create_dependency_service_spec.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::CreateDependencyService do
+ describe '#execute' do
+ let_it_be(:namespace) {create(:namespace)}
+ let_it_be(:version) { '1.0.1' }
+ let_it_be(:package_name) { "@#{namespace.path}/my-app".freeze }
+
+ context 'when packages are published' do
+ let(:json_file) { 'packages/npm/payload.json' }
+ let(:params) do
+ Gitlab::Json.parse(fixture_file(json_file)
+ .gsub('@root/npm-test', package_name)
+ .gsub('1.0.1', version))
+ .with_indifferent_access
+ end
+ let(:package_version) { params[:versions].each_key.first }
+ let(:dependencies) { params[:versions][package_version] }
+ let(:package) { create(:npm_package) }
+ let(:dependency_names) { package.dependency_links.flat_map(&:dependency).map(&:name).sort }
+ let(:dependency_link_types) { package.dependency_links.map(&:dependency_type).sort }
+
+ subject { described_class.new(package, dependencies).execute }
+
+ it 'creates dependencies and links' do
+ expect(Packages::Dependency)
+ .to receive(:ids_for_package_names_and_version_patterns)
+ .once
+ .and_call_original
+
+ expect { subject }
+ .to change { Packages::Dependency.count }.by(1)
+ .and change { Packages::DependencyLink.count }.by(1)
+ expect(dependency_names).to match_array(%w(express))
+ expect(dependency_link_types).to match_array(%w(dependencies))
+ end
+
+ context 'with repeated packages' do
+ let(:json_file) { 'packages/npm/payload_with_duplicated_packages.json' }
+
+ it 'creates dependencies and links' do
+ expect(Packages::Dependency)
+ .to receive(:ids_for_package_names_and_version_patterns)
+ .exactly(4).times
+ .and_call_original
+
+ expect { subject }
+ .to change { Packages::Dependency.count }.by(4)
+ .and change { Packages::DependencyLink.count }.by(6)
+ expect(dependency_names).to match_array(%w(d3 d3 d3 dagre-d3 dagre-d3 express))
+ expect(dependency_link_types).to match_array(%w(bundleDependencies dependencies dependencies devDependencies devDependencies peerDependencies))
+ end
+ end
+
+ context 'with dependencies bulk insert conflicts' do
+ let_it_be(:rows) { [{ name: 'express', version_pattern: '^4.16.4' }] }
+
+ it 'creates dependences and links' do
+ original_bulk_insert = ::Gitlab::Database.method(:bulk_insert)
+ expect(::Gitlab::Database)
+ .to receive(:bulk_insert) do |table, rows, return_ids: false, disable_quote: [], on_conflict: nil|
+ call_count = table == Packages::Dependency.table_name ? 2 : 1
+ call_count.times { original_bulk_insert.call(table, rows, return_ids: return_ids, disable_quote: disable_quote, on_conflict: on_conflict) }
+ end.twice
+ expect(Packages::Dependency)
+ .to receive(:ids_for_package_names_and_version_patterns)
+ .twice
+ .and_call_original
+
+ expect { subject }
+ .to change { Packages::Dependency.count }.by(1)
+ .and change { Packages::DependencyLink.count }.by(1)
+ expect(dependency_names).to match_array(%w(express))
+ expect(dependency_link_types).to match_array(%w(dependencies))
+ end
+ end
+
+ context 'with existing dependencies' do
+ let(:other_package) { create(:npm_package) }
+
+ before do
+ described_class.new(other_package, dependencies).execute
+ end
+
+ it 'reuses them' do
+ expect { subject }
+ .to not_change { Packages::Dependency.count }
+ .and change { Packages::DependencyLink.count }.by(1)
+ end
+ end
+
+ context 'with a dependency not described with a hash' do
+ let(:invalid_dependencies) { dependencies.tap { |d| d['bundleDependencies'] = false } }
+
+ subject { described_class.new(package, invalid_dependencies).execute }
+
+ it 'creates dependencies and links' do
+ expect(Packages::Dependency)
+ .to receive(:ids_for_package_names_and_version_patterns)
+ .once
+ .and_call_original
+
+ expect { subject }
+ .to change { Packages::Dependency.count }.by(1)
+ .and change { Packages::DependencyLink.count }.by(1)
+ expect(dependency_names).to match_array(%w(express))
+ expect(dependency_link_types).to match_array(%w(dependencies))
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/create_package_file_service_spec.rb b/spec/services/packages/create_package_file_service_spec.rb
new file mode 100644
index 00000000000..93dde54916a
--- /dev/null
+++ b/spec/services/packages/create_package_file_service_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::CreatePackageFileService do
+ let(:package) { create(:maven_package) }
+
+ describe '#execute' do
+ context 'with valid params' do
+ let(:params) do
+ {
+ file: Tempfile.new,
+ file_name: 'foo.jar'
+ }
+ end
+
+ it 'creates a new package file' do
+ package_file = described_class.new(package, params).execute
+
+ expect(package_file).to be_valid
+ expect(package_file.file_name).to eq('foo.jar')
+ end
+ end
+
+ context 'file is missing' do
+ let(:params) do
+ {
+ file_name: 'foo.jar'
+ }
+ end
+
+ it 'raises an error' do
+ service = described_class.new(package, params)
+
+ expect { service.execute }.to raise_error(ActiveRecord::RecordInvalid)
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/maven/create_package_service_spec.rb b/spec/services/packages/maven/create_package_service_spec.rb
new file mode 100644
index 00000000000..bfdf62008ba
--- /dev/null
+++ b/spec/services/packages/maven/create_package_service_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Maven::CreatePackageService do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:app_name) { 'my-app' }
+ let(:version) { '1.0-SNAPSHOT' }
+ let(:path) { "my/company/app/#{app_name}" }
+ let(:path_with_version) { "#{path}/#{version}" }
+
+ describe '#execute' do
+ subject(:package) { described_class.new(project, user, params).execute }
+
+ context 'with version' do
+ let(:params) do
+ {
+ path: path_with_version,
+ name: path,
+ version: version
+ }
+ end
+
+ it 'creates a new package with metadatum' do
+ expect(package).to be_valid
+ expect(package.name).to eq(path)
+ expect(package.version).to eq(version)
+ expect(package.package_type).to eq('maven')
+ expect(package.maven_metadatum).to be_valid
+ expect(package.maven_metadatum.path).to eq(path_with_version)
+ expect(package.maven_metadatum.app_group).to eq('my.company.app')
+ expect(package.maven_metadatum.app_name).to eq(app_name)
+ expect(package.maven_metadatum.app_version).to eq(version)
+ end
+
+ it_behaves_like 'assigns build to package'
+ end
+
+ context 'without version' do
+ let(:params) do
+ {
+ path: path,
+ name: path,
+ version: nil
+ }
+ end
+
+ it 'creates a new package with metadatum' do
+ package = described_class.new(project, user, params).execute
+
+ expect(package).to be_valid
+ expect(package.name).to eq(path)
+ expect(package.version).to be nil
+ expect(package.maven_metadatum).to be_valid
+ expect(package.maven_metadatum.path).to eq(path)
+ expect(package.maven_metadatum.app_group).to eq('my.company.app')
+ expect(package.maven_metadatum.app_name).to eq(app_name)
+ expect(package.maven_metadatum.app_version).to be nil
+ end
+ end
+
+ context 'path is missing' do
+ let(:params) do
+ {
+ name: path,
+ version: version
+ }
+ end
+
+ it 'raises an error' do
+ service = described_class.new(project, user, params)
+
+ expect { service.execute }.to raise_error(ActiveRecord::RecordInvalid)
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/maven/find_or_create_package_service_spec.rb b/spec/services/packages/maven/find_or_create_package_service_spec.rb
new file mode 100644
index 00000000000..c9441324216
--- /dev/null
+++ b/spec/services/packages/maven/find_or_create_package_service_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Maven::FindOrCreatePackageService do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:app_name) { 'my-app' }
+ let_it_be(:version) { '1.0-SNAPSHOT' }
+ let_it_be(:path) { "my/company/app/#{app_name}" }
+ let_it_be(:path_with_version) { "#{path}/#{version}" }
+ let_it_be(:params) do
+ {
+ path: path_with_version,
+ name: path,
+ version: version
+ }
+ end
+
+ describe '#execute' do
+ subject { described_class.new(project, user, params).execute }
+
+ context 'without any existing package' do
+ it 'creates a package' do
+ expect { subject }.to change { Packages::Package.count }.by(1)
+ end
+ end
+
+ context 'with an existing package' do
+ let_it_be(:existing_package) { create(:maven_package, name: path, version: version, project: project) }
+
+ it { is_expected.to eq existing_package }
+ it "doesn't create a new package" do
+ expect { subject }
+ .to not_change { Packages::Package.count }
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/npm/create_package_service_spec.rb b/spec/services/packages/npm/create_package_service_spec.rb
new file mode 100644
index 00000000000..25bbbf82bec
--- /dev/null
+++ b/spec/services/packages/npm/create_package_service_spec.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Npm::CreatePackageService do
+ let(:namespace) {create(:namespace)}
+ let(:project) { create(:project, namespace: namespace) }
+ let(:user) { create(:user) }
+ let(:version) { '1.0.1' }
+
+ let(:params) do
+ Gitlab::Json.parse(fixture_file('packages/npm/payload.json')
+ .gsub('@root/npm-test', package_name)
+ .gsub('1.0.1', version)).with_indifferent_access
+ .merge!(override)
+ end
+ let(:override) { {} }
+ let(:package_name) { "@#{namespace.path}/my-app".freeze }
+
+ subject { described_class.new(project, user, params).execute }
+
+ shared_examples 'valid package' do
+ it 'creates a package' do
+ expect { subject }
+ .to change { Packages::Package.count }.by(1)
+ .and change { Packages::Package.npm.count }.by(1)
+ .and change { Packages::Tag.count }.by(1)
+ end
+
+ it { is_expected.to be_valid }
+
+ it 'creates a package with name and version' do
+ package = subject
+
+ expect(package.name).to eq(package_name)
+ expect(package.version).to eq(version)
+ end
+
+ it { expect(subject.name).to eq(package_name) }
+ it { expect(subject.version).to eq(version) }
+ end
+
+ describe '#execute' do
+ context 'scoped package' do
+ it_behaves_like 'valid package'
+
+ it_behaves_like 'assigns build to package'
+ end
+
+ context 'invalid package name' do
+ let(:package_name) { "@#{namespace.path}/my-group/my-app".freeze }
+
+ it { expect { subject }.to raise_error(ActiveRecord::RecordInvalid) }
+ end
+
+ context 'package already exists' do
+ let(:package_name) { "@#{namespace.path}/my_package" }
+ let!(:existing_package) { create(:npm_package, project: project, name: package_name, version: '1.0.1') }
+
+ it { expect(subject[:http_status]).to eq 403 }
+ it { expect(subject[:message]).to be 'Package already exists.' }
+ end
+
+ context 'with incorrect namespace' do
+ let(:package_name) { '@my_other_namespace/my-app' }
+
+ it 'raises a RecordInvalid error' do
+ expect { subject }.to raise_error(ActiveRecord::RecordInvalid)
+ end
+ end
+
+ context 'with empty versions' do
+ let(:override) { { versions: {} } }
+
+ it { expect(subject[:http_status]).to eq 400 }
+ it { expect(subject[:message]).to eq 'Version is empty.' }
+ end
+
+ context 'with invalid versions' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:version) do
+ [
+ '1',
+ '1.2',
+ '1./2.3',
+ '../../../../../1.2.3',
+ '%2e%2e%2f1.2.3'
+ ]
+ end
+
+ with_them do
+ it { expect { subject }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Version is invalid') }
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/npm/create_tag_service_spec.rb b/spec/services/packages/npm/create_tag_service_spec.rb
new file mode 100644
index 00000000000..e7a784068fa
--- /dev/null
+++ b/spec/services/packages/npm/create_tag_service_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Npm::CreateTagService do
+ let(:package) { create(:npm_package) }
+ let(:tag_name) { 'test-tag' }
+
+ describe '#execute' do
+ subject { described_class.new(package, tag_name).execute }
+
+ shared_examples 'it creates the tag' do
+ it { expect { subject }.to change { Packages::Tag.count }.by(1) }
+ it { expect(subject.name).to eq(tag_name) }
+ it 'adds tag to the package' do
+ tag = subject
+ expect(package.reload.tags).to match_array([tag])
+ end
+ end
+
+ context 'with no existing tag name' do
+ it_behaves_like 'it creates the tag'
+ end
+
+ context 'with exisiting tag name' do
+ let!(:package_tag2) { create(:packages_tag, package: package2, name: tag_name) }
+
+ context 'on package with different name' do
+ let!(:package2) { create(:npm_package, project: package.project) }
+
+ it_behaves_like 'it creates the tag'
+ end
+
+ context 'on different package type' do
+ let!(:package2) { create(:conan_package, project: package.project, name: 'conan_package_name', version: package.version) }
+
+ it_behaves_like 'it creates the tag'
+ end
+
+ context 'on same package with different version' do
+ let!(:package2) { create(:npm_package, project: package.project, name: package.name, version: '5.0.0-testing') }
+
+ it { expect { subject }.to not_change { Packages::Tag.count } }
+ it { expect(subject.name).to eq(tag_name) }
+
+ it 'adds tag to the package' do
+ tag = subject
+ expect(package.reload.tags).to match_array([tag])
+ expect(package2.reload.tags).to be_empty
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/nuget/create_dependency_service_spec.rb b/spec/services/packages/nuget/create_dependency_service_spec.rb
new file mode 100644
index 00000000000..268c8837e25
--- /dev/null
+++ b/spec/services/packages/nuget/create_dependency_service_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::CreateDependencyService do
+ let_it_be(:package, reload: true) { create(:nuget_package) }
+
+ describe '#execute' do
+ RSpec.shared_examples 'creating dependencies, links and nuget metadata for' do |expected_dependency_names, dependency_count, dependency_link_count|
+ let(:dependencies_with_metadata) { dependencies.select { |dep| dep[:target_framework].present? } }
+
+ it 'creates dependencies, links and nuget metadata' do
+ expect { subject }
+ .to change { Packages::Dependency.count }.by(dependency_count)
+ .and change { Packages::DependencyLink.count }.by(dependency_link_count)
+ .and change { Packages::Nuget::DependencyLinkMetadatum.count }.by(dependencies_with_metadata.size)
+ expect(expected_dependency_names).to contain_exactly(*dependency_names)
+ expect(package.dependency_links.map(&:dependency_type).uniq).to contain_exactly('dependencies')
+
+ dependencies_with_metadata.each do |dependency|
+ name = dependency[:name]
+ version_pattern = service.send(:version_or_empty_string, dependency[:version])
+ metadatum = package.dependency_links.joins(:dependency)
+ .find_by(packages_dependencies: { name: name, version_pattern: version_pattern })
+ .nuget_metadatum
+ expect(metadatum.target_framework).to eq dependency[:target_framework]
+ end
+ end
+ end
+
+ let_it_be(:dependencies) do
+ [
+ { name: 'Moqi', version: '2.5.6' },
+ { name: 'Castle.Core' },
+ { name: 'Test.Dependency', version: '2.3.7', target_framework: '.NETStandard2.0' },
+ { name: 'Newtonsoft.Json', version: '12.0.3', target_framework: '.NETStandard2.0' }
+ ]
+ end
+
+ let(:dependency_names) { package.dependency_links.flat_map(&:dependency).map(&:name) }
+ let(:service) { described_class.new(package, dependencies) }
+
+ subject { service.execute }
+
+ it_behaves_like 'creating dependencies, links and nuget metadata for', %w(Castle.Core Moqi Newtonsoft.Json Test.Dependency), 4, 4
+
+ context 'with existing dependencies' do
+ let_it_be(:exisiting_dependency) { create(:packages_dependency, name: 'Moqi', version_pattern: '2.5.6') }
+
+ it_behaves_like 'creating dependencies, links and nuget metadata for', %w(Castle.Core Moqi Newtonsoft.Json Test.Dependency), 3, 4
+ end
+
+ context 'with dependencies with no target framework' do
+ let_it_be(:dependencies) do
+ [
+ { name: 'Moqi', version: '2.5.6' },
+ { name: 'Castle.Core' },
+ { name: 'Test.Dependency', version: '2.3.7' },
+ { name: 'Newtonsoft.Json', version: '12.0.3' }
+ ]
+ end
+
+ it_behaves_like 'creating dependencies, links and nuget metadata for', %w(Castle.Core Moqi Newtonsoft.Json Test.Dependency), 4, 4
+ end
+
+ context 'with empty dependencies' do
+ let_it_be(:dependencies) { [] }
+
+ it 'is a no op' do
+ expect(service).not_to receive(:create_dependency_links)
+ expect(service).not_to receive(:create_dependency_link_metadata)
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/nuget/create_package_service_spec.rb b/spec/services/packages/nuget/create_package_service_spec.rb
new file mode 100644
index 00000000000..1579b42d9ad
--- /dev/null
+++ b/spec/services/packages/nuget/create_package_service_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::CreatePackageService do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:params) { {} }
+
+ describe '#execute' do
+ subject { described_class.new(project, user, params).execute }
+
+ it 'creates the package' do
+ expect { subject }.to change { Packages::Package.count }.by(1)
+ package = Packages::Package.last
+
+ expect(package).to be_valid
+ expect(package.name).to eq(Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME)
+ expect(package.version).to start_with(Packages::Nuget::CreatePackageService::PACKAGE_VERSION)
+ expect(package.package_type).to eq('nuget')
+ end
+
+ it 'can create two packages in a row' do
+ expect { subject }.to change { Packages::Package.count }.by(1)
+ expect { described_class.new(project, user, params).execute }.to change { Packages::Package.count }.by(1)
+
+ package = Packages::Package.last
+
+ expect(package).to be_valid
+ expect(package.name).to eq(Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME)
+ expect(package.version).to start_with(Packages::Nuget::CreatePackageService::PACKAGE_VERSION)
+ expect(package.package_type).to eq('nuget')
+ end
+ end
+end
diff --git a/spec/services/packages/nuget/metadata_extraction_service_spec.rb b/spec/services/packages/nuget/metadata_extraction_service_spec.rb
new file mode 100644
index 00000000000..39fc0f9e6a1
--- /dev/null
+++ b/spec/services/packages/nuget/metadata_extraction_service_spec.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::MetadataExtractionService do
+ let(:package_file) { create(:nuget_package).package_files.first }
+ let(:service) { described_class.new(package_file.id) }
+
+ describe '#execute' do
+ subject { service.execute }
+
+ context 'with valid package file id' do
+ expected_metadata = {
+ package_name: 'DummyProject.DummyPackage',
+ package_version: '1.0.0',
+ package_dependencies: [
+ {
+ name: 'Newtonsoft.Json',
+ target_framework: '.NETCoreApp3.0',
+ version: '12.0.3'
+ }
+ ],
+ package_tags: []
+ }
+
+ it { is_expected.to eq(expected_metadata) }
+ end
+
+ context 'with nuspec file' do
+ before do
+ allow(service).to receive(:nuspec_file).and_return(fixture_file(nuspec_filepath))
+ end
+
+ context 'with dependencies' do
+ let(:nuspec_filepath) { 'packages/nuget/with_dependencies.nuspec' }
+
+ it { is_expected.to have_key(:package_dependencies) }
+
+ it 'extracts dependencies' do
+ dependencies = subject[:package_dependencies]
+
+ expect(dependencies).to include(name: 'Moqi', version: '2.5.6')
+ expect(dependencies).to include(name: 'Castle.Core')
+ expect(dependencies).to include(name: 'Test.Dependency', version: '2.3.7', target_framework: '.NETStandard2.0')
+ expect(dependencies).to include(name: 'Newtonsoft.Json', version: '12.0.3', target_framework: '.NETStandard2.0')
+ end
+ end
+
+ context 'with a nuspec file with metadata' do
+ let(:nuspec_filepath) { 'packages/nuget/with_metadata.nuspec' }
+
+ it { expect(subject[:package_tags].sort).to eq(%w(foo bar test tag1 tag2 tag3 tag4 tag5).sort) }
+ end
+ end
+
+ context 'with a nuspec file with metadata' do
+ let_it_be(:nuspec_filepath) { 'packages/nuget/with_metadata.nuspec' }
+
+ before do
+ allow(service).to receive(:nuspec_file).and_return(fixture_file(nuspec_filepath))
+ end
+
+ it { expect(subject[:license_url]).to eq('https://opensource.org/licenses/MIT') }
+ it { expect(subject[:project_url]).to eq('https://gitlab.com/gitlab-org/gitlab') }
+ it { expect(subject[:icon_url]).to eq('https://opensource.org/files/osi_keyhole_300X300_90ppi_0.png') }
+ end
+
+ context 'with invalid package file id' do
+ let(:package_file) { OpenStruct.new(id: 555) }
+
+ it { expect { subject }.to raise_error(::Packages::Nuget::MetadataExtractionService::ExtractionError, 'invalid package file') }
+ end
+
+ context 'linked to a non nuget package' do
+ before do
+ package_file.package.maven!
+ end
+
+ it { expect { subject }.to raise_error(::Packages::Nuget::MetadataExtractionService::ExtractionError, 'invalid package file') }
+ end
+
+ context 'with a 0 byte package file id' do
+ before do
+ allow_any_instance_of(Packages::PackageFileUploader).to receive(:size).and_return(0)
+ end
+
+ it { expect { subject }.to raise_error(::Packages::Nuget::MetadataExtractionService::ExtractionError, 'invalid package file') }
+ end
+
+ context 'without the nuspec file' do
+ before do
+ allow_any_instance_of(Zip::File).to receive(:glob).and_return([])
+ end
+
+ it { expect { subject }.to raise_error(::Packages::Nuget::MetadataExtractionService::ExtractionError, 'nuspec file not found') }
+ end
+
+ context 'with a too big nuspec file' do
+ before do
+ allow_any_instance_of(Zip::File).to receive(:glob).and_return([OpenStruct.new(size: 6.megabytes)])
+ end
+
+ it { expect { subject }.to raise_error(::Packages::Nuget::MetadataExtractionService::ExtractionError, 'nuspec file too big') }
+ end
+ end
+end
diff --git a/spec/services/packages/nuget/search_service_spec.rb b/spec/services/packages/nuget/search_service_spec.rb
new file mode 100644
index 00000000000..d163e7087e4
--- /dev/null
+++ b/spec/services/packages/nuget/search_service_spec.rb
@@ -0,0 +1,116 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::SearchService do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:package_a) { create(:nuget_package, project: project, name: 'DummyPackageA') }
+ let_it_be(:packages_b) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageB') }
+ let_it_be(:packages_c) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageC') }
+ let_it_be(:package_d) { create(:nuget_package, project: project, name: 'FooBarD') }
+ let_it_be(:other_package_a) { create(:nuget_package, name: 'DummyPackageA') }
+ let_it_be(:other_package_a) { create(:nuget_package, name: 'DummyPackageB') }
+ let(:search_term) { 'ummy' }
+ let(:per_page) { 5 }
+ let(:padding) { 0 }
+ let(:include_prerelease_versions) { true }
+ let(:options) { { include_prerelease_versions: include_prerelease_versions, per_page: per_page, padding: padding } }
+
+ describe '#execute' do
+ subject { described_class.new(project, search_term, options).execute }
+
+ it { expect_search_results 3, package_a, packages_b, packages_c }
+
+ context 'with a smaller per page count' do
+ let(:per_page) { 2 }
+
+ it { expect_search_results 3, package_a, packages_b }
+ end
+
+ context 'with 0 per page count' do
+ let(:per_page) { 0 }
+
+ it { expect_search_results 3, [] }
+ end
+
+ context 'with a negative per page count' do
+ let(:per_page) { -1 }
+
+ it { expect { subject }.to raise_error(ArgumentError, 'negative per_page') }
+ end
+
+ context 'with a padding' do
+ let(:padding) { 2 }
+
+ it { expect_search_results 3, packages_c }
+ end
+
+ context 'with a too big padding' do
+ let(:padding) { 5 }
+
+ it { expect_search_results 3, [] }
+ end
+
+ context 'with a negative padding' do
+ let(:padding) { -1 }
+
+ it { expect { subject }.to raise_error(ArgumentError, 'negative padding') }
+ end
+
+ context 'with search term' do
+ let(:search_term) { 'umm' }
+
+ it { expect_search_results 3, package_a, packages_b, packages_c }
+ end
+
+ context 'with nil search term' do
+ let(:search_term) { nil }
+
+ it { expect_search_results 4, package_a, packages_b, packages_c, package_d }
+ end
+
+ context 'with empty search term' do
+ let(:search_term) { '' }
+
+ it { expect_search_results 4, package_a, packages_b, packages_c, package_d }
+ end
+
+ context 'with prefix search term' do
+ let(:search_term) { 'dummy' }
+
+ it { expect_search_results 3, package_a, packages_b, packages_c }
+ end
+
+ context 'with suffix search term' do
+ let(:search_term) { 'packagec' }
+
+ it { expect_search_results 1, packages_c }
+ end
+
+ context 'with pre release packages' do
+ let_it_be(:package_e) { create(:nuget_package, project: project, name: 'DummyPackageE', version: '3.2.1-alpha') }
+
+ context 'including them' do
+ it { expect_search_results 4, package_a, packages_b, packages_c, package_e }
+ end
+
+ context 'excluding them' do
+ let(:include_prerelease_versions) { false }
+
+ it { expect_search_results 3, package_a, packages_b, packages_c }
+
+ context 'when mixed with release versions' do
+ let_it_be(:package_e_release) { create(:nuget_package, project: project, name: 'DummyPackageE', version: '3.2.1') }
+
+ it { expect_search_results 4, package_a, packages_b, packages_c, package_e_release }
+ end
+ end
+ end
+
+ def expect_search_results(total_count, *results)
+ search = subject
+
+ expect(search.total_count).to eq total_count
+ expect(search.results).to match_array(Array.wrap(results).flatten)
+ end
+ end
+end
diff --git a/spec/services/packages/nuget/sync_metadatum_service_spec.rb b/spec/services/packages/nuget/sync_metadatum_service_spec.rb
new file mode 100644
index 00000000000..32093c48b76
--- /dev/null
+++ b/spec/services/packages/nuget/sync_metadatum_service_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::SyncMetadatumService do
+ let_it_be(:package, reload: true) { create(:nuget_package) }
+ let_it_be(:metadata) do
+ {
+ project_url: 'https://test.org/test',
+ license_url: 'https://test.org/MIT',
+ icon_url: 'https://test.org/icon.png'
+ }
+ end
+
+ let(:service) { described_class.new(package, metadata) }
+ let(:nuget_metadatum) { package.nuget_metadatum }
+
+ describe '#execute' do
+ subject { service.execute }
+
+ RSpec.shared_examples 'saving metadatum attributes' do
+ it 'saves nuget metadatum' do
+ subject
+
+ metadata.each do |attribute, expected_value|
+ expect(nuget_metadatum.send(attribute)).to eq(expected_value)
+ end
+ end
+ end
+
+ it 'creates a nuget metadatum' do
+ expect { subject }
+ .to change { package.nuget_metadatum.present? }.from(false).to(true)
+ end
+
+ it_behaves_like 'saving metadatum attributes'
+
+ context 'with exisiting nuget metadatum' do
+ let_it_be(:package) { create(:nuget_package, :with_metadatum) }
+
+ it 'does not create a nuget metadatum' do
+ expect { subject }.to change { ::Packages::Nuget::Metadatum.count }.by(0)
+ end
+
+ it_behaves_like 'saving metadatum attributes'
+
+ context 'with empty metadata' do
+ let_it_be(:metadata) { {} }
+
+ it 'destroys the nuget metadatum' do
+ expect { subject }
+ .to change { package.reload.nuget_metadatum.present? }.from(true).to(false)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/nuget/update_package_from_metadata_service_spec.rb b/spec/services/packages/nuget/update_package_from_metadata_service_spec.rb
new file mode 100644
index 00000000000..b7c780c1ee2
--- /dev/null
+++ b/spec/services/packages/nuget/update_package_from_metadata_service_spec.rb
@@ -0,0 +1,237 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_redis_shared_state do
+ include ExclusiveLeaseHelpers
+
+ let(:package) { create(:nuget_package) }
+ let(:package_file) { package.package_files.first }
+ let(:service) { described_class.new(package_file) }
+ let(:package_name) { 'DummyProject.DummyPackage' }
+ let(:package_version) { '1.0.0' }
+ let(:package_file_name) { 'dummyproject.dummypackage.1.0.0.nupkg' }
+
+ RSpec.shared_examples 'raising an' do |error_class|
+ it "raises an #{error_class}" do
+ expect { subject }.to raise_error(error_class)
+ end
+ end
+
+ describe '#execute' do
+ subject { service.execute }
+
+ before do
+ stub_package_file_object_storage(enabled: true, direct_upload: true)
+ end
+
+ RSpec.shared_examples 'taking the lease' do
+ before do
+ allow(service).to receive(:lease_release?).and_return(false)
+ end
+
+ it 'takes the lease' do
+ expect(service).to receive(:try_obtain_lease).and_call_original
+
+ subject
+
+ expect(service.exclusive_lease.exists?).to be_truthy
+ end
+ end
+
+ RSpec.shared_examples 'not updating the package if the lease is taken' do
+ context 'without obtaining the exclusive lease' do
+ let(:lease_key) { "packages:nuget:update_package_from_metadata_service:package:#{package_id}" }
+ let(:metadata) { { package_name: package_name, package_version: package_version } }
+ let(:package_from_package_file) { package_file.package }
+
+ before do
+ stub_exclusive_lease_taken(lease_key, timeout: 1.hour)
+ # to allow the above stub, we need to stub the metadata function as the
+ # original implementation will try to get an exclusive lease on the
+ # file in object storage
+ allow(service).to receive(:metadata).and_return(metadata)
+ end
+
+ it 'does not update the package' do
+ expect(service).to receive(:try_obtain_lease).and_call_original
+
+ expect { subject }
+ .to change { ::Packages::Package.count }.by(0)
+ .and change { Packages::DependencyLink.count }.by(0)
+ expect(package_file.reload.file_name).not_to eq(package_file_name)
+ expect(package_file.package.reload.name).not_to eq(package_name)
+ expect(package_file.package.version).not_to eq(package_version)
+ end
+ end
+ end
+
+ context 'with no existing package' do
+ let(:package_id) { package.id }
+
+ it 'updates package and package file' do
+ expect { subject }
+ .to change { ::Packages::Package.count }.by(1)
+ .and change { Packages::Dependency.count }.by(1)
+ .and change { Packages::DependencyLink.count }.by(1)
+ .and change { ::Packages::Nuget::Metadatum.count }.by(0)
+
+ expect(package.reload.name).to eq(package_name)
+ expect(package.version).to eq(package_version)
+ expect(package_file.reload.file_name).to eq(package_file_name)
+ # hard reset needed to properly reload package_file.file
+ expect(Packages::PackageFile.find(package_file.id).file.size).not_to eq 0
+ end
+
+ it_behaves_like 'taking the lease'
+
+ it_behaves_like 'not updating the package if the lease is taken'
+ end
+
+ context 'with existing package' do
+ let!(:existing_package) { create(:nuget_package, project: package.project, name: package_name, version: package_version) }
+ let(:package_id) { existing_package.id }
+
+ it 'link existing package and updates package file' do
+ expect(service).to receive(:try_obtain_lease).and_call_original
+
+ expect { subject }
+ .to change { ::Packages::Package.count }.by(-1)
+ .and change { Packages::Dependency.count }.by(0)
+ .and change { Packages::DependencyLink.count }.by(0)
+ .and change { Packages::Nuget::DependencyLinkMetadatum.count }.by(0)
+ .and change { ::Packages::Nuget::Metadatum.count }.by(0)
+ expect(package_file.reload.file_name).to eq(package_file_name)
+ expect(package_file.package).to eq(existing_package)
+ end
+
+ it_behaves_like 'taking the lease'
+
+ it_behaves_like 'not updating the package if the lease is taken'
+ end
+
+ context 'with a nuspec file with metadata' do
+ let(:nuspec_filepath) { 'packages/nuget/with_metadata.nuspec' }
+ let(:expected_tags) { %w(foo bar test tag1 tag2 tag3 tag4 tag5) }
+
+ before do
+ allow_any_instance_of(Packages::Nuget::MetadataExtractionService)
+ .to receive(:nuspec_file)
+ .and_return(fixture_file(nuspec_filepath))
+ end
+
+ it 'creates tags' do
+ expect(service).to receive(:try_obtain_lease).and_call_original
+ expect { subject }.to change { ::Packages::Tag.count }.by(8)
+ expect(package.reload.tags.map(&:name)).to contain_exactly(*expected_tags)
+ end
+
+ context 'with existing package and tags' do
+ let!(:existing_package) { create(:nuget_package, project: package.project, name: 'DummyProject.WithMetadata', version: '1.2.3') }
+ let!(:tag1) { create(:packages_tag, package: existing_package, name: 'tag1') }
+ let!(:tag2) { create(:packages_tag, package: existing_package, name: 'tag2') }
+ let!(:tag3) { create(:packages_tag, package: existing_package, name: 'tag_not_in_metadata') }
+
+ it 'creates tags and deletes those not in metadata' do
+ expect(service).to receive(:try_obtain_lease).and_call_original
+ expect { subject }.to change { ::Packages::Tag.count }.by(5)
+ expect(existing_package.tags.map(&:name)).to contain_exactly(*expected_tags)
+ end
+ end
+
+ it 'creates nuget metadatum' do
+ expect { subject }
+ .to change { ::Packages::Package.count }.by(1)
+ .and change { ::Packages::Nuget::Metadatum.count }.by(1)
+
+ metadatum = package_file.reload.package.nuget_metadatum
+ expect(metadatum.license_url).to eq('https://opensource.org/licenses/MIT')
+ expect(metadatum.project_url).to eq('https://gitlab.com/gitlab-org/gitlab')
+ expect(metadatum.icon_url).to eq('https://opensource.org/files/osi_keyhole_300X300_90ppi_0.png')
+ end
+
+ context 'with too long url' do
+ let_it_be(:too_long_url) { "http://localhost/#{'bananas' * 50}" }
+
+ let(:metadata) { { package_name: package_name, package_version: package_version, license_url: too_long_url } }
+
+ before do
+ allow(service).to receive(:metadata).and_return(metadata)
+ end
+
+ it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError
+ end
+ end
+
+ context 'with nuspec file with dependencies' do
+ let(:nuspec_filepath) { 'packages/nuget/with_dependencies.nuspec' }
+ let(:package_name) { 'Test.Package' }
+ let(:package_version) { '3.5.2' }
+ let(:package_file_name) { 'test.package.3.5.2.nupkg' }
+
+ before do
+ allow_any_instance_of(Packages::Nuget::MetadataExtractionService)
+ .to receive(:nuspec_file)
+ .and_return(fixture_file(nuspec_filepath))
+ end
+
+ it 'updates package and package file' do
+ expect { subject }
+ .to change { ::Packages::Package.count }.by(1)
+ .and change { Packages::Dependency.count }.by(4)
+ .and change { Packages::DependencyLink.count }.by(4)
+ .and change { Packages::Nuget::DependencyLinkMetadatum.count }.by(2)
+
+ expect(package.reload.name).to eq(package_name)
+ expect(package.version).to eq(package_version)
+ expect(package_file.reload.file_name).to eq(package_file_name)
+ # hard reset needed to properly reload package_file.file
+ expect(Packages::PackageFile.find(package_file.id).file.size).not_to eq 0
+ end
+ end
+
+ context 'with package file not containing a nuspec file' do
+ before do
+ allow_any_instance_of(Zip::File).to receive(:glob).and_return([])
+ end
+
+ it_behaves_like 'raising an', ::Packages::Nuget::MetadataExtractionService::ExtractionError
+ end
+
+ context 'with package file with a blank package name' do
+ before do
+ allow(service).to receive(:package_name).and_return('')
+ end
+
+ it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError
+ end
+
+ context 'with package file with a blank package version' do
+ before do
+ allow(service).to receive(:package_version).and_return('')
+ end
+
+ it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError
+ end
+
+ context 'with an invalid package version' do
+ invalid_versions = [
+ '555',
+ '1.2',
+ '1./2.3',
+ '../../../../../1.2.3',
+ '%2e%2e%2f1.2.3'
+ ]
+
+ invalid_versions.each do |invalid_version|
+ it "raises an error for version #{invalid_version}" do
+ allow(service).to receive(:package_version).and_return(invalid_version)
+
+ expect { subject }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Version is invalid')
+ expect(package_file.file_name).not_to include(invalid_version)
+ expect(package_file.file.file.path).not_to include(invalid_version)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/pypi/create_package_service_spec.rb b/spec/services/packages/pypi/create_package_service_spec.rb
new file mode 100644
index 00000000000..250b43d1f75
--- /dev/null
+++ b/spec/services/packages/pypi/create_package_service_spec.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Pypi::CreatePackageService do
+ include PackagesManagerApiSpecHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:params) do
+ {
+ name: 'foo',
+ version: '1.0',
+ content: temp_file('foo.tgz'),
+ requires_python: '>=2.7',
+ sha256_digest: '123',
+ md5_digest: '567'
+ }
+ end
+
+ describe '#execute' do
+ subject { described_class.new(project, user, params).execute }
+
+ let(:created_package) { Packages::Package.pypi.last }
+
+ context 'without an existing package' do
+ it 'creates the package' do
+ expect { subject }.to change { Packages::Package.pypi.count }.by(1)
+
+ expect(created_package.name).to eq 'foo'
+ expect(created_package.version).to eq '1.0'
+
+ expect(created_package.pypi_metadatum.required_python).to eq '>=2.7'
+ expect(created_package.package_files.size).to eq 1
+ expect(created_package.package_files.first.file_name).to eq 'foo.tgz'
+ expect(created_package.package_files.first.file_sha256).to eq '123'
+ expect(created_package.package_files.first.file_md5).to eq '567'
+ end
+ end
+
+ context 'with an existing package' do
+ before do
+ described_class.new(project, user, params).execute
+ end
+
+ context 'with an existing file' do
+ before do
+ params[:content] = temp_file('foo.tgz')
+ params[:sha256_digest] = 'abc'
+ params[:md5_digest] = 'def'
+ end
+
+ it 'replaces the file' do
+ expect { subject }
+ .to change { Packages::Package.pypi.count }.by(0)
+ .and change { Packages::PackageFile.count }.by(1)
+
+ expect(created_package.package_files.size).to eq 2
+ expect(created_package.package_files.first.file_name).to eq 'foo.tgz'
+ expect(created_package.package_files.first.file_sha256).to eq '123'
+ expect(created_package.package_files.first.file_md5).to eq '567'
+ expect(created_package.package_files.last.file_name).to eq 'foo.tgz'
+ expect(created_package.package_files.last.file_sha256).to eq 'abc'
+ expect(created_package.package_files.last.file_md5).to eq 'def'
+ end
+ end
+
+ context 'without an existing file' do
+ before do
+ params[:content] = temp_file('another.tgz')
+ end
+
+ it 'adds the file' do
+ expect { subject }
+ .to change { Packages::Package.pypi.count }.by(0)
+ .and change { Packages::PackageFile.count }.by(1)
+
+ expect(created_package.package_files.size).to eq 2
+ expect(created_package.package_files.map(&:file_name).sort).to eq ['another.tgz', 'foo.tgz']
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/remove_tag_service_spec.rb b/spec/services/packages/remove_tag_service_spec.rb
new file mode 100644
index 00000000000..084635824e5
--- /dev/null
+++ b/spec/services/packages/remove_tag_service_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::RemoveTagService do
+ let!(:package_tag) { create(:packages_tag) }
+
+ describe '#execute' do
+ subject { described_class.new(package_tag).execute }
+
+ context 'with existing tag' do
+ it { expect { subject }.to change { Packages::Tag.count }.by(-1) }
+ end
+
+ context 'with nil' do
+ subject { described_class.new(nil) }
+
+ it { expect { subject }.to raise_error(ArgumentError) }
+ end
+ end
+end
diff --git a/spec/services/packages/update_tags_service_spec.rb b/spec/services/packages/update_tags_service_spec.rb
new file mode 100644
index 00000000000..4a122d1c718
--- /dev/null
+++ b/spec/services/packages/update_tags_service_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::UpdateTagsService do
+ let_it_be(:package, reload: true) { create(:nuget_package) }
+
+ let(:tags) { %w(test-tag tag1 tag2 tag3) }
+ let(:service) { described_class.new(package, tags) }
+
+ describe '#execute' do
+ subject { service.execute }
+
+ RSpec.shared_examples 'updating tags' do |tags_count|
+ it 'updates a tag' do
+ expect { subject }.to change { Packages::Tag.count }.by(tags_count)
+ expect(package.reload.tags.map(&:name)).to contain_exactly(*tags)
+ end
+ end
+
+ it_behaves_like 'updating tags', 4
+
+ context 'with an existing tag' do
+ before do
+ create(:packages_tag, package: package2, name: 'test-tag')
+ end
+
+ context 'on the same package' do
+ let_it_be(:package2) { package }
+
+ it_behaves_like 'updating tags', 3
+
+ context 'with different name' do
+ before do
+ create(:packages_tag, package: package2, name: 'to_be_destroyed')
+ end
+
+ it_behaves_like 'updating tags', 2
+ end
+ end
+
+ context 'on a different package' do
+ let_it_be(:package2) { create(:nuget_package) }
+
+ it_behaves_like 'updating tags', 4
+ end
+ end
+
+ context 'with empty tags' do
+ let(:tags) { [] }
+
+ it 'is a no op' do
+ expect(package).not_to receive(:tags)
+ expect(::Gitlab::Database).not_to receive(:bulk_insert)
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/services/pages/delete_services_spec.rb b/spec/services/pages/delete_services_spec.rb
index c253f294e80..f6d4694b4dd 100644
--- a/spec/services/pages/delete_services_spec.rb
+++ b/spec/services/pages/delete_services_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Pages::DeleteService do
+RSpec.describe Pages::DeleteService do
let_it_be(:project) { create(:project, path: "my.project")}
let_it_be(:admin) { create(:admin) }
let_it_be(:domain) { create(:pages_domain, project: project) }
diff --git a/spec/services/pages_domains/create_acme_order_service_spec.rb b/spec/services/pages_domains/create_acme_order_service_spec.rb
index d59aa9b979e..35b2cc56973 100644
--- a/spec/services/pages_domains/create_acme_order_service_spec.rb
+++ b/spec/services/pages_domains/create_acme_order_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PagesDomains::CreateAcmeOrderService do
+RSpec.describe PagesDomains::CreateAcmeOrderService do
include LetsEncryptHelpers
let(:pages_domain) { create(:pages_domain) }
diff --git a/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb
index 22fcc6b9a79..4d489d7fe4b 100644
--- a/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb
+++ b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PagesDomains::ObtainLetsEncryptCertificateService do
+RSpec.describe PagesDomains::ObtainLetsEncryptCertificateService do
include LetsEncryptHelpers
let(:pages_domain) { create(:pages_domain, :without_certificate, :without_key) }
diff --git a/spec/services/pages_domains/retry_acme_order_service_spec.rb b/spec/services/pages_domains/retry_acme_order_service_spec.rb
index 0185f10864c..601de24e766 100644
--- a/spec/services/pages_domains/retry_acme_order_service_spec.rb
+++ b/spec/services/pages_domains/retry_acme_order_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PagesDomains::RetryAcmeOrderService do
+RSpec.describe PagesDomains::RetryAcmeOrderService do
let(:domain) { create(:pages_domain, auto_ssl_enabled: true, auto_ssl_failed: true) }
let(:service) { described_class.new(domain) }
diff --git a/spec/services/personal_access_tokens/create_service_spec.rb b/spec/services/personal_access_tokens/create_service_spec.rb
index 9190434b96a..475ade95948 100644
--- a/spec/services/personal_access_tokens/create_service_spec.rb
+++ b/spec/services/personal_access_tokens/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PersonalAccessTokens::CreateService do
+RSpec.describe PersonalAccessTokens::CreateService do
describe '#execute' do
context 'with valid params' do
it 'creates personal access token record' do
diff --git a/spec/services/personal_access_tokens/last_used_service_spec.rb b/spec/services/personal_access_tokens/last_used_service_spec.rb
new file mode 100644
index 00000000000..6fc74e27dd9
--- /dev/null
+++ b/spec/services/personal_access_tokens/last_used_service_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe PersonalAccessTokens::LastUsedService do
+ describe '#execute' do
+ subject { described_class.new(personal_access_token).execute }
+
+ context 'when the personal access token has not been used recently' do
+ let_it_be(:personal_access_token) { create(:personal_access_token, last_used_at: 1.year.ago) }
+
+ it 'updates the last_used_at timestamp' do
+ expect { subject }.to change { personal_access_token.last_used_at }
+ end
+
+ it 'does not run on read-only GitLab instances' do
+ allow(::Gitlab::Database).to receive(:read_only?).and_return(true)
+
+ expect { subject }.not_to change { personal_access_token.last_used_at }
+ end
+ end
+
+ context 'when the personal access token has been used recently' do
+ let_it_be(:personal_access_token) { create(:personal_access_token, last_used_at: 1.minute.ago) }
+
+ it 'does not update the last_used_at timestamp' do
+ expect { subject }.not_to change { personal_access_token.last_used_at }
+ end
+ end
+
+ context 'when the last_used_at timestamp is nil' do
+ let_it_be(:personal_access_token) { create(:personal_access_token, last_used_at: nil) }
+
+ it 'updates the last_used_at timestamp' do
+ expect { subject }.to change { personal_access_token.last_used_at }
+ end
+ end
+
+ context 'when not a personal access token' do
+ let_it_be(:personal_access_token) { create(:oauth_access_token) }
+
+ it 'does not execute' do
+ expect(subject).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/services/pod_logs/base_service_spec.rb b/spec/services/pod_logs/base_service_spec.rb
index bc4989b59d9..6f7731fda3a 100644
--- a/spec/services/pod_logs/base_service_spec.rb
+++ b/spec/services/pod_logs/base_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::PodLogs::BaseService do
+RSpec.describe ::PodLogs::BaseService do
include KubernetesHelpers
let_it_be(:cluster) { create(:cluster, :provided_by_gcp, environment_scope: '*') }
diff --git a/spec/services/pod_logs/elasticsearch_service_spec.rb b/spec/services/pod_logs/elasticsearch_service_spec.rb
index 8060d07461a..9431e47c6f2 100644
--- a/spec/services/pod_logs/elasticsearch_service_spec.rb
+++ b/spec/services/pod_logs/elasticsearch_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::PodLogs::ElasticsearchService do
+RSpec.describe ::PodLogs::ElasticsearchService do
let_it_be(:cluster) { create(:cluster, :provided_by_gcp, environment_scope: '*') }
let(:namespace) { 'autodevops-deploy-9-production' }
diff --git a/spec/services/pod_logs/kubernetes_service_spec.rb b/spec/services/pod_logs/kubernetes_service_spec.rb
index a1f7645323b..3e31ff15c1b 100644
--- a/spec/services/pod_logs/kubernetes_service_spec.rb
+++ b/spec/services/pod_logs/kubernetes_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::PodLogs::KubernetesService do
+RSpec.describe ::PodLogs::KubernetesService do
include KubernetesHelpers
let_it_be(:cluster) { create(:cluster, :provided_by_gcp, environment_scope: '*') }
diff --git a/spec/services/post_receive_service_spec.rb b/spec/services/post_receive_service_spec.rb
index 25f4122f134..c726e1851a7 100644
--- a/spec/services/post_receive_service_spec.rb
+++ b/spec/services/post_receive_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PostReceiveService do
+RSpec.describe PostReceiveService do
include Gitlab::Routing
let_it_be(:user) { create(:user) }
@@ -166,41 +166,6 @@ describe PostReceiveService do
expect(subject).to include(build_alert_message(message))
end
end
-
- context 'storage size limit alerts' do
- let(:check_storage_size_response) { ServiceResponse.success }
-
- before do
- expect_next_instance_of(Namespaces::CheckStorageSizeService, project.namespace, user) do |check_storage_size_service|
- expect(check_storage_size_service).to receive(:execute).and_return(check_storage_size_response)
- end
- end
-
- context 'when there is no payload' do
- it 'adds no alert' do
- expect(subject.size).to eq(1)
- end
- end
-
- context 'when there is payload' do
- let(:check_storage_size_response) do
- ServiceResponse.success(
- payload: {
- alert_level: :info,
- usage_message: "Usage",
- explanation_message: "Explanation"
- }
- )
- end
-
- it 'adds an alert' do
- response = subject
-
- expect(response.size).to eq(2)
- expect(response).to include(build_alert_message("##### INFO #####\nUsage\nExplanation"))
- end
- end
- end
end
context 'with PersonalSnippet' do
diff --git a/spec/services/preview_markdown_service_spec.rb b/spec/services/preview_markdown_service_spec.rb
index d25e9958831..2509d1300b3 100644
--- a/spec/services/preview_markdown_service_spec.rb
+++ b/spec/services/preview_markdown_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PreviewMarkdownService do
+RSpec.describe PreviewMarkdownService do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
diff --git a/spec/services/projects/after_import_service_spec.rb b/spec/services/projects/after_import_service_spec.rb
index 82f654cea10..a109348ea19 100644
--- a/spec/services/projects/after_import_service_spec.rb
+++ b/spec/services/projects/after_import_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::AfterImportService do
+RSpec.describe Projects::AfterImportService do
include GitHelpers
subject { described_class.new(project) }
@@ -72,6 +72,26 @@ describe Projects::AfterImportService do
end
end
+ context 'when housekeeping service lease is taken' do
+ let(:exception) { Projects::HousekeepingService::LeaseTaken.new }
+
+ it 'logs the error message' do
+ allow_next_instance_of(Projects::HousekeepingService) do |instance|
+ expect(instance).to receive(:execute).and_raise(exception)
+ end
+
+ expect(Gitlab::Import::Logger).to receive(:info).with(
+ {
+ message: 'Project housekeeping failed',
+ project_full_path: project.full_path,
+ project_id: project.id,
+ 'error.message' => exception.to_s
+ }).and_call_original
+
+ subject.execute
+ end
+ end
+
context 'when after import action throw retriable exception one time' do
let(:exception) { GRPC::DeadlineExceeded.new }
diff --git a/spec/services/projects/after_rename_service_spec.rb b/spec/services/projects/after_rename_service_spec.rb
index b81dd3d7e3f..52136b37c66 100644
--- a/spec/services/projects/after_rename_service_spec.rb
+++ b/spec/services/projects/after_rename_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::AfterRenameService do
+RSpec.describe Projects::AfterRenameService do
let(:rugged_config) { rugged_repo(project.repository).config }
let(:legacy_storage) { Storage::LegacyProject.new(project) }
let(:hashed_storage) { Storage::Hashed.new(project) }
diff --git a/spec/services/projects/alerting/notify_service_spec.rb b/spec/services/projects/alerting/notify_service_spec.rb
index 2f8c2049f85..123b0bad2a8 100644
--- a/spec/services/projects/alerting/notify_service_spec.rb
+++ b/spec/services/projects/alerting/notify_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::Alerting::NotifyService do
+RSpec.describe Projects::Alerting::NotifyService do
let_it_be(:project, reload: true) { create(:project) }
before do
@@ -21,7 +21,7 @@ describe Projects::Alerting::NotifyService do
it 'processes issues' do
expect(IncidentManagement::ProcessAlertWorker)
.to receive(:perform_async)
- .with(project.id, kind_of(Hash), kind_of(Integer))
+ .with(nil, nil, kind_of(Integer))
.once
Sidekiq::Testing.inline! do
@@ -64,12 +64,6 @@ describe Projects::Alerting::NotifyService do
end
end
- shared_examples 'NotifyService does not create alert' do
- it 'does not create alert' do
- expect { subject }.not_to change(AlertManagement::Alert, :count)
- end
- end
-
describe '#execute' do
let(:token) { 'invalid-token' }
let(:starts_at) { Time.current.change(usec: 0) }
@@ -107,60 +101,64 @@ describe Projects::Alerting::NotifyService do
end
context 'with valid payload' do
+ shared_examples 'assigns the alert properties' do
+ it 'ensure that created alert has all data properly assigned' do
+ subject
+
+ expect(last_alert_attributes).to match(
+ project_id: project.id,
+ title: payload_raw.fetch(:title),
+ started_at: Time.zone.parse(payload_raw.fetch(:start_time)),
+ severity: payload_raw.fetch(:severity),
+ status: AlertManagement::Alert::STATUSES[:triggered],
+ events: 1,
+ hosts: payload_raw.fetch(:hosts),
+ payload: payload_raw.with_indifferent_access,
+ issue_id: nil,
+ description: payload_raw.fetch(:description),
+ monitoring_tool: payload_raw.fetch(:monitoring_tool),
+ service: payload_raw.fetch(:service),
+ fingerprint: Digest::SHA1.hexdigest(fingerprint),
+ ended_at: nil,
+ prometheus_alert_id: nil,
+ environment_id: nil
+ )
+ end
+ end
+
let(:last_alert_attributes) do
AlertManagement::Alert.last.attributes
.except('id', 'iid', 'created_at', 'updated_at')
.with_indifferent_access
end
- it 'creates AlertManagement::Alert' do
- expect { subject }.to change(AlertManagement::Alert, :count).by(1)
- end
-
- it 'created alert has all data properly assigned' do
- subject
-
- expect(last_alert_attributes).to match(
- project_id: project.id,
- title: payload_raw.fetch(:title),
- started_at: Time.zone.parse(payload_raw.fetch(:start_time)),
- severity: payload_raw.fetch(:severity),
- status: AlertManagement::Alert::STATUSES[:triggered],
- events: 1,
- hosts: payload_raw.fetch(:hosts),
- payload: payload_raw.with_indifferent_access,
- issue_id: nil,
- description: payload_raw.fetch(:description),
- monitoring_tool: payload_raw.fetch(:monitoring_tool),
- service: payload_raw.fetch(:service),
- fingerprint: Digest::SHA1.hexdigest(fingerprint),
- ended_at: nil
- )
- end
-
- it 'executes the alert service hooks' do
- slack_service = create(:service, type: 'SlackService', project: project, alert_events: true, active: true)
- subject
-
- expect(ProjectServiceWorker).to have_received(:perform_async).with(slack_service.id, an_instance_of(Hash))
- end
+ it_behaves_like 'creates an alert management alert'
+ it_behaves_like 'assigns the alert properties'
context 'existing alert with same fingerprint' do
let(:fingerprint_sha) { Digest::SHA1.hexdigest(fingerprint) }
- let!(:existing_alert) { create(:alert_management_alert, project: project, fingerprint: fingerprint_sha) }
+ let!(:alert) { create(:alert_management_alert, project: project, fingerprint: fingerprint_sha) }
+
+ it_behaves_like 'adds an alert management alert event'
+
+ context 'existing alert is resolved' do
+ let!(:alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: fingerprint_sha) }
- it 'does not create AlertManagement::Alert' do
- expect { subject }.not_to change(AlertManagement::Alert, :count)
+ it_behaves_like 'creates an alert management alert'
+ it_behaves_like 'assigns the alert properties'
end
- it 'increments the existing alert count' do
- expect { subject }.to change { existing_alert.reload.events }.from(1).to(2)
+ context 'existing alert is ignored' do
+ let!(:alert) { create(:alert_management_alert, :ignored, project: project, fingerprint: fingerprint_sha) }
+
+ it_behaves_like 'adds an alert management alert event'
end
- it 'does not executes the alert service hooks' do
- subject
+ context 'two existing alerts, one resolved one open' do
+ let!(:resolved_existing_alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: fingerprint_sha) }
+ let!(:alert) { create(:alert_management_alert, project: project, fingerprint: fingerprint_sha) }
- expect(ProjectServiceWorker).not_to have_received(:perform_async)
+ it_behaves_like 'adds an alert management alert event'
end
end
@@ -172,9 +170,7 @@ describe Projects::Alerting::NotifyService do
}
end
- it 'creates AlertManagement::Alert' do
- expect { subject }.to change(AlertManagement::Alert, :count).by(1)
- end
+ it_behaves_like 'creates an alert management alert'
it 'created alert has all data properly assigned' do
subject
@@ -193,7 +189,9 @@ describe Projects::Alerting::NotifyService do
monitoring_tool: nil,
service: nil,
fingerprint: nil,
- ended_at: nil
+ ended_at: nil,
+ prometheus_alert_id: nil,
+ environment_id: nil
)
end
end
@@ -214,19 +212,19 @@ describe Projects::Alerting::NotifyService do
end
it_behaves_like 'does not process incident issues due to error', http_status: :bad_request
- it_behaves_like 'NotifyService does not create alert'
+ it_behaves_like 'does not an create alert management alert'
end
context 'when alert already exists' do
let(:fingerprint_sha) { Digest::SHA1.hexdigest(fingerprint) }
- let!(:existing_alert) { create(:alert_management_alert, project: project, fingerprint: fingerprint_sha) }
+ let!(:alert) { create(:alert_management_alert, project: project, fingerprint: fingerprint_sha) }
context 'when existing alert does not have an associated issue' do
it_behaves_like 'processes incident issues'
end
context 'when existing alert has an associated issue' do
- let!(:existing_alert) { create(:alert_management_alert, :with_issue, project: project, fingerprint: fingerprint_sha) }
+ let!(:alert) { create(:alert_management_alert, :with_issue, project: project, fingerprint: fingerprint_sha) }
it_behaves_like 'does not process incident issues'
end
@@ -242,14 +240,14 @@ describe Projects::Alerting::NotifyService do
context 'with invalid token' do
it_behaves_like 'does not process incident issues due to error', http_status: :unauthorized
- it_behaves_like 'NotifyService does not create alert'
+ it_behaves_like 'does not an create alert management alert'
end
context 'with deactivated Alerts Service' do
let!(:alerts_service) { create(:alerts_service, :inactive, project: project) }
it_behaves_like 'does not process incident issues due to error', http_status: :forbidden
- it_behaves_like 'NotifyService does not create alert'
+ it_behaves_like 'does not an create alert management alert'
end
end
end
diff --git a/spec/services/projects/auto_devops/disable_service_spec.rb b/spec/services/projects/auto_devops/disable_service_spec.rb
index fb1ab3f9949..1f161990fb2 100644
--- a/spec/services/projects/auto_devops/disable_service_spec.rb
+++ b/spec/services/projects/auto_devops/disable_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Projects::AutoDevops::DisableService, '#execute' do
+RSpec.describe Projects::AutoDevops::DisableService, '#execute' do
let(:project) { create(:project, :repository, :auto_devops) }
let(:auto_devops) { project.auto_devops }
diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb
index b625653bc77..336aa37096a 100644
--- a/spec/services/projects/autocomplete_service_spec.rb
+++ b/spec/services/projects/autocomplete_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::AutocompleteService do
+RSpec.describe Projects::AutocompleteService do
describe '#issues' do
describe 'confidential issues' do
let(:author) { create(:user) }
diff --git a/spec/services/projects/batch_open_issues_count_service_spec.rb b/spec/services/projects/batch_open_issues_count_service_spec.rb
index 8cb0ce03fba..82d50604309 100644
--- a/spec/services/projects/batch_open_issues_count_service_spec.rb
+++ b/spec/services/projects/batch_open_issues_count_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::BatchOpenIssuesCountService do
+RSpec.describe Projects::BatchOpenIssuesCountService do
let!(:project_1) { create(:project) }
let!(:project_2) { create(:project) }
diff --git a/spec/services/projects/cleanup_service_spec.rb b/spec/services/projects/cleanup_service_spec.rb
index 5c246854eb7..528f31456a9 100644
--- a/spec/services/projects/cleanup_service_spec.rb
+++ b/spec/services/projects/cleanup_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::CleanupService do
+RSpec.describe Projects::CleanupService do
let(:project) { create(:project, :repository, bfg_object_map: fixture_file_upload('spec/fixtures/bfg_object_map.txt')) }
let(:object_map) { project.bfg_object_map }
diff --git a/spec/services/projects/container_repository/cleanup_tags_service_spec.rb b/spec/services/projects/container_repository/cleanup_tags_service_spec.rb
index 11ea7d51673..2c708e75a25 100644
--- a/spec/services/projects/container_repository/cleanup_tags_service_spec.rb
+++ b/spec/services/projects/container_repository/cleanup_tags_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ContainerRepository::CleanupTagsService do
+RSpec.describe Projects::ContainerRepository::CleanupTagsService do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :private) }
let_it_be(:repository) { create(:container_repository, :root, project: project) }
diff --git a/spec/services/projects/container_repository/delete_tags_service_spec.rb b/spec/services/projects/container_repository/delete_tags_service_spec.rb
index e17e4b6f7c9..3d065deefdf 100644
--- a/spec/services/projects/container_repository/delete_tags_service_spec.rb
+++ b/spec/services/projects/container_repository/delete_tags_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ContainerRepository::DeleteTagsService do
+RSpec.describe Projects::ContainerRepository::DeleteTagsService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :private) }
let_it_be(:repository) { create(:container_repository, :root, project: project) }
@@ -20,6 +20,31 @@ describe Projects::ContainerRepository::DeleteTagsService do
tags: %w(latest A Ba Bb C D E))
end
+ RSpec.shared_examples 'logging a success response' do
+ it 'logs an info message' do
+ expect(service).to receive(:log_info).with(
+ service_class: 'Projects::ContainerRepository::DeleteTagsService',
+ message: 'deleted tags',
+ container_repository_id: repository.id,
+ deleted_tags_count: tags.size
+ )
+
+ subject
+ end
+ end
+
+ RSpec.shared_examples 'logging an error response' do |message: 'could not delete tags'|
+ it 'logs an error message' do
+ expect(service).to receive(:log_error).with(
+ service_class: 'Projects::ContainerRepository::DeleteTagsService',
+ message: message,
+ container_repository_id: repository.id
+ )
+
+ subject
+ end
+ end
+
describe '#execute' do
let(:tags) { %w[A] }
@@ -47,11 +72,8 @@ describe Projects::ContainerRepository::DeleteTagsService do
let_it_be(:tags) { %w[A Ba] }
it 'deletes the tags by name' do
- stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/tags/reference/A")
- .to_return(status: 200, body: "")
-
- stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/tags/reference/Ba")
- .to_return(status: 200, body: "")
+ stub_delete_reference_request('A')
+ stub_delete_reference_request('Ba')
expect_delete_tag_by_name('A')
expect_delete_tag_by_name('Ba')
@@ -60,26 +82,29 @@ describe Projects::ContainerRepository::DeleteTagsService do
end
it 'succeeds when tag delete returns 404' do
- stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/tags/reference/A")
- .to_return(status: 200, body: "")
-
- stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/tags/reference/Ba")
- .to_return(status: 404, body: "")
+ stub_delete_reference_request('A')
+ stub_delete_reference_request('Ba', 404)
is_expected.to include(status: :success)
end
+ it_behaves_like 'logging a success response' do
+ before do
+ stub_delete_reference_request('A')
+ stub_delete_reference_request('Ba')
+ end
+ end
+
context 'with failures' do
context 'when the delete request fails' do
before do
- stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/tags/reference/A")
- .to_return(status: 500, body: "")
-
- stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/tags/reference/Ba")
- .to_return(status: 500, body: "")
+ stub_delete_reference_request('A', 500)
+ stub_delete_reference_request('Ba', 500)
end
it { is_expected.to include(status: :error) }
+
+ it_behaves_like 'logging an error response'
end
end
end
@@ -104,19 +129,35 @@ describe Projects::ContainerRepository::DeleteTagsService do
end
end
end
+
context 'and the feature is disabled' do
+ let_it_be(:tags) { %w[A Ba] }
+
before do
stub_feature_flags(container_registry_fast_tag_delete: false)
+ stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
+ stub_put_manifest_request('A')
+ stub_put_manifest_request('Ba')
end
it 'fallbacks to slow delete' do
expect(service).not_to receive(:fast_delete)
- expect(service).to receive(:slow_delete).with(repository, tags)
+ expect(service).to receive(:slow_delete).with(repository, tags).and_call_original
+
+ expect_delete_tag_by_digest('sha256:dummy')
subject
end
+
+ it_behaves_like 'logging a success response' do
+ before do
+ allow(service).to receive(:slow_delete).and_call_original
+ expect_delete_tag_by_digest('sha256:dummy')
+ end
+ end
end
end
+
context 'when the registry does not support fast delete' do
let_it_be(:project) { create(:project, :private) }
let_it_be(:repository) { create(:container_repository, :root, project: project) }
@@ -155,11 +196,8 @@ describe Projects::ContainerRepository::DeleteTagsService do
it 'deletes the tags using a dummy image' do
stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
- stub_request(:put, "http://registry.gitlab/v2/#{repository.path}/manifests/A")
- .to_return(status: 200, body: "", headers: { 'docker-content-digest' => 'sha256:dummy' })
-
- stub_request(:put, "http://registry.gitlab/v2/#{repository.path}/manifests/Ba")
- .to_return(status: 200, body: "", headers: { 'docker-content-digest' => 'sha256:dummy' })
+ stub_put_manifest_request('A')
+ stub_put_manifest_request('Ba')
expect_delete_tag_by_digest('sha256:dummy')
@@ -169,11 +207,8 @@ describe Projects::ContainerRepository::DeleteTagsService do
it 'succeeds when tag delete returns 404' do
stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
- stub_request(:put, "http://registry.gitlab/v2/#{repository.path}/manifests/A")
- .to_return(status: 200, body: "", headers: { 'docker-content-digest' => 'sha256:dummy' })
-
- stub_request(:put, "http://registry.gitlab/v2/#{repository.path}/manifests/Ba")
- .to_return(status: 200, body: "", headers: { 'docker-content-digest' => 'sha256:dummy' })
+ stub_put_manifest_request('A')
+ stub_put_manifest_request('Ba')
stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/manifests/sha256:dummy")
.to_return(status: 404, body: "", headers: {})
@@ -181,6 +216,15 @@ describe Projects::ContainerRepository::DeleteTagsService do
is_expected.to include(status: :success)
end
+ it_behaves_like 'logging a success response' do
+ before do
+ stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
+ stub_put_manifest_request('A')
+ stub_put_manifest_request('Ba')
+ expect_delete_tag_by_digest('sha256:dummy')
+ end
+ end
+
context 'with failures' do
context 'when the dummy manifest generation fails' do
before do
@@ -188,23 +232,23 @@ describe Projects::ContainerRepository::DeleteTagsService do
end
it { is_expected.to include(status: :error) }
+
+ it_behaves_like 'logging an error response', message: 'could not generate manifest'
end
context 'when updating the tags fails' do
before do
stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
- stub_request(:put, "http://registry.gitlab/v2/#{repository.path}/manifests/A")
- .to_return(status: 500, body: "", headers: { 'docker-content-digest' => 'sha256:dummy' })
-
- stub_request(:put, "http://registry.gitlab/v2/#{repository.path}/manifests/Ba")
- .to_return(status: 500, body: "", headers: { 'docker-content-digest' => 'sha256:dummy' })
+ stub_put_manifest_request('A', 500)
+ stub_put_manifest_request('Ba', 500)
stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/manifests/sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3")
.to_return(status: 200, body: "", headers: {})
end
it { is_expected.to include(status: :error) }
+ it_behaves_like 'logging an error response'
end
end
end
@@ -214,6 +258,16 @@ describe Projects::ContainerRepository::DeleteTagsService do
private
+ def stub_delete_reference_request(tag, status = 200)
+ stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/tags/reference/#{tag}")
+ .to_return(status: status, body: '')
+ end
+
+ def stub_put_manifest_request(tag, status = 200, headers = { 'docker-content-digest' => 'sha256:dummy' })
+ stub_request(:put, "http://registry.gitlab/v2/#{repository.path}/manifests/#{tag}")
+ .to_return(status: status, body: '', headers: headers)
+ end
+
def stub_tag_digest(tag, digest)
stub_request(:head, "http://registry.gitlab/v2/#{repository.path}/manifests/#{tag}")
.to_return(status: 200, body: "", headers: { 'docker-content-digest' => digest })
diff --git a/spec/services/projects/container_repository/destroy_service_spec.rb b/spec/services/projects/container_repository/destroy_service_spec.rb
index 753b7540d7f..20e75d94e05 100644
--- a/spec/services/projects/container_repository/destroy_service_spec.rb
+++ b/spec/services/projects/container_repository/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ContainerRepository::DestroyService do
+RSpec.describe Projects::ContainerRepository::DestroyService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :private) }
diff --git a/spec/services/projects/count_service_spec.rb b/spec/services/projects/count_service_spec.rb
index e345b508f53..11b2b57a277 100644
--- a/spec/services/projects/count_service_spec.rb
+++ b/spec/services/projects/count_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::CountService do
+RSpec.describe Projects::CountService do
let(:project) { build(:project, id: 1) }
let(:service) { described_class.new(project) }
diff --git a/spec/services/projects/create_from_template_service_spec.rb b/spec/services/projects/create_from_template_service_spec.rb
index 0b4772e8f02..7e23daabcd3 100644
--- a/spec/services/projects/create_from_template_service_spec.rb
+++ b/spec/services/projects/create_from_template_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::CreateFromTemplateService do
+RSpec.describe Projects::CreateFromTemplateService do
let(:user) { create(:user) }
let(:template_name) { 'rails' }
let(:project_params) do
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index e70ee05ed31..9eb7cacbbcb 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::CreateService, '#execute' do
+RSpec.describe Projects::CreateService, '#execute' do
include ExternalAuthorizationServiceHelpers
include GitHelpers
@@ -240,13 +240,21 @@ describe Projects::CreateService, '#execute' do
end
context 'import data' do
- it 'stores import data and URL' do
- import_data = { data: { 'test' => 'some data' } }
- project = create_project(user, { name: 'test', import_url: 'http://import-url', import_data: import_data })
+ let(:import_data) { { data: { 'test' => 'some data' } } }
+ let(:imported_project) { create_project(user, { name: 'test', import_url: 'http://import-url', import_data: import_data }) }
+
+ it 'does not write repository config' do
+ expect_next_instance_of(Project) do |project|
+ expect(project).not_to receive(:write_repository_config)
+ end
- expect(project.import_data).to be_persisted
- expect(project.import_data.data).to eq(import_data[:data])
- expect(project.import_url).to eq('http://import-url')
+ imported_project
+ end
+
+ it 'stores import data and URL' do
+ expect(imported_project.import_data).to be_persisted
+ expect(imported_project.import_data.data).to eq(import_data[:data])
+ expect(imported_project.import_url).to eq('http://import-url')
end
end
@@ -438,14 +446,35 @@ describe Projects::CreateService, '#execute' do
end
context 'when readme initialization is requested' do
- it 'creates README.md' do
+ let(:project) { create_project(user, opts) }
+
+ before do
opts[:initialize_with_readme] = '1'
+ end
- project = create_project(user, opts)
+ shared_examples 'creates README.md' do
+ it { expect(project.repository.commit_count).to be(1) }
+ it { expect(project.repository.readme.name).to eql('README.md') }
+ it { expect(project.repository.readme.data).to include('# GitLab') }
+ end
- expect(project.repository.commit_count).to be(1)
- expect(project.repository.readme.name).to eql('README.md')
- expect(project.repository.readme.data).to include('# GitLab')
+ it_behaves_like 'creates README.md'
+
+ context 'and a default_branch_name is specified' do
+ before do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:default_branch_name)
+ .and_return('example_branch')
+ end
+
+ it_behaves_like 'creates README.md'
+
+ it 'creates README.md within the specified branch rather than master' do
+ branches = project.repository.branches
+
+ expect(branches.size).to eq(1)
+ expect(branches.collect(&:name)).to contain_exactly('example_branch')
+ end
end
end
@@ -647,10 +676,6 @@ describe Projects::CreateService, '#execute' do
end
it 'updates authorization for current_user' do
- expect(Users::RefreshAuthorizedProjectsService).to(
- receive(:new).with(user).and_call_original
- )
-
project = create_project(user, opts)
expect(
@@ -682,10 +707,6 @@ describe Projects::CreateService, '#execute' do
end
it 'updates authorization for current_user' do
- expect(Users::RefreshAuthorizedProjectsService).to(
- receive(:new).with(user).and_call_original
- )
-
project = create_project(user, opts)
expect(
diff --git a/spec/services/projects/deploy_tokens/create_service_spec.rb b/spec/services/projects/deploy_tokens/create_service_spec.rb
index 5c3ada8af4e..831dbc06588 100644
--- a/spec/services/projects/deploy_tokens/create_service_spec.rb
+++ b/spec/services/projects/deploy_tokens/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::DeployTokens::CreateService do
+RSpec.describe Projects::DeployTokens::CreateService do
it_behaves_like 'a deploy token creation service' do
let(:entity) { create(:project) }
let(:deploy_token_class) { ProjectDeployToken }
diff --git a/spec/services/projects/deploy_tokens/destroy_service_spec.rb b/spec/services/projects/deploy_tokens/destroy_service_spec.rb
index 24407f46615..edb2345aa6c 100644
--- a/spec/services/projects/deploy_tokens/destroy_service_spec.rb
+++ b/spec/services/projects/deploy_tokens/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::DeployTokens::DestroyService do
+RSpec.describe Projects::DeployTokens::DestroyService do
it_behaves_like 'a deploy token deletion service' do
let_it_be(:entity) { create(:project) }
let_it_be(:deploy_token_class) { ProjectDeployToken }
diff --git a/spec/services/projects/destroy_rollback_service_spec.rb b/spec/services/projects/destroy_rollback_service_spec.rb
index 8facf17dc45..f63939337b8 100644
--- a/spec/services/projects/destroy_rollback_service_spec.rb
+++ b/spec/services/projects/destroy_rollback_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::DestroyRollbackService do
+RSpec.describe Projects::DestroyRollbackService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, namespace: user.namespace) }
let(:repository) { project.repository }
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 58c40d04fe9..56b19c33ece 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::DestroyService do
+RSpec.describe Projects::DestroyService do
include ProjectForksHelper
let_it_be(:user) { create(:user) }
diff --git a/spec/services/projects/detect_repository_languages_service_spec.rb b/spec/services/projects/detect_repository_languages_service_spec.rb
index 76600b0e77c..cf4c7a5024d 100644
--- a/spec/services/projects/detect_repository_languages_service_spec.rb
+++ b/spec/services/projects/detect_repository_languages_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::DetectRepositoryLanguagesService, :clean_gitlab_redis_shared_state do
+RSpec.describe Projects::DetectRepositoryLanguagesService, :clean_gitlab_redis_shared_state do
let_it_be(:project, reload: true) { create(:project, :repository) }
subject { described_class.new(project) }
diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb
index 06efc2ff825..0f743eaa7f5 100644
--- a/spec/services/projects/download_service_spec.rb
+++ b/spec/services/projects/download_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::DownloadService do
+RSpec.describe Projects::DownloadService do
describe 'File service' do
before do
@user = create(:user)
diff --git a/spec/services/projects/enable_deploy_key_service_spec.rb b/spec/services/projects/enable_deploy_key_service_spec.rb
index 64de373d7f6..f297ec374cf 100644
--- a/spec/services/projects/enable_deploy_key_service_spec.rb
+++ b/spec/services/projects/enable_deploy_key_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::EnableDeployKeyService do
+RSpec.describe Projects::EnableDeployKeyService do
let(:deploy_key) { create(:deploy_key, public: true) }
let(:project) { create(:project) }
let(:user) { project.creator}
diff --git a/spec/services/projects/fetch_statistics_increment_service_spec.rb b/spec/services/projects/fetch_statistics_increment_service_spec.rb
index fcfb138aad6..16121a42c39 100644
--- a/spec/services/projects/fetch_statistics_increment_service_spec.rb
+++ b/spec/services/projects/fetch_statistics_increment_service_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
module Projects
- describe FetchStatisticsIncrementService do
+ RSpec.describe FetchStatisticsIncrementService do
let(:project) { create(:project) }
describe '#execute' do
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index 112a41c773b..c49aa42b147 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ForkService do
+RSpec.describe Projects::ForkService do
include ProjectForksHelper
shared_examples 'forks count cache refresh' do
@@ -10,6 +10,7 @@ describe Projects::ForkService do
expect(from_project.forks_count).to be_zero
fork_project(from_project, to_user)
+ BatchLoader::Executor.clear_current
expect(from_project.forks_count).to eq(1)
end
@@ -327,7 +328,7 @@ describe Projects::ForkService do
destination_storage_name: 'test_second_storage'
)
Projects::UpdateRepositoryStorageService.new(storage_move).execute
- fork_after_move = fork_project(project)
+ fork_after_move = fork_project(project.reload)
pool_repository_before_move = PoolRepository.joins(:shard)
.find_by(source_project: project, shards: { name: 'default' })
pool_repository_after_move = PoolRepository.joins(:shard)
@@ -405,6 +406,7 @@ describe Projects::ForkService do
expect(fork_from_project.forks_count).to be_zero
subject.execute(fork_to_project)
+ BatchLoader::Executor.clear_current
expect(fork_from_project.forks_count).to eq(1)
end
diff --git a/spec/services/projects/forks_count_service_spec.rb b/spec/services/projects/forks_count_service_spec.rb
index 21a75eafc7a..31662f78973 100644
--- a/spec/services/projects/forks_count_service_spec.rb
+++ b/spec/services/projects/forks_count_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ForksCountService, :use_clean_rails_memory_store_caching do
+RSpec.describe Projects::ForksCountService, :use_clean_rails_memory_store_caching do
let(:project) { build(:project) }
subject { described_class.new(project) }
diff --git a/spec/services/projects/git_deduplication_service_spec.rb b/spec/services/projects/git_deduplication_service_spec.rb
index 9e6279da7de..b98db5bc41b 100644
--- a/spec/services/projects/git_deduplication_service_spec.rb
+++ b/spec/services/projects/git_deduplication_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::GitDeduplicationService do
+RSpec.describe Projects::GitDeduplicationService do
include ExclusiveLeaseHelpers
let(:pool) { create(:pool_repository, :ready) }
@@ -139,7 +139,7 @@ describe Projects::GitDeduplicationService do
end
it 'fails when a lease is already out' do
- expect(service).to receive(:log_error).with('Cannot obtain an exclusive lease. There must be another instance already in execution.')
+ expect(service).to receive(:log_error).with("Cannot obtain an exclusive lease for #{service.class.name}. There must be another instance already in execution.")
service.execute
end
diff --git a/spec/services/projects/gitlab_projects_import_service_spec.rb b/spec/services/projects/gitlab_projects_import_service_spec.rb
index 1662d4577aa..09d093a9916 100644
--- a/spec/services/projects/gitlab_projects_import_service_spec.rb
+++ b/spec/services/projects/gitlab_projects_import_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::GitlabProjectsImportService do
+RSpec.describe Projects::GitlabProjectsImportService do
let_it_be(:namespace) { create(:namespace) }
let(:path) { 'test-path' }
let(:file) { fixture_file_upload('spec/fixtures/project_export.tar.gz') }
diff --git a/spec/services/projects/group_links/create_service_spec.rb b/spec/services/projects/group_links/create_service_spec.rb
index 22f7c8bdcb4..6468e3007c2 100644
--- a/spec/services/projects/group_links/create_service_spec.rb
+++ b/spec/services/projects/group_links/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::GroupLinks::CreateService, '#execute' do
+RSpec.describe Projects::GroupLinks::CreateService, '#execute' do
let_it_be(:user) { create :user }
let_it_be(:group) { create :group }
let_it_be(:project) { create :project }
@@ -23,7 +23,7 @@ describe Projects::GroupLinks::CreateService, '#execute' do
expect { subject.execute(group) }.to change { project.project_group_links.count }.from(0).to(1)
end
- it 'updates authorization' do
+ it 'updates authorization', :sidekiq_inline do
expect { subject.execute(group) }.to(
change { Ability.allowed?(user, :read_project, project) }
.from(false).to(true))
@@ -36,4 +36,50 @@ describe Projects::GroupLinks::CreateService, '#execute' do
it 'returns error if user is not allowed to share with a group' do
expect { subject.execute(create(:group)) }.not_to change { project.project_group_links.count }
end
+
+ context 'with specialized_project_authorization_workers' do
+ let_it_be(:other_user) { create(:user) }
+
+ before do
+ group.add_developer(other_user)
+ end
+
+ it 'schedules authorization update for users with access to group' do
+ expect(AuthorizedProjectsWorker).not_to(
+ receive(:bulk_perform_async)
+ )
+ expect(AuthorizedProjectUpdate::ProjectGroupLinkCreateWorker).to(
+ receive(:perform_async).and_call_original
+ )
+ expect(AuthorizedProjectUpdate::UserRefreshWithLowUrgencyWorker).to(
+ receive(:bulk_perform_in)
+ .with(1.hour,
+ array_including([user.id], [other_user.id]),
+ batch_delay: 30.seconds, batch_size: 100)
+ .and_call_original
+ )
+
+ subject.execute(group)
+ end
+
+ context 'when feature is disabled' do
+ before do
+ stub_feature_flags(specialized_project_authorization_project_share_worker: false)
+ end
+
+ it 'uses AuthorizedProjectsWorker' do
+ expect(AuthorizedProjectsWorker).to(
+ receive(:bulk_perform_async).with(array_including([user.id], [other_user.id])).and_call_original
+ )
+ expect(AuthorizedProjectUpdate::ProjectCreateWorker).not_to(
+ receive(:perform_async)
+ )
+ expect(AuthorizedProjectUpdate::UserRefreshWithLowUrgencyWorker).not_to(
+ receive(:bulk_perform_in)
+ )
+
+ subject.execute(group)
+ end
+ end
+ end
end
diff --git a/spec/services/projects/group_links/destroy_service_spec.rb b/spec/services/projects/group_links/destroy_service_spec.rb
index 0a8c9580e70..459b79b2d7d 100644
--- a/spec/services/projects/group_links/destroy_service_spec.rb
+++ b/spec/services/projects/group_links/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::GroupLinks::DestroyService, '#execute' do
+RSpec.describe Projects::GroupLinks::DestroyService, '#execute' do
let_it_be(:user) { create :user }
let_it_be(:project) { create(:project, :private) }
let_it_be(:group) { create(:group) }
diff --git a/spec/services/projects/group_links/update_service_spec.rb b/spec/services/projects/group_links/update_service_spec.rb
index 5be2ae1e0f7..053c5eb611e 100644
--- a/spec/services/projects/group_links/update_service_spec.rb
+++ b/spec/services/projects/group_links/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::GroupLinks::UpdateService, '#execute' do
+RSpec.describe Projects::GroupLinks::UpdateService, '#execute' do
let_it_be(:user) { create :user }
let_it_be(:group) { create :group }
let_it_be(:project) { create :project }
diff --git a/spec/services/projects/hashed_storage/base_attachment_service_spec.rb b/spec/services/projects/hashed_storage/base_attachment_service_spec.rb
index 070dd5fc1b8..5e1b6f2e404 100644
--- a/spec/services/projects/hashed_storage/base_attachment_service_spec.rb
+++ b/spec/services/projects/hashed_storage/base_attachment_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::HashedStorage::BaseAttachmentService do
+RSpec.describe Projects::HashedStorage::BaseAttachmentService do
let(:project) { create(:project, :repository, storage_version: 0, skip_disk_validation: true) }
subject(:service) { described_class.new(project: project, old_disk_path: project.full_path, logger: nil) }
diff --git a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb
index 7c7e188a12d..c8f24c6ce00 100644
--- a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb
+++ b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::HashedStorage::MigrateAttachmentsService do
+RSpec.describe Projects::HashedStorage::MigrateAttachmentsService do
subject(:service) { described_class.new(project: project, old_disk_path: project.full_path, logger: nil) }
let(:project) { create(:project, :repository, storage_version: 1, skip_disk_validation: true) }
diff --git a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
index f1eaf8324e0..e03e75653ff 100644
--- a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
+++ b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::HashedStorage::MigrateRepositoryService do
+RSpec.describe Projects::HashedStorage::MigrateRepositoryService do
include GitHelpers
let(:gitlab_shell) { Gitlab::Shell.new }
diff --git a/spec/services/projects/hashed_storage/migration_service_spec.rb b/spec/services/projects/hashed_storage/migration_service_spec.rb
index 0a7975305dc..ef96c17dd85 100644
--- a/spec/services/projects/hashed_storage/migration_service_spec.rb
+++ b/spec/services/projects/hashed_storage/migration_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::HashedStorage::MigrationService do
+RSpec.describe Projects::HashedStorage::MigrationService do
let(:project) { create(:project, :empty_repo, :wiki_repo, :legacy_storage) }
let(:logger) { double }
let!(:project_attachment) { build(:file_uploader, project: project) }
diff --git a/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb b/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb
index 54695e6e48f..d4cb46c82ad 100644
--- a/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb
+++ b/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::HashedStorage::RollbackAttachmentsService do
+RSpec.describe Projects::HashedStorage::RollbackAttachmentsService do
subject(:service) { described_class.new(project: project, old_disk_path: project.disk_path, logger: nil) }
let(:project) { create(:project, :repository, skip_disk_validation: true) }
diff --git a/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb
index 1c0f446d9cf..f2b1ce30a54 100644
--- a/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb
+++ b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::HashedStorage::RollbackRepositoryService, :clean_gitlab_redis_shared_state do
+RSpec.describe Projects::HashedStorage::RollbackRepositoryService, :clean_gitlab_redis_shared_state do
include GitHelpers
let(:gitlab_shell) { Gitlab::Shell.new }
diff --git a/spec/services/projects/hashed_storage/rollback_service_spec.rb b/spec/services/projects/hashed_storage/rollback_service_spec.rb
index e6b7daba99e..0bd63f2da2a 100644
--- a/spec/services/projects/hashed_storage/rollback_service_spec.rb
+++ b/spec/services/projects/hashed_storage/rollback_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::HashedStorage::RollbackService do
+RSpec.describe Projects::HashedStorage::RollbackService do
let(:project) { create(:project, :empty_repo, :wiki_repo) }
let(:logger) { double }
let!(:project_attachment) { build(:file_uploader, project: project) }
diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb
index 98a27a71c26..18871f010f8 100644
--- a/spec/services/projects/housekeeping_service_spec.rb
+++ b/spec/services/projects/housekeeping_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::HousekeepingService do
+RSpec.describe Projects::HousekeepingService do
subject { described_class.new(project) }
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/services/projects/import_error_filter_spec.rb b/spec/services/projects/import_error_filter_spec.rb
index 312b658de89..fd31cd52cc4 100644
--- a/spec/services/projects/import_error_filter_spec.rb
+++ b/spec/services/projects/import_error_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ImportErrorFilter do
+RSpec.describe Projects::ImportErrorFilter do
it 'filters any full paths' do
message = 'Error importing into /my/folder Permission denied @ unlink_internal - /var/opt/gitlab/gitlab-rails/shared/a/b/c/uploads/file'
diff --git a/spec/services/projects/import_export/export_service_spec.rb b/spec/services/projects/import_export/export_service_spec.rb
index 19891341311..111c1264777 100644
--- a/spec/services/projects/import_export/export_service_spec.rb
+++ b/spec/services/projects/import_export/export_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ImportExport::ExportService do
+RSpec.describe Projects::ImportExport::ExportService do
describe '#execute' do
let!(:user) { create(:user) }
let(:project) { create(:project) }
diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb
index ca6750b373d..92e18b6cb46 100644
--- a/spec/services/projects/import_service_spec.rb
+++ b/spec/services/projects/import_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ImportService do
+RSpec.describe Projects::ImportService do
let!(:project) { create(:project) }
let(:user) { project.creator }
diff --git a/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb
index 99d35fdc7f7..66a450bd734 100644
--- a/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb
+++ b/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Projects::LfsPointers::LfsDownloadLinkListService do
+RSpec.describe Projects::LfsPointers::LfsDownloadLinkListService do
let(:import_url) { 'http://www.gitlab.com/demo/repo.git' }
let(:lfs_endpoint) { "#{import_url}/info/lfs/objects/batch" }
let!(:project) { create(:project, import_url: import_url) }
diff --git a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
index 496d1fe67f2..a606371099d 100644
--- a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
+++ b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Projects::LfsPointers::LfsDownloadService do
+RSpec.describe Projects::LfsPointers::LfsDownloadService do
include StubRequests
let(:project) { create(:project) }
diff --git a/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb
index 016028a96bf..b36b0b8d6b2 100644
--- a/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb
+++ b/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Projects::LfsPointers::LfsImportService do
+RSpec.describe Projects::LfsPointers::LfsImportService do
let(:project) { create(:project) }
let(:user) { project.creator }
let(:import_url) { 'http://www.gitlab.com/demo/repo.git' }
diff --git a/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb
index b64662f3782..d59f5dbae19 100644
--- a/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb
+++ b/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Projects::LfsPointers::LfsLinkService do
+RSpec.describe Projects::LfsPointers::LfsLinkService do
let!(:project) { create(:project, lfs_enabled: true) }
let!(:lfs_objects_project) { create_list(:lfs_objects_project, 2, project: project) }
let(:new_oids) { { 'oid1' => 123, 'oid2' => 125 } }
diff --git a/spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb
index e94d8a85987..0799a33f856 100644
--- a/spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb
+++ b/spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Projects::LfsPointers::LfsObjectDownloadListService do
+RSpec.describe Projects::LfsPointers::LfsObjectDownloadListService do
let(:import_url) { 'http://www.gitlab.com/demo/repo.git' }
let(:default_endpoint) { "#{import_url}/info/lfs/objects/batch"}
let(:group) { create(:group, lfs_enabled: true)}
diff --git a/spec/services/projects/move_access_service_spec.rb b/spec/services/projects/move_access_service_spec.rb
index efa34c84522..de3871414af 100644
--- a/spec/services/projects/move_access_service_spec.rb
+++ b/spec/services/projects/move_access_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::MoveAccessService do
+RSpec.describe Projects::MoveAccessService do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project_with_access) { create(:project, namespace: user.namespace) }
diff --git a/spec/services/projects/move_deploy_keys_projects_service_spec.rb b/spec/services/projects/move_deploy_keys_projects_service_spec.rb
index a5d28fb0fbf..e69b4dd4fc7 100644
--- a/spec/services/projects/move_deploy_keys_projects_service_spec.rb
+++ b/spec/services/projects/move_deploy_keys_projects_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::MoveDeployKeysProjectsService do
+RSpec.describe Projects::MoveDeployKeysProjectsService do
let!(:user) { create(:user) }
let!(:project_with_deploy_keys) { create(:project, namespace: user.namespace) }
let!(:target_project) { create(:project, namespace: user.namespace) }
diff --git a/spec/services/projects/move_forks_service_spec.rb b/spec/services/projects/move_forks_service_spec.rb
index 8f9f048d5ff..7d3637b7758 100644
--- a/spec/services/projects/move_forks_service_spec.rb
+++ b/spec/services/projects/move_forks_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::MoveForksService do
+RSpec.describe Projects::MoveForksService do
include ProjectForksHelper
let!(:user) { create(:user) }
diff --git a/spec/services/projects/move_lfs_objects_projects_service_spec.rb b/spec/services/projects/move_lfs_objects_projects_service_spec.rb
index 114509229c5..b73286fba9a 100644
--- a/spec/services/projects/move_lfs_objects_projects_service_spec.rb
+++ b/spec/services/projects/move_lfs_objects_projects_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::MoveLfsObjectsProjectsService do
+RSpec.describe Projects::MoveLfsObjectsProjectsService do
let!(:user) { create(:user) }
let!(:project_with_lfs_objects) { create(:project, namespace: user.namespace) }
let!(:target_project) { create(:project, namespace: user.namespace) }
diff --git a/spec/services/projects/move_notification_settings_service_spec.rb b/spec/services/projects/move_notification_settings_service_spec.rb
index 54d85404bf6..7c9f1dd30d2 100644
--- a/spec/services/projects/move_notification_settings_service_spec.rb
+++ b/spec/services/projects/move_notification_settings_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::MoveNotificationSettingsService do
+RSpec.describe Projects::MoveNotificationSettingsService do
let(:user) { create(:user) }
let(:project_with_notifications) { create(:project, namespace: user.namespace) }
let(:target_project) { create(:project, namespace: user.namespace) }
diff --git a/spec/services/projects/move_project_authorizations_service_spec.rb b/spec/services/projects/move_project_authorizations_service_spec.rb
index fe3ba31c881..a37b4d807a0 100644
--- a/spec/services/projects/move_project_authorizations_service_spec.rb
+++ b/spec/services/projects/move_project_authorizations_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::MoveProjectAuthorizationsService do
+RSpec.describe Projects::MoveProjectAuthorizationsService do
let!(:user) { create(:user) }
let(:project_with_users) { create(:project, namespace: user.namespace) }
let(:target_project) { create(:project, namespace: user.namespace) }
diff --git a/spec/services/projects/move_project_group_links_service_spec.rb b/spec/services/projects/move_project_group_links_service_spec.rb
index 6140d679929..196a8f2b339 100644
--- a/spec/services/projects/move_project_group_links_service_spec.rb
+++ b/spec/services/projects/move_project_group_links_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::MoveProjectGroupLinksService do
+RSpec.describe Projects::MoveProjectGroupLinksService do
let!(:user) { create(:user) }
let(:project_with_groups) { create(:project, namespace: user.namespace) }
let(:target_project) { create(:project, namespace: user.namespace) }
diff --git a/spec/services/projects/move_project_members_service_spec.rb b/spec/services/projects/move_project_members_service_spec.rb
index bdd5cd6a87a..f14f00e3866 100644
--- a/spec/services/projects/move_project_members_service_spec.rb
+++ b/spec/services/projects/move_project_members_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::MoveProjectMembersService do
+RSpec.describe Projects::MoveProjectMembersService do
let!(:user) { create(:user) }
let(:project_with_users) { create(:project, namespace: user.namespace) }
let(:target_project) { create(:project, namespace: user.namespace) }
diff --git a/spec/services/projects/move_users_star_projects_service_spec.rb b/spec/services/projects/move_users_star_projects_service_spec.rb
index cde188f9f5f..0f766ebd0ec 100644
--- a/spec/services/projects/move_users_star_projects_service_spec.rb
+++ b/spec/services/projects/move_users_star_projects_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::MoveUsersStarProjectsService do
+RSpec.describe Projects::MoveUsersStarProjectsService do
let!(:user) { create(:user) }
let!(:project_with_stars) { create(:project, namespace: user.namespace) }
let!(:target_project) { create(:project, namespace: user.namespace) }
diff --git a/spec/services/projects/open_issues_count_service_spec.rb b/spec/services/projects/open_issues_count_service_spec.rb
index c1d49befeb9..c739fea5ecf 100644
--- a/spec/services/projects/open_issues_count_service_spec.rb
+++ b/spec/services/projects/open_issues_count_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::OpenIssuesCountService, :use_clean_rails_memory_store_caching do
+RSpec.describe Projects::OpenIssuesCountService, :use_clean_rails_memory_store_caching do
let(:project) { create(:project) }
subject { described_class.new(project) }
diff --git a/spec/services/projects/open_merge_requests_count_service_spec.rb b/spec/services/projects/open_merge_requests_count_service_spec.rb
index 7d848f9f2c3..6caef181e77 100644
--- a/spec/services/projects/open_merge_requests_count_service_spec.rb
+++ b/spec/services/projects/open_merge_requests_count_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::OpenMergeRequestsCountService, :use_clean_rails_memory_store_caching do
+RSpec.describe Projects::OpenMergeRequestsCountService, :use_clean_rails_memory_store_caching do
let_it_be(:project) { create(:project) }
subject { described_class.new(project) }
diff --git a/spec/services/projects/operations/update_service_spec.rb b/spec/services/projects/operations/update_service_spec.rb
index f4d62b48fe5..3cfc9844d65 100644
--- a/spec/services/projects/operations/update_service_spec.rb
+++ b/spec/services/projects/operations/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::Operations::UpdateService do
+RSpec.describe Projects::Operations::UpdateService do
let_it_be(:user) { create(:user) }
let_it_be(:project, refind: true) { create(:project) }
diff --git a/spec/services/projects/overwrite_project_service_spec.rb b/spec/services/projects/overwrite_project_service_spec.rb
index def39ad3789..e4495da9807 100644
--- a/spec/services/projects/overwrite_project_service_spec.rb
+++ b/spec/services/projects/overwrite_project_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::OverwriteProjectService do
+RSpec.describe Projects::OverwriteProjectService do
include ProjectForksHelper
let(:user) { create(:user) }
diff --git a/spec/services/projects/participants_service_spec.rb b/spec/services/projects/participants_service_spec.rb
index f4a04159db4..33a3e37a2d2 100644
--- a/spec/services/projects/participants_service_spec.rb
+++ b/spec/services/projects/participants_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ParticipantsService do
+RSpec.describe Projects::ParticipantsService do
describe '#groups' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public) }
diff --git a/spec/services/projects/prometheus/alerts/create_events_service_spec.rb b/spec/services/projects/prometheus/alerts/create_events_service_spec.rb
deleted file mode 100644
index 61236b5bbdb..00000000000
--- a/spec/services/projects/prometheus/alerts/create_events_service_spec.rb
+++ /dev/null
@@ -1,312 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Projects::Prometheus::Alerts::CreateEventsService do
- let(:user) { create(:user) }
- let_it_be(:project) { create(:project) }
- let(:metric) { create(:prometheus_metric, project: project) }
- let(:service) { described_class.new(project, user, alerts_payload) }
-
- shared_examples 'events persisted' do |expected_count|
- subject { service.execute }
-
- it 'returns proper amount of created events' do
- expect(subject.size).to eq(expected_count)
- end
-
- it 'increments event count' do
- expect { subject }.to change { PrometheusAlertEvent.count }.to(expected_count)
- end
- end
-
- shared_examples 'no events persisted' do
- subject { service.execute }
-
- it 'returns no created events' do
- expect(subject).to be_empty
- end
-
- it 'does not change event count' do
- expect { subject }.not_to change { PrometheusAlertEvent.count }
- end
- end
-
- shared_examples 'self managed events persisted' do
- subject { service.execute }
-
- it 'returns created events' do
- expect(subject).not_to be_empty
- end
-
- it 'does change self managed event count' do
- expect { subject }.to change { SelfManagedPrometheusAlertEvent.count }
- end
- end
-
- context 'with valid alerts_payload' do
- let!(:alert) { create(:prometheus_alert, prometheus_metric: metric, project: project) }
-
- let(:events) { service.execute }
-
- context 'with a firing payload' do
- let(:started_at) { truncate_to_second(Time.current) }
- let(:firing_event) { alert_payload(status: 'firing', started_at: started_at) }
- let(:alerts_payload) { { 'alerts' => [firing_event] } }
-
- it_behaves_like 'events persisted', 1
-
- it 'returns created event' do
- event = events.first
-
- expect(event).to be_firing
- expect(event.started_at).to eq(started_at)
- expect(event.ended_at).to be_nil
- end
-
- context 'with 2 different firing events' do
- let(:another_firing_event) { alert_payload(status: 'firing', started_at: started_at + 1) }
- let(:alerts_payload) { { 'alerts' => [firing_event, another_firing_event] } }
-
- it_behaves_like 'events persisted', 2
- end
-
- context 'with already persisted firing event' do
- before do
- service.execute
- end
-
- it_behaves_like 'no events persisted'
- end
-
- context 'with duplicate payload' do
- let(:alerts_payload) { { 'alerts' => [firing_event, firing_event] } }
-
- it_behaves_like 'events persisted', 1
- end
- end
-
- context 'with a resolved payload' do
- let(:started_at) { truncate_to_second(Time.current) }
- let(:ended_at) { started_at + 1 }
- let(:resolved_event) { alert_payload(status: 'resolved', started_at: started_at, ended_at: ended_at) }
- let(:alerts_payload) { { 'alerts' => [resolved_event] } }
- let(:payload_key) { Gitlab::Alerting::Alert.new(project: project, payload: resolved_event).gitlab_fingerprint }
-
- context 'with a matching firing event' do
- before do
- create(:prometheus_alert_event,
- prometheus_alert: alert,
- payload_key: payload_key,
- started_at: started_at)
- end
-
- it 'does not create an additional event' do
- expect { service.execute }.not_to change { PrometheusAlertEvent.count }
- end
-
- it 'marks firing event as `resolved`' do
- expect(events.size).to eq(1)
-
- event = events.first
- expect(event).to be_resolved
- expect(event.started_at).to eq(started_at)
- expect(event.ended_at).to eq(ended_at)
- end
-
- context 'with duplicate payload' do
- let(:alerts_payload) { { 'alerts' => [resolved_event, resolved_event] } }
-
- it 'does not create an additional event' do
- expect { service.execute }.not_to change { PrometheusAlertEvent.count }
- end
-
- it 'marks firing event as `resolved` only once' do
- expect(events.size).to eq(1)
- end
- end
- end
-
- context 'without a matching firing event' do
- context 'due to payload_key' do
- let(:payload_key) { 'some other payload_key' }
-
- before do
- create(:prometheus_alert_event,
- prometheus_alert: alert,
- payload_key: payload_key,
- started_at: started_at)
- end
-
- it_behaves_like 'no events persisted'
- end
-
- context 'due to status' do
- before do
- create(:prometheus_alert_event, :resolved,
- prometheus_alert: alert,
- started_at: started_at)
- end
-
- it_behaves_like 'no events persisted'
- end
- end
-
- context 'with already resolved event' do
- before do
- service.execute
- end
-
- it_behaves_like 'no events persisted'
- end
- end
-
- context 'with a metric from another project' do
- let(:another_project) { create(:project) }
- let(:metric) { create(:prometheus_metric, project: another_project) }
- let(:alerts_payload) { { 'alerts' => [alert_payload] } }
-
- let!(:alert) do
- create(:prometheus_alert,
- prometheus_metric: metric,
- project: another_project)
- end
-
- it_behaves_like 'no events persisted'
- end
- end
-
- context 'with invalid payload' do
- let(:alert) { create(:prometheus_alert, prometheus_metric: metric, project: project) }
-
- describe '`alerts` key' do
- context 'is missing' do
- let(:alerts_payload) { {} }
-
- it_behaves_like 'no events persisted'
- end
-
- context 'is nil' do
- let(:alerts_payload) { { 'alerts' => nil } }
-
- it_behaves_like 'no events persisted'
- end
-
- context 'is empty' do
- let(:alerts_payload) { { 'alerts' => [] } }
-
- it_behaves_like 'no events persisted'
- end
-
- context 'is not a Hash' do
- let(:alerts_payload) { { 'alerts' => [:not_a_hash] } }
-
- it_behaves_like 'no events persisted'
- end
-
- describe '`status`' do
- context 'is missing' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(status: nil)] } }
-
- it_behaves_like 'no events persisted'
- end
-
- context 'is invalid' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(status: 'invalid')] } }
-
- it_behaves_like 'no events persisted'
- end
- end
-
- describe '`started_at`' do
- context 'is missing' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(started_at: nil)] } }
-
- it_behaves_like 'no events persisted'
- end
-
- context 'is invalid' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(started_at: 'invalid date')] } }
-
- it_behaves_like 'no events persisted'
- end
- end
-
- describe '`ended_at`' do
- context 'is missing and status is resolved' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(ended_at: nil, status: 'resolved')] } }
-
- it_behaves_like 'no events persisted'
- end
-
- context 'is invalid and status is resolved' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(ended_at: 'invalid date', status: 'resolved')] } }
-
- it_behaves_like 'no events persisted'
- end
- end
-
- describe '`labels`' do
- describe '`gitlab_alert_id`' do
- context 'is missing' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(gitlab_alert_id: nil)] } }
-
- it_behaves_like 'no events persisted'
- end
-
- context 'is missing but title is given' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(gitlab_alert_id: nil, title: 'alert')] } }
-
- it_behaves_like 'self managed events persisted'
- end
-
- context 'is missing and environment name is given' do
- let(:environment) { create(:environment, project: project) }
- let(:alerts_payload) { { 'alerts' => [alert_payload(gitlab_alert_id: nil, title: 'alert', environment: environment.name)] } }
-
- it_behaves_like 'self managed events persisted'
-
- it 'associates the environment to the alert event' do
- service.execute
-
- expect(SelfManagedPrometheusAlertEvent.last.environment).to eq environment
- end
- end
-
- context 'is invalid' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(gitlab_alert_id: '-1')] } }
-
- it_behaves_like 'no events persisted'
- end
- end
- end
- end
- end
-
- private
-
- def alert_payload(status: 'firing', started_at: Time.current, ended_at: Time.current, gitlab_alert_id: alert.prometheus_metric_id, title: nil, environment: nil)
- payload = {}
-
- payload['status'] = status if status
- payload['startsAt'] = utc_rfc3339(started_at) if started_at
- payload['endsAt'] = utc_rfc3339(ended_at) if ended_at
- payload['labels'] = {}
- payload['labels']['gitlab_alert_id'] = gitlab_alert_id.to_s if gitlab_alert_id
- payload['labels']['alertname'] = title if title
- payload['labels']['gitlab_environment_name'] = environment if environment
-
- payload
- end
-
- # Example: 2018-09-27T18:25:31.079079416Z
- def utc_rfc3339(date)
- date.utc.rfc3339
- rescue
- date
- end
-
- def truncate_to_second(date)
- date.change(usec: 0)
- end
-end
diff --git a/spec/services/projects/prometheus/alerts/create_service_spec.rb b/spec/services/projects/prometheus/alerts/create_service_spec.rb
index 50c776df734..c0bc9336558 100644
--- a/spec/services/projects/prometheus/alerts/create_service_spec.rb
+++ b/spec/services/projects/prometheus/alerts/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::Prometheus::Alerts::CreateService do
+RSpec.describe Projects::Prometheus::Alerts::CreateService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
diff --git a/spec/services/projects/prometheus/alerts/destroy_service_spec.rb b/spec/services/projects/prometheus/alerts/destroy_service_spec.rb
index 7205ace8308..573711051b7 100644
--- a/spec/services/projects/prometheus/alerts/destroy_service_spec.rb
+++ b/spec/services/projects/prometheus/alerts/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::Prometheus::Alerts::DestroyService do
+RSpec.describe Projects::Prometheus::Alerts::DestroyService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:alert) { create(:prometheus_alert, project: project) }
diff --git a/spec/services/projects/prometheus/alerts/notify_service_spec.rb b/spec/services/projects/prometheus/alerts/notify_service_spec.rb
index 95acedb1e76..aae257e3e3a 100644
--- a/spec/services/projects/prometheus/alerts/notify_service_spec.rb
+++ b/spec/services/projects/prometheus/alerts/notify_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::Prometheus::Alerts::NotifyService do
+RSpec.describe Projects::Prometheus::Alerts::NotifyService do
include PrometheusHelpers
let_it_be(:project, reload: true) { create(:project) }
@@ -36,48 +36,8 @@ describe Projects::Prometheus::Alerts::NotifyService do
end
end
- shared_examples 'processes incident issues' do |amount|
- let(:create_incident_service) { spy }
-
- it 'processes issues' do
- expect(IncidentManagement::ProcessPrometheusAlertWorker)
- .to receive(:perform_async)
- .with(project.id, kind_of(Hash))
- .exactly(amount).times
-
- Sidekiq::Testing.inline! do
- expect(subject).to be_success
- end
- end
- end
-
- shared_examples 'does not process incident issues' do
- it 'does not process issues' do
- expect(IncidentManagement::ProcessPrometheusAlertWorker)
- .not_to receive(:perform_async)
-
- expect(subject).to be_success
- end
- end
-
- shared_examples 'persists events' do
- let(:create_events_service) { spy }
-
- it 'persists events' do
- expect(Projects::Prometheus::Alerts::CreateEventsService)
- .to receive(:new)
- .and_return(create_events_service)
-
- expect(create_events_service)
- .to receive(:execute)
-
- expect(subject).to be_success
- end
- end
-
shared_examples 'notifies alerts' do
it_behaves_like 'sends notification email'
- it_behaves_like 'persists events'
end
shared_examples 'no notifications' do |http_status:|
@@ -102,6 +62,41 @@ describe Projects::Prometheus::Alerts::NotifyService do
let(:payload_alert_firing) { payload_raw['alerts'].first }
let(:token) { 'token' }
+ context 'with environment specific clusters' do
+ let(:prd_cluster) do
+ cluster
+ end
+
+ let(:stg_cluster) do
+ create(:cluster, :provided_by_user, projects: [project], enabled: true, environment_scope: 'stg/*')
+ end
+
+ let(:stg_environment) do
+ create(:environment, project: project, name: 'stg/1')
+ end
+
+ let(:alert_firing) do
+ create(:prometheus_alert, project: project, environment: stg_environment)
+ end
+
+ before do
+ create(:clusters_applications_prometheus, :installed,
+ cluster: prd_cluster, alert_manager_token: token)
+ create(:clusters_applications_prometheus, :installed,
+ cluster: stg_cluster, alert_manager_token: nil)
+ end
+
+ context 'without token' do
+ let(:token_input) { nil }
+
+ it_behaves_like 'notifies alerts'
+ end
+
+ context 'with token' do
+ it_behaves_like 'no notifications', http_status: :unauthorized
+ end
+ end
+
context 'with project specific cluster' do
using RSpec::Parameterized::TableSyntax
@@ -222,8 +217,6 @@ describe Projects::Prometheus::Alerts::NotifyService do
context 'when incident_management_setting does not exist' do
let!(:setting) { nil }
- it_behaves_like 'persists events'
-
it 'does not send notification email', :sidekiq_might_not_need_inline do
expect_any_instance_of(NotificationService)
.not_to receive(:async)
@@ -241,8 +234,6 @@ describe Projects::Prometheus::Alerts::NotifyService do
create(:project_incident_management_setting, send_email: false, project: project)
end
- it_behaves_like 'persists events'
-
it 'does not send notification' do
expect(NotificationService).not_to receive(:new)
@@ -276,45 +267,6 @@ describe Projects::Prometheus::Alerts::NotifyService do
end
end
end
-
- context 'process incident issues' do
- before do
- create(:prometheus_service, project: project)
- create(:project_alerting_setting, project: project, token: token)
- end
-
- context 'with create_issue setting enabled' do
- before do
- setting.update!(create_issue: true)
- end
-
- it_behaves_like 'processes incident issues', 2
-
- context 'multiple firing alerts' do
- let(:payload_raw) do
- prometheus_alert_payload(firing: [alert_firing, alert_firing], resolved: [])
- end
-
- it_behaves_like 'processes incident issues', 2
- end
-
- context 'without firing alerts' do
- let(:payload_raw) do
- prometheus_alert_payload(firing: [], resolved: [alert_resolved])
- end
-
- it_behaves_like 'processes incident issues', 1
- end
- end
-
- context 'with create_issue setting disabled' do
- before do
- setting.update!(create_issue: false)
- end
-
- it_behaves_like 'does not process incident issues'
- end
- end
end
context 'with invalid payload' do
@@ -345,13 +297,6 @@ describe Projects::Prometheus::Alerts::NotifyService do
subject
end
-
- it 'does not process issues' do
- expect(IncidentManagement::ProcessPrometheusAlertWorker)
- .not_to receive(:perform_async)
-
- subject
- end
end
end
diff --git a/spec/services/projects/prometheus/alerts/update_service_spec.rb b/spec/services/projects/prometheus/alerts/update_service_spec.rb
index 8a99c2679f7..e831d001838 100644
--- a/spec/services/projects/prometheus/alerts/update_service_spec.rb
+++ b/spec/services/projects/prometheus/alerts/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::Prometheus::Alerts::UpdateService do
+RSpec.describe Projects::Prometheus::Alerts::UpdateService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:environment) { create(:environment, project: project) }
diff --git a/spec/services/projects/prometheus/metrics/destroy_service_spec.rb b/spec/services/projects/prometheus/metrics/destroy_service_spec.rb
index 81fce82cf46..17cc88b27b6 100644
--- a/spec/services/projects/prometheus/metrics/destroy_service_spec.rb
+++ b/spec/services/projects/prometheus/metrics/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::Prometheus::Metrics::DestroyService do
+RSpec.describe Projects::Prometheus::Metrics::DestroyService do
let(:metric) { create(:prometheus_metric) }
subject { described_class.new(metric) }
diff --git a/spec/services/projects/prometheus/metrics/update_service_spec.rb b/spec/services/projects/prometheus/metrics/update_service_spec.rb
index a53c6ae37cd..bf87093150c 100644
--- a/spec/services/projects/prometheus/metrics/update_service_spec.rb
+++ b/spec/services/projects/prometheus/metrics/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::Prometheus::Metrics::UpdateService do
+RSpec.describe Projects::Prometheus::Metrics::UpdateService do
let(:metric) { create(:prometheus_metric) }
it 'updates the prometheus metric' do
diff --git a/spec/services/projects/propagate_service_template_spec.rb b/spec/services/projects/propagate_service_template_spec.rb
index ddc27c037f8..266bf2cc213 100644
--- a/spec/services/projects/propagate_service_template_spec.rb
+++ b/spec/services/projects/propagate_service_template_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::PropagateServiceTemplate do
+RSpec.describe Projects::PropagateServiceTemplate do
describe '.propagate' do
let!(:service_template) do
PushoverService.create(
diff --git a/spec/services/projects/protect_default_branch_service_spec.rb b/spec/services/projects/protect_default_branch_service_spec.rb
index c0b819ab17b..a485a64ca35 100644
--- a/spec/services/projects/protect_default_branch_service_spec.rb
+++ b/spec/services/projects/protect_default_branch_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::ProtectDefaultBranchService do
+RSpec.describe Projects::ProtectDefaultBranchService do
let(:service) { described_class.new(project) }
let(:project) { create(:project) }
diff --git a/spec/services/projects/repository_languages_service_spec.rb b/spec/services/projects/repository_languages_service_spec.rb
index 46c5095327d..cb61a7a1a3e 100644
--- a/spec/services/projects/repository_languages_service_spec.rb
+++ b/spec/services/projects/repository_languages_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::RepositoryLanguagesService do
+RSpec.describe Projects::RepositoryLanguagesService do
let(:service) { described_class.new(project, project.owner) }
context 'when detected_repository_languages flag is set' do
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 0e2431c0e44..72426a6f6ec 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::TransferService do
+RSpec.describe Projects::TransferService do
include GitHelpers
let(:user) { create(:user) }
diff --git a/spec/services/projects/unlink_fork_service_spec.rb b/spec/services/projects/unlink_fork_service_spec.rb
index a6bdc69cdca..6a2c55a5e55 100644
--- a/spec/services/projects/unlink_fork_service_spec.rb
+++ b/spec/services/projects/unlink_fork_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::UnlinkForkService, :use_clean_rails_memory_store_caching do
+RSpec.describe Projects::UnlinkForkService, :use_clean_rails_memory_store_caching do
include ProjectForksHelper
subject { described_class.new(forked_project, user) }
@@ -53,6 +53,7 @@ describe Projects::UnlinkForkService, :use_clean_rails_memory_store_caching do
expect(source.forks_count).to eq(1)
subject.execute
+ BatchLoader::Executor.clear_current
expect(source.forks_count).to be_zero
end
@@ -146,6 +147,7 @@ describe Projects::UnlinkForkService, :use_clean_rails_memory_store_caching do
expect(project.forks_count).to eq(2)
subject.execute
+ BatchLoader::Executor.clear_current
expect(project.forks_count).to be_zero
end
@@ -212,6 +214,7 @@ describe Projects::UnlinkForkService, :use_clean_rails_memory_store_caching do
expect(forked_project.forks_count).to eq(1)
subject.execute
+ BatchLoader::Executor.clear_current
expect(project.forks_count).to eq(1)
expect(forked_project.forks_count).to eq(0)
diff --git a/spec/services/projects/update_pages_configuration_service_spec.rb b/spec/services/projects/update_pages_configuration_service_spec.rb
index 363d3df0f84..c4c9fc779fa 100644
--- a/spec/services/projects/update_pages_configuration_service_spec.rb
+++ b/spec/services/projects/update_pages_configuration_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::UpdatePagesConfigurationService do
+RSpec.describe Projects::UpdatePagesConfigurationService do
let(:project) { create(:project) }
let(:service) { described_class.new(project) }
diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb
index 29c3c300d1b..2e02cb56668 100644
--- a/spec/services/projects/update_pages_service_spec.rb
+++ b/spec/services/projects/update_pages_service_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Projects::UpdatePagesService do
+RSpec.describe Projects::UpdatePagesService do
let_it_be(:project, refind: true) { create(:project, :repository) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit('HEAD').sha) }
let(:build) { create(:ci_build, pipeline: pipeline, ref: 'HEAD') }
diff --git a/spec/services/projects/update_remote_mirror_service_spec.rb b/spec/services/projects/update_remote_mirror_service_spec.rb
index 418973fb0a6..f0a8074f46c 100644
--- a/spec/services/projects/update_remote_mirror_service_spec.rb
+++ b/spec/services/projects/update_remote_mirror_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::UpdateRemoteMirrorService do
+RSpec.describe Projects::UpdateRemoteMirrorService do
let(:project) { create(:project, :repository) }
let(:remote_project) { create(:forked_project_with_submodules) }
let(:remote_mirror) { create(:remote_mirror, project: project, enabled: true) }
diff --git a/spec/services/projects/update_repository_storage_service_spec.rb b/spec/services/projects/update_repository_storage_service_spec.rb
index e37580e7367..57e02c26b71 100644
--- a/spec/services/projects/update_repository_storage_service_spec.rb
+++ b/spec/services/projects/update_repository_storage_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::UpdateRepositoryStorageService do
+RSpec.describe Projects::UpdateRepositoryStorageService do
include Gitlab::ShellAdapter
subject { described_class.new(repository_storage_move) }
@@ -37,14 +37,15 @@ describe Projects::UpdateRepositoryStorageService do
project.repository.path_to_repo
end
- expect(project_repository_double).to receive(:create_repository)
- .and_return(true)
expect(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
expect(project_repository_double).to receive(:checksum)
.and_return(checksum)
+ expect(GitlabShellWorker).to receive(:perform_async).with(:mv_repository, 'default', anything, anything)
+ .and_call_original
result = subject.execute
+ project.reload
expect(result).to be_success
expect(project).not_to be_repository_read_only
@@ -70,8 +71,6 @@ describe Projects::UpdateRepositoryStorageService do
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
- expect(project_repository_double).to receive(:create_repository)
- .and_return(true)
expect(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
.and_raise(Gitlab::Git::CommandError)
@@ -90,8 +89,6 @@ describe Projects::UpdateRepositoryStorageService do
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
- expect(project_repository_double).to receive(:create_repository)
- .and_return(true)
expect(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
expect(project_repository_double).to receive(:checksum)
@@ -113,20 +110,43 @@ describe Projects::UpdateRepositoryStorageService do
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
- expect(project_repository_double).to receive(:create_repository)
- .and_return(true)
expect(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
expect(project_repository_double).to receive(:checksum)
.and_return(checksum)
result = subject.execute
+ project.reload
expect(result).to be_success
expect(project.repository_storage).to eq('test_second_storage')
expect(project.reload_pool_repository).to be_nil
end
end
+
+ context 'when the repository move is finished' do
+ let(:repository_storage_move) { create(:project_repository_storage_move, :finished, project: project, destination_storage_name: destination) }
+
+ it 'is idempotent' do
+ expect do
+ result = subject.execute
+
+ expect(result).to be_success
+ end.not_to change(repository_storage_move, :state)
+ end
+ end
+
+ context 'when the repository move is failed' do
+ let(:repository_storage_move) { create(:project_repository_storage_move, :failed, project: project, destination_storage_name: destination) }
+
+ it 'is idempotent' do
+ expect do
+ result = subject.execute
+
+ expect(result).to be_success
+ end.not_to change(repository_storage_move, :state)
+ end
+ end
end
context 'with wiki repository' do
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index 8a17884f641..6620ee6e697 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::UpdateService do
+RSpec.describe Projects::UpdateService do
include ExternalAuthorizationServiceHelpers
include ProjectForksHelper
diff --git a/spec/services/projects/update_statistics_service_spec.rb b/spec/services/projects/update_statistics_service_spec.rb
index 8534853fbc7..92e97186be3 100644
--- a/spec/services/projects/update_statistics_service_spec.rb
+++ b/spec/services/projects/update_statistics_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Projects::UpdateStatisticsService do
+RSpec.describe Projects::UpdateStatisticsService do
let(:service) { described_class.new(project, nil, statistics: statistics)}
let(:statistics) { %w(repository_size) }
diff --git a/spec/services/prometheus/create_default_alerts_service_spec.rb b/spec/services/prometheus/create_default_alerts_service_spec.rb
index a28c38491de..e149161d881 100644
--- a/spec/services/prometheus/create_default_alerts_service_spec.rb
+++ b/spec/services/prometheus/create_default_alerts_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Prometheus::CreateDefaultAlertsService do
+RSpec.describe Prometheus::CreateDefaultAlertsService do
let_it_be(:project) { create(:project, :repository) }
let(:instance) { described_class.new(project: project) }
let(:expected_alerts) { described_class::DEFAULT_ALERTS }
diff --git a/spec/services/prometheus/proxy_service_spec.rb b/spec/services/prometheus/proxy_service_spec.rb
index bd451ff00a1..f22ea361fde 100644
--- a/spec/services/prometheus/proxy_service_spec.rb
+++ b/spec/services/prometheus/proxy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Prometheus::ProxyService do
+RSpec.describe Prometheus::ProxyService do
include ReactiveCachingHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/services/prometheus/proxy_variable_substitution_service_spec.rb b/spec/services/prometheus/proxy_variable_substitution_service_spec.rb
index 2435dda07b4..d8c1fdffb98 100644
--- a/spec/services/prometheus/proxy_variable_substitution_service_spec.rb
+++ b/spec/services/prometheus/proxy_variable_substitution_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Prometheus::ProxyVariableSubstitutionService do
+RSpec.describe Prometheus::ProxyVariableSubstitutionService do
describe '#execute' do
let_it_be(:environment) { create(:environment) }
diff --git a/spec/services/protected_branches/create_service_spec.rb b/spec/services/protected_branches/create_service_spec.rb
index 82d24ec43f6..986322e4d87 100644
--- a/spec/services/protected_branches/create_service_spec.rb
+++ b/spec/services/protected_branches/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProtectedBranches::CreateService do
+RSpec.describe ProtectedBranches::CreateService do
let(:project) { create(:project) }
let(:user) { project.owner }
let(:params) do
diff --git a/spec/services/protected_branches/destroy_service_spec.rb b/spec/services/protected_branches/destroy_service_spec.rb
index 3287eb9a59b..98d31147754 100644
--- a/spec/services/protected_branches/destroy_service_spec.rb
+++ b/spec/services/protected_branches/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProtectedBranches::DestroyService do
+RSpec.describe ProtectedBranches::DestroyService do
let(:protected_branch) { create(:protected_branch) }
let(:project) { protected_branch.project }
let(:user) { project.owner }
diff --git a/spec/services/protected_branches/update_service_spec.rb b/spec/services/protected_branches/update_service_spec.rb
index 7967ff81075..fdfbdf2e6ae 100644
--- a/spec/services/protected_branches/update_service_spec.rb
+++ b/spec/services/protected_branches/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProtectedBranches::UpdateService do
+RSpec.describe ProtectedBranches::UpdateService do
let(:protected_branch) { create(:protected_branch) }
let(:project) { protected_branch.project }
let(:user) { project.owner }
diff --git a/spec/services/protected_tags/create_service_spec.rb b/spec/services/protected_tags/create_service_spec.rb
index e58a539eb6f..e85a43eb51c 100644
--- a/spec/services/protected_tags/create_service_spec.rb
+++ b/spec/services/protected_tags/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProtectedTags::CreateService do
+RSpec.describe ProtectedTags::CreateService do
let(:project) { create(:project) }
let(:user) { project.owner }
let(:params) do
diff --git a/spec/services/protected_tags/destroy_service_spec.rb b/spec/services/protected_tags/destroy_service_spec.rb
index 52d1d1caa34..fbd1452a8d1 100644
--- a/spec/services/protected_tags/destroy_service_spec.rb
+++ b/spec/services/protected_tags/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProtectedTags::DestroyService do
+RSpec.describe ProtectedTags::DestroyService do
let(:protected_tag) { create(:protected_tag) }
let(:project) { protected_tag.project }
let(:user) { project.owner }
diff --git a/spec/services/protected_tags/update_service_spec.rb b/spec/services/protected_tags/update_service_spec.rb
index ca5109aca9c..ed151ca2347 100644
--- a/spec/services/protected_tags/update_service_spec.rb
+++ b/spec/services/protected_tags/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProtectedTags::UpdateService do
+RSpec.describe ProtectedTags::UpdateService do
let(:protected_tag) { create(:protected_tag) }
let(:project) { protected_tag.project }
let(:user) { project.owner }
diff --git a/spec/services/push_event_payload_service_spec.rb b/spec/services/push_event_payload_service_spec.rb
index 855b10c0259..de2bec21a3c 100644
--- a/spec/services/push_event_payload_service_spec.rb
+++ b/spec/services/push_event_payload_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PushEventPayloadService do
+RSpec.describe PushEventPayloadService do
let(:event) { create(:push_event) }
describe '#execute' do
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index 1bd402e38be..57e32b1aea9 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe QuickActions::InterpretService do
+RSpec.describe QuickActions::InterpretService do
let(:project) { create(:project, :public) }
let(:developer) { create(:user) }
let(:developer2) { create(:user) }
@@ -792,7 +792,7 @@ describe QuickActions::InterpretService do
let(:issuable) { issue }
end
- it_behaves_like 'assign command', :quarantine do
+ it_behaves_like 'assign command', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27989' do
let(:content) { "/assign @#{developer.username} @#{developer2.username}" }
let(:issuable) { merge_request }
end
diff --git a/spec/services/quick_actions/target_service_spec.rb b/spec/services/quick_actions/target_service_spec.rb
index 0aeb29cbeec..d960678f809 100644
--- a/spec/services/quick_actions/target_service_spec.rb
+++ b/spec/services/quick_actions/target_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe QuickActions::TargetService do
+RSpec.describe QuickActions::TargetService do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:service) { described_class.new(project, user) }
diff --git a/spec/services/releases/create_evidence_service_spec.rb b/spec/services/releases/create_evidence_service_spec.rb
index caa36a6b21d..818d20f0468 100644
--- a/spec/services/releases/create_evidence_service_spec.rb
+++ b/spec/services/releases/create_evidence_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Releases::CreateEvidenceService do
+RSpec.describe Releases::CreateEvidenceService do
let_it_be(:project) { create(:project) }
let(:release) { create(:release, project: project) }
let(:service) { described_class.new(release) }
diff --git a/spec/services/releases/create_service_spec.rb b/spec/services/releases/create_service_spec.rb
index 4e3d9d5f108..3c0698aa203 100644
--- a/spec/services/releases/create_service_spec.rb
+++ b/spec/services/releases/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Releases::CreateService do
+RSpec.describe Releases::CreateService do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:tag_name) { project.repository.tag_names.first }
diff --git a/spec/services/releases/destroy_service_spec.rb b/spec/services/releases/destroy_service_spec.rb
index 9d027767cd2..bc5bff0b31d 100644
--- a/spec/services/releases/destroy_service_spec.rb
+++ b/spec/services/releases/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Releases::DestroyService do
+RSpec.describe Releases::DestroyService do
let(:project) { create(:project, :repository) }
let(:mainatiner) { create(:user) }
let(:repoter) { create(:user) }
diff --git a/spec/services/releases/update_service_spec.rb b/spec/services/releases/update_service_spec.rb
index 7f1849e39a4..00544b820cb 100644
--- a/spec/services/releases/update_service_spec.rb
+++ b/spec/services/releases/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Releases::UpdateService do
+RSpec.describe Releases::UpdateService do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:new_name) { 'A new name' }
diff --git a/spec/services/repositories/destroy_rollback_service_spec.rb b/spec/services/repositories/destroy_rollback_service_spec.rb
index c3cdae17de7..9cc41a4c7f8 100644
--- a/spec/services/repositories/destroy_rollback_service_spec.rb
+++ b/spec/services/repositories/destroy_rollback_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Repositories::DestroyRollbackService do
+RSpec.describe Repositories::DestroyRollbackService do
let_it_be(:user) { create(:user) }
let!(:project) { create(:project, :repository, namespace: user.namespace) }
let(:repository) { project.repository }
diff --git a/spec/services/repositories/destroy_service_spec.rb b/spec/services/repositories/destroy_service_spec.rb
index 9c2694483c1..30ec84b44e7 100644
--- a/spec/services/repositories/destroy_service_spec.rb
+++ b/spec/services/repositories/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Repositories::DestroyService do
+RSpec.describe Repositories::DestroyService do
let_it_be(:user) { create(:user) }
let!(:project) { create(:project, :repository, namespace: user.namespace) }
let(:repository) { project.repository }
@@ -34,6 +34,21 @@ describe Repositories::DestroyService do
project.touch
end
+ context 'on a read-only instance' do
+ before do
+ allow(Gitlab::Database).to receive(:read_only?).and_return(true)
+ end
+
+ it 'schedules the repository deletion' do
+ expect(Repositories::ShellDestroyService).to receive(:new).with(repository).and_call_original
+
+ expect(GitlabShellWorker).to receive(:perform_in)
+ .with(Repositories::ShellDestroyService::REPO_REMOVAL_DELAY, :remove_repository, project.repository_storage, remove_path)
+
+ subject
+ end
+ end
+
it 'removes the repository', :sidekiq_inline do
subject
diff --git a/spec/services/repositories/shell_destroy_service_spec.rb b/spec/services/repositories/shell_destroy_service_spec.rb
index 9419977f6fe..9020ef7b209 100644
--- a/spec/services/repositories/shell_destroy_service_spec.rb
+++ b/spec/services/repositories/shell_destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Repositories::ShellDestroyService do
+RSpec.describe Repositories::ShellDestroyService do
let_it_be(:user) { create(:user) }
let!(:project) { create(:project, :repository, namespace: user.namespace) }
let(:path) { project.repository.disk_path }
diff --git a/spec/services/repository_archive_clean_up_service_spec.rb b/spec/services/repository_archive_clean_up_service_spec.rb
index 80b177a0174..c6d673fb1b5 100644
--- a/spec/services/repository_archive_clean_up_service_spec.rb
+++ b/spec/services/repository_archive_clean_up_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RepositoryArchiveCleanUpService do
+RSpec.describe RepositoryArchiveCleanUpService do
subject(:service) { described_class.new }
describe '#execute (new archive locations)' do
diff --git a/spec/services/reset_project_cache_service_spec.rb b/spec/services/reset_project_cache_service_spec.rb
index a4db4481c36..3e79270da8d 100644
--- a/spec/services/reset_project_cache_service_spec.rb
+++ b/spec/services/reset_project_cache_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResetProjectCacheService do
+RSpec.describe ResetProjectCacheService do
let(:project) { create(:project) }
let(:user) { create(:user) }
diff --git a/spec/services/resource_access_tokens/create_service_spec.rb b/spec/services/resource_access_tokens/create_service_spec.rb
index 57e7e4e66de..f22c379cd30 100644
--- a/spec/services/resource_access_tokens/create_service_spec.rb
+++ b/spec/services/resource_access_tokens/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResourceAccessTokens::CreateService do
+RSpec.describe ResourceAccessTokens::CreateService do
subject { described_class.new(user, resource, params).execute }
let_it_be(:user) { create(:user) }
@@ -45,6 +45,27 @@ describe ResourceAccessTokens::CreateService do
expect(access_token.user.reload.user_type).to eq("#{resource_type}_bot")
end
+ context 'email confirmation status' do
+ shared_examples_for 'creates a user that has their email confirmed' do
+ it 'creates a user that has their email confirmed' do
+ response = subject
+ access_token = response.payload[:access_token]
+
+ expect(access_token.user.reload.confirmed?).to eq(true)
+ end
+ end
+
+ context 'when created by an admin' do
+ it_behaves_like 'creates a user that has their email confirmed' do
+ let(:user) { create(:admin) }
+ end
+ end
+
+ context 'when created by a non-admin' do
+ it_behaves_like 'creates a user that has their email confirmed'
+ end
+ end
+
context 'bot name' do
context 'when no value is passed' do
it 'uses default value' do
diff --git a/spec/services/resource_access_tokens/revoke_service_spec.rb b/spec/services/resource_access_tokens/revoke_service_spec.rb
index 3ce82745b9e..ffc06d770f8 100644
--- a/spec/services/resource_access_tokens/revoke_service_spec.rb
+++ b/spec/services/resource_access_tokens/revoke_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResourceAccessTokens::RevokeService do
+RSpec.describe ResourceAccessTokens::RevokeService do
subject { described_class.new(user, resource, access_token).execute }
let_it_be(:user) { create(:user) }
diff --git a/spec/services/resource_events/change_labels_service_spec.rb b/spec/services/resource_events/change_labels_service_spec.rb
index 2b987b7fec9..efee185669e 100644
--- a/spec/services/resource_events/change_labels_service_spec.rb
+++ b/spec/services/resource_events/change_labels_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResourceEvents::ChangeLabelsService do
+RSpec.describe ResourceEvents::ChangeLabelsService do
let_it_be(:project) { create(:project) }
let_it_be(:author) { create(:user) }
let(:resource) { create(:issue, project: project) }
diff --git a/spec/services/resource_events/change_milestone_service_spec.rb b/spec/services/resource_events/change_milestone_service_spec.rb
index dec01d0db8d..9c0f9420f7a 100644
--- a/spec/services/resource_events/change_milestone_service_spec.rb
+++ b/spec/services/resource_events/change_milestone_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResourceEvents::ChangeMilestoneService do
+RSpec.describe ResourceEvents::ChangeMilestoneService do
[:issue, :merge_request].each do |issuable|
it_behaves_like 'a milestone events creator' do
let(:resource) { create(issuable) }
diff --git a/spec/services/resource_events/change_state_service_spec.rb b/spec/services/resource_events/change_state_service_spec.rb
index e5d2a4ab11e..5b5379b241b 100644
--- a/spec/services/resource_events/change_state_service_spec.rb
+++ b/spec/services/resource_events/change_state_service_spec.rb
@@ -2,38 +2,95 @@
require 'spec_helper'
-describe ResourceEvents::ChangeStateService do
+RSpec.describe ResourceEvents::ChangeStateService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let(:issue) { create(:issue, project: project) }
let(:merge_request) { create(:merge_request, source_project: project) }
+ let(:source_commit) { create(:commit, project: project) }
+ let(:source_merge_request) { create(:merge_request, source_project: project, target_project: project, target_branch: 'foo') }
- describe '#execute' do
- context 'when resource is an issue' do
- %w[opened reopened closed locked].each do |state|
- it "creates the expected event if issue has #{state} state" do
- described_class.new(user: user, resource: issue).execute(state)
+ shared_examples 'a state event' do
+ %w[opened reopened closed locked].each do |state|
+ it "creates the expected event if resource has #{state} state" do
+ described_class.new(user: user, resource: resource).execute(status: state, mentionable_source: source)
+
+ event = resource.resource_state_events.last
- event = issue.resource_state_events.last
- expect(event.issue).to eq(issue)
+ if resource.is_a?(Issue)
+ expect(event.issue).to eq(resource)
expect(event.merge_request).to be_nil
- expect(event.state).to eq(state)
+ elsif resource.is_a?(MergeRequest)
+ expect(event.issue).to be_nil
+ expect(event.merge_request).to eq(resource)
end
+
+ expect(event.state).to eq(state)
+
+ expect_event_source(event, source)
end
end
+ end
- context 'when resource is a merge request' do
- %w[opened reopened closed locked merged].each do |state|
- it "creates the expected event if merge request has #{state} state" do
- described_class.new(user: user, resource: merge_request).execute(state)
+ describe '#execute' do
+ context 'when resource is an Issue' do
+ context 'when no source is given' do
+ it_behaves_like 'a state event' do
+ let(:resource) { issue }
+ let(:source) { nil }
+ end
+ end
- event = merge_request.resource_state_events.last
- expect(event.issue).to be_nil
- expect(event.merge_request).to eq(merge_request)
- expect(event.state).to eq(state)
+ context 'when source commit is given' do
+ it_behaves_like 'a state event' do
+ let(:resource) { issue }
+ let(:source) { source_commit }
+ end
+ end
+
+ context 'when source merge request is given' do
+ it_behaves_like 'a state event' do
+ let(:resource) { issue }
+ let(:source) { source_merge_request }
end
end
end
+
+ context 'when resource is a MergeRequest' do
+ context 'when no source is given' do
+ it_behaves_like 'a state event' do
+ let(:resource) { merge_request }
+ let(:source) { nil }
+ end
+ end
+
+ context 'when source commit is given' do
+ it_behaves_like 'a state event' do
+ let(:resource) { merge_request }
+ let(:source) { source_commit }
+ end
+ end
+
+ context 'when source merge request is given' do
+ it_behaves_like 'a state event' do
+ let(:resource) { merge_request }
+ let(:source) { source_merge_request }
+ end
+ end
+ end
+ end
+
+ def expect_event_source(event, source)
+ if source.is_a?(MergeRequest)
+ expect(event.source_commit).to be_nil
+ expect(event.source_merge_request).to eq(source)
+ elsif source.is_a?(Commit)
+ expect(event.source_commit).to eq(source.id)
+ expect(event.source_merge_request).to be_nil
+ else
+ expect(event.source_merge_request).to be_nil
+ expect(event.source_commit).to be_nil
+ end
end
end
diff --git a/spec/services/resource_events/merge_into_notes_service_spec.rb b/spec/services/resource_events/merge_into_notes_service_spec.rb
index 2664a27244d..6209294f4ce 100644
--- a/spec/services/resource_events/merge_into_notes_service_spec.rb
+++ b/spec/services/resource_events/merge_into_notes_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResourceEvents::MergeIntoNotesService do
+RSpec.describe ResourceEvents::MergeIntoNotesService do
def create_event(params)
event_params = { action: :add, label: label, issue: resource,
user: user }
@@ -61,7 +61,7 @@ describe ResourceEvents::MergeIntoNotesService do
event = create_event(created_at: 1.day.ago)
notes = described_class.new(resource, user,
- last_fetched_at: 2.days.ago.to_i).execute
+ last_fetched_at: 2.days.ago).execute
expect(notes.count).to eq 1
expect(notes.first.discussion_id).to eq event.discussion_id
diff --git a/spec/services/resource_events/synthetic_label_notes_builder_service_spec.rb b/spec/services/resource_events/synthetic_label_notes_builder_service_spec.rb
index 41902bc1da1..cb42ad5b617 100644
--- a/spec/services/resource_events/synthetic_label_notes_builder_service_spec.rb
+++ b/spec/services/resource_events/synthetic_label_notes_builder_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResourceEvents::SyntheticLabelNotesBuilderService do
+RSpec.describe ResourceEvents::SyntheticLabelNotesBuilderService do
describe '#execute' do
let!(:user) { create(:user) }
diff --git a/spec/services/resource_events/synthetic_milestone_notes_builder_service_spec.rb b/spec/services/resource_events/synthetic_milestone_notes_builder_service_spec.rb
index e98b8bd00dc..5e3afeabee7 100644
--- a/spec/services/resource_events/synthetic_milestone_notes_builder_service_spec.rb
+++ b/spec/services/resource_events/synthetic_milestone_notes_builder_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ResourceEvents::SyntheticMilestoneNotesBuilderService do
+RSpec.describe ResourceEvents::SyntheticMilestoneNotesBuilderService do
describe '#execute' do
let_it_be(:user) { create(:user) }
let_it_be(:issue) { create(:issue, author: user) }
diff --git a/spec/services/search/global_service_spec.rb b/spec/services/search/global_service_spec.rb
index 0f829df90b3..90ad18e5571 100644
--- a/spec/services/search/global_service_spec.rb
+++ b/spec/services/search/global_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Search::GlobalService do
+RSpec.describe Search::GlobalService do
let(:user) { create(:user) }
let(:internal_user) { create(:user) }
diff --git a/spec/services/search/group_service_spec.rb b/spec/services/search/group_service_spec.rb
index cfb672753b8..d3026d158d4 100644
--- a/spec/services/search/group_service_spec.rb
+++ b/spec/services/search/group_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Search::GroupService do
+RSpec.describe Search::GroupService do
shared_examples_for 'group search' do
context 'finding projects by name' do
let(:user) { create(:user) }
diff --git a/spec/services/search/snippet_service_spec.rb b/spec/services/search/snippet_service_spec.rb
index cb2bb0c43fd..ceaf3d055bf 100644
--- a/spec/services/search/snippet_service_spec.rb
+++ b/spec/services/search/snippet_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Search::SnippetService do
+RSpec.describe Search::SnippetService do
let_it_be(:author) { create(:author) }
let_it_be(:project) { create(:project, :public) }
diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb
index 0333eb85fb6..52aef73ac77 100644
--- a/spec/services/search_service_spec.rb
+++ b/spec/services/search_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SearchService do
+RSpec.describe SearchService do
let_it_be(:user) { create(:user) }
let_it_be(:accessible_group) { create(:group, :private) }
diff --git a/spec/services/serverless/associate_domain_service_spec.rb b/spec/services/serverless/associate_domain_service_spec.rb
index 3d1a878bcf5..3b5231989bc 100644
--- a/spec/services/serverless/associate_domain_service_spec.rb
+++ b/spec/services/serverless/associate_domain_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Serverless::AssociateDomainService do
+RSpec.describe Serverless::AssociateDomainService do
subject { described_class.new(knative, pages_domain_id: pages_domain_id, creator: creator) }
let(:sdc) { create(:serverless_domain_cluster, pages_domain: create(:pages_domain, :instance_serverless)) }
diff --git a/spec/services/service_desk_settings/update_service_spec.rb b/spec/services/service_desk_settings/update_service_spec.rb
new file mode 100644
index 00000000000..8b920d536b4
--- /dev/null
+++ b/spec/services/service_desk_settings/update_service_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe ServiceDeskSettings::UpdateService do
+ describe '#execute' do
+ let_it_be(:settings) { create(:service_desk_setting, outgoing_name: 'original name') }
+ let_it_be(:user) { create(:user) }
+
+ context 'with valid params' do
+ let(:params) { { outgoing_name: 'some name', project_key: 'foo' } }
+
+ it 'updates service desk settings' do
+ result = described_class.new(settings.project, user, params).execute
+
+ expect(result[:status]).to eq :success
+ expect(settings.reload.outgoing_name).to eq 'some name'
+ expect(settings.reload.project_key).to eq 'foo'
+ end
+
+ context 'when service_desk_custom_address is disabled' do
+ before do
+ stub_feature_flags(service_desk_custom_address: false)
+ end
+
+ it 'ignores project_key parameter' do
+ result = described_class.new(settings.project, user, params).execute
+
+ expect(result[:status]).to eq :success
+ expect(settings.reload.project_key).to be_nil
+ end
+ end
+ end
+
+ context 'with invalid params' do
+ let(:params) { { outgoing_name: 'x' * 256 } }
+
+ it 'does not update service desk settings' do
+ result = described_class.new(settings.project, user, params).execute
+
+ expect(result[:status]).to eq :error
+ expect(result[:message]).to eq 'Outgoing name is too long (maximum is 255 characters)'
+ expect(settings.reload.outgoing_name).to eq 'original name'
+ end
+ end
+ end
+end
diff --git a/spec/services/service_response_spec.rb b/spec/services/service_response_spec.rb
index 2c944a63ebb..986b26e67d7 100644
--- a/spec/services/service_response_spec.rb
+++ b/spec/services/service_response_spec.rb
@@ -4,7 +4,7 @@ require 'fast_spec_helper'
ActiveSupport::Dependencies.autoload_paths << 'app/services'
-describe ServiceResponse do
+RSpec.describe ServiceResponse do
describe '.success' do
it 'creates a successful response without a message' do
expect(described_class.success).to be_success
diff --git a/spec/services/snippets/bulk_destroy_service_spec.rb b/spec/services/snippets/bulk_destroy_service_spec.rb
index 6e5623e575f..8a6250a8b45 100644
--- a/spec/services/snippets/bulk_destroy_service_spec.rb
+++ b/spec/services/snippets/bulk_destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Snippets::BulkDestroyService do
+RSpec.describe Snippets::BulkDestroyService do
let_it_be(:project) { create(:project) }
let(:user) { create(:user) }
let!(:personal_snippet) { create(:personal_snippet, :repository, author: user) }
diff --git a/spec/services/snippets/count_service_spec.rb b/spec/services/snippets/count_service_spec.rb
index 4137e65dcca..5ce637d0bac 100644
--- a/spec/services/snippets/count_service_spec.rb
+++ b/spec/services/snippets/count_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Snippets::CountService do
+RSpec.describe Snippets::CountService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public) }
diff --git a/spec/services/snippets/create_service_spec.rb b/spec/services/snippets/create_service_spec.rb
index fa8cbc87563..62eef00b67f 100644
--- a/spec/services/snippets/create_service_spec.rb
+++ b/spec/services/snippets/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Snippets::CreateService do
+RSpec.describe Snippets::CreateService do
describe '#execute' do
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:user, :admin) }
@@ -177,10 +177,8 @@ describe Snippets::CreateService do
end
it 'returns a generic error' do
- response = subject
-
- expect(response).to be_error
- expect(response.payload[:snippet].errors[:repository]).to eq ['Error creating the snippet']
+ expect(subject).to be_error
+ expect(snippet.errors[:repository]).to eq ['Error creating the snippet']
end
end
@@ -230,15 +228,15 @@ describe Snippets::CreateService do
end
end
- shared_examples 'when snippet_files param is present' do
+ shared_examples 'when snippet_actions param is present' do
let(:file_path) { 'snippet_file_path.rb' }
let(:content) { 'snippet_content' }
- let(:snippet_files) { [{ action: 'create', file_path: file_path, content: content }] }
+ let(:snippet_actions) { [{ action: 'create', file_path: file_path, content: content }] }
let(:base_opts) do
{
title: 'Test snippet',
visibility_level: Gitlab::VisibilityLevel::PRIVATE,
- snippet_files: snippet_files
+ snippet_actions: snippet_actions
}
end
@@ -250,7 +248,7 @@ describe Snippets::CreateService do
end
it 'commit the files to the repository' do
- subject
+ expect(subject).to be_success
blob = snippet.repository.blob_at('master', file_path)
@@ -261,28 +259,42 @@ describe Snippets::CreateService do
let(:extra_opts) { { content: 'foo', file_name: 'path' } }
it 'a validation error is raised' do
- response = subject
- snippet = response.payload[:snippet]
-
- expect(response).to be_error
+ expect(subject).to be_error
expect(snippet.errors.full_messages_for(:content)).to eq ['Content and snippet files cannot be used together']
expect(snippet.errors.full_messages_for(:file_name)).to eq ['File name and snippet files cannot be used together']
expect(snippet.repository.exists?).to be_falsey
end
end
- context 'when snippet_files param is invalid' do
- let(:snippet_files) { [{ action: 'invalid_action', file_path: 'snippet_file_path.rb', content: 'snippet_content' }] }
+ context 'when snippet_actions param is invalid' do
+ let(:snippet_actions) { [{ action: 'invalid_action', file_path: 'snippet_file_path.rb', content: 'snippet_content' }] }
it 'a validation error is raised' do
- response = subject
- snippet = response.payload[:snippet]
+ expect(subject).to be_error
+ expect(snippet.errors.full_messages_for(:snippet_actions)).to eq ['Snippet actions have invalid data']
+ expect(snippet.repository.exists?).to be_falsey
+ end
+ end
- expect(response).to be_error
- expect(snippet.errors.full_messages_for(:snippet_files)).to eq ['Snippet files have invalid data']
+ context 'when snippet_actions contain an action different from "create"' do
+ let(:snippet_actions) { [{ action: 'delete', file_path: 'snippet_file_path.rb' }] }
+
+ it 'a validation error is raised' do
+ expect(subject).to be_error
+ expect(snippet.errors.full_messages_for(:snippet_actions)).to eq ['Snippet actions have invalid data']
expect(snippet.repository.exists?).to be_falsey
end
end
+
+ context 'when "create" operation does not have file_path or is empty' do
+ let(:snippet_actions) { [{ action: 'create', content: content }, { action: 'create', content: content, file_path: '' }] }
+
+ it 'generates the file path for the files' do
+ expect(subject).to be_success
+ expect(snippet.repository.blob_at('master', 'snippetfile1.txt').data).to eq content
+ expect(snippet.repository.blob_at('master', 'snippetfile2.txt').data).to eq content
+ end
+ end
end
context 'when ProjectSnippet' do
@@ -299,7 +311,7 @@ describe Snippets::CreateService do
it_behaves_like 'an error service response when save fails'
it_behaves_like 'creates repository and files'
it_behaves_like 'after_save callback to store_mentions', ProjectSnippet
- it_behaves_like 'when snippet_files param is present'
+ it_behaves_like 'when snippet_actions param is present'
context 'when uploaded files are passed to the service' do
let(:extra_opts) { { files: ['foo'] } }
@@ -326,7 +338,7 @@ describe Snippets::CreateService do
it_behaves_like 'an error service response when save fails'
it_behaves_like 'creates repository and files'
it_behaves_like 'after_save callback to store_mentions', PersonalSnippet
- it_behaves_like 'when snippet_files param is present'
+ it_behaves_like 'when snippet_actions param is present'
context 'when the snippet description contains files' do
include FileMoverHelpers
diff --git a/spec/services/snippets/destroy_service_spec.rb b/spec/services/snippets/destroy_service_spec.rb
index 840dc11a740..e53d00b9ca1 100644
--- a/spec/services/snippets/destroy_service_spec.rb
+++ b/spec/services/snippets/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Snippets::DestroyService do
+RSpec.describe Snippets::DestroyService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:other_user) { create(:user) }
@@ -105,6 +105,26 @@ describe Snippets::DestroyService do
it_behaves_like 'a successful destroy'
it_behaves_like 'deletes the snippet repository'
+
+ context 'project statistics' do
+ before do
+ snippet.statistics.refresh!
+ end
+
+ it 'updates stats after deletion' do
+ expect(project.reload.statistics.snippets_size).not_to be_zero
+
+ subject
+
+ expect(project.reload.statistics.snippets_size).to be_zero
+ end
+
+ it 'schedules a namespace statistics update' do
+ expect(Namespaces::ScheduleAggregationWorker).to receive(:perform_async).with(project.namespace_id).once
+
+ subject
+ end
+ end
end
context 'when user is not able to admin_project_snippet' do
@@ -122,6 +142,12 @@ describe Snippets::DestroyService do
it_behaves_like 'a successful destroy'
it_behaves_like 'deletes the snippet repository'
+
+ it 'schedules a namespace statistics update' do
+ expect(Namespaces::ScheduleAggregationWorker).to receive(:perform_async).with(author.namespace_id)
+
+ subject
+ end
end
context 'when user is not able to admin_personal_snippet' do
diff --git a/spec/services/snippets/repository_validation_service_spec.rb b/spec/services/snippets/repository_validation_service_spec.rb
index 1c139d8c223..e2a0d0faa18 100644
--- a/spec/services/snippets/repository_validation_service_spec.rb
+++ b/spec/services/snippets/repository_validation_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Snippets::RepositoryValidationService do
+RSpec.describe Snippets::RepositoryValidationService do
describe '#execute' do
let_it_be(:user) { create(:user) }
let_it_be(:snippet) { create(:personal_snippet, :empty_repo, author: user) }
diff --git a/spec/services/snippets/update_service_spec.rb b/spec/services/snippets/update_service_spec.rb
index 7e6441ad2f9..66dddcc49de 100644
--- a/spec/services/snippets/update_service_spec.rb
+++ b/spec/services/snippets/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Snippets::UpdateService do
+RSpec.describe Snippets::UpdateService do
describe '#execute' do
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create :user, admin: true }
@@ -302,22 +302,22 @@ describe Snippets::UpdateService do
end
end
- shared_examples 'when snippet_files param is present' do
+ shared_examples 'when snippet_actions param is present' do
let(:file_path) { 'CHANGELOG' }
let(:content) { 'snippet_content' }
let(:new_title) { 'New title' }
- let(:snippet_files) { [{ action: 'update', previous_path: file_path, file_path: file_path, content: content }] }
+ let(:snippet_actions) { [{ action: 'update', previous_path: file_path, file_path: file_path, content: content }] }
let(:base_opts) do
{
title: new_title,
- snippet_files: snippet_files
+ snippet_actions: snippet_actions
}
end
it 'updates a snippet with the provided attributes' do
file_path = 'foo'
- snippet_files[0][:action] = 'move'
- snippet_files[0][:file_path] = file_path
+ snippet_actions[0][:action] = 'move'
+ snippet_actions[0][:file_path] = file_path
response = subject
snippet = response.payload[:snippet]
@@ -328,7 +328,7 @@ describe Snippets::UpdateService do
expect(snippet.content).to eq(content)
end
- it 'commit the files to the repository' do
+ it 'commits the files to the repository' do
subject
blob = snippet.repository.blob_at('master', file_path)
@@ -349,15 +349,27 @@ describe Snippets::UpdateService do
end
end
- context 'when snippet_files param is invalid' do
- let(:snippet_files) { [{ action: 'invalid_action' }] }
+ context 'when snippet_file content is not present' do
+ let(:snippet_actions) { [{ action: :move, previous_path: file_path, file_path: 'new_file_path' }] }
+
+ it 'does not update snippet content' do
+ content = snippet.content
+
+ expect(subject).to be_success
+
+ expect(snippet.reload.content).to eq content
+ end
+ end
+
+ context 'when snippet_actions param is invalid' do
+ let(:snippet_actions) { [{ action: 'invalid_action' }] }
it 'raises a validation error' do
response = subject
snippet = response.payload[:snippet]
expect(response).to be_error
- expect(snippet.errors.full_messages_for(:snippet_files)).to eq ['Snippet files have invalid data']
+ expect(snippet.errors.full_messages_for(:snippet_actions)).to eq ['Snippet actions have invalid data']
end
end
@@ -376,6 +388,226 @@ describe Snippets::UpdateService do
expect(snippet.content).to eq(content)
end
end
+
+ context 'commit actions' do
+ let(:new_path) { 'created_new_file' }
+ let(:base_opts) { { snippet_actions: snippet_actions } }
+
+ shared_examples 'returns an error' do |error_msg|
+ specify do
+ response = subject
+
+ expect(response).to be_error
+ expect(response.message).to eq error_msg
+ end
+ end
+
+ context 'update action' do
+ let(:snippet_actions) { [{ action: :update, file_path: file_path, content: content }] }
+
+ it 'updates the file content' do
+ expect(subject).to be_success
+
+ blob = blob(file_path)
+
+ expect(blob.data).to eq content
+ end
+
+ context 'when previous_path is present' do
+ let(:snippet_actions) { [{ action: :update, previous_path: file_path, file_path: file_path, content: content }] }
+
+ it 'updates the file content' do
+ expect(subject).to be_success
+
+ blob = blob(file_path)
+
+ expect(blob.data).to eq content
+ end
+ end
+
+ context 'when content is not present' do
+ let(:snippet_actions) { [{ action: :update, file_path: file_path }] }
+
+ it_behaves_like 'returns an error', 'Snippet actions have invalid data'
+ end
+
+ context 'when file_path does not exist' do
+ let(:snippet_actions) { [{ action: :update, file_path: 'makeup_name', content: content }] }
+
+ it_behaves_like 'returns an error', 'Repository Error updating the snippet'
+ end
+ end
+
+ context 'move action' do
+ context 'when file_path and previous_path are the same' do
+ let(:snippet_actions) { [{ action: :move, previous_path: file_path, file_path: file_path }] }
+
+ it_behaves_like 'returns an error', 'Snippet actions have invalid data'
+ end
+
+ context 'when file_path and previous_path are different' do
+ let(:snippet_actions) { [{ action: :move, previous_path: file_path, file_path: new_path }] }
+
+ it 'renames the file' do
+ old_blob = blob(file_path)
+
+ expect(subject).to be_success
+
+ blob = blob(new_path)
+
+ expect(blob).to be_present
+ expect(blob.data).to eq old_blob.data
+ end
+ end
+
+ context 'when previous_path does not exist' do
+ let(:snippet_actions) { [{ action: :move, previous_path: 'makeup_name', file_path: new_path }] }
+
+ it_behaves_like 'returns an error', 'Repository Error updating the snippet'
+ end
+
+ context 'when user wants to rename the file and update content' do
+ let(:snippet_actions) { [{ action: :move, previous_path: file_path, file_path: new_path, content: content }] }
+
+ it 'performs both operations' do
+ expect(subject).to be_success
+
+ blob = blob(new_path)
+
+ expect(blob).to be_present
+ expect(blob.data).to eq content
+ end
+ end
+ end
+
+ context 'delete action' do
+ let(:snippet_actions) { [{ action: :delete, file_path: file_path }] }
+
+ shared_examples 'deletes the file' do
+ specify do
+ old_blob = blob(file_path)
+ expect(old_blob).to be_present
+
+ expect(subject).to be_success
+ expect(blob(file_path)).to be_nil
+ end
+ end
+
+ it_behaves_like 'deletes the file'
+
+ context 'when previous_path is present and same as file_path' do
+ let(:snippet_actions) { [{ action: :delete, previous_path: file_path, file_path: file_path }] }
+
+ it_behaves_like 'deletes the file'
+ end
+
+ context 'when previous_path is present and is different from file_path' do
+ let(:snippet_actions) { [{ action: :delete, previous_path: 'foo', file_path: file_path }] }
+
+ it_behaves_like 'deletes the file'
+ end
+
+ context 'when content is present' do
+ let(:snippet_actions) { [{ action: :delete, file_path: file_path, content: 'foo' }] }
+
+ it_behaves_like 'deletes the file'
+ end
+
+ context 'when file_path does not exist' do
+ let(:snippet_actions) { [{ action: :delete, file_path: 'makeup_name' }] }
+
+ it_behaves_like 'returns an error', 'Repository Error updating the snippet'
+ end
+ end
+
+ context 'create action' do
+ let(:snippet_actions) { [{ action: :create, file_path: new_path, content: content }] }
+
+ it 'creates the file' do
+ expect(subject).to be_success
+
+ blob = blob(new_path)
+ expect(blob).to be_present
+ expect(blob.data).to eq content
+ end
+
+ context 'when content is not present' do
+ let(:snippet_actions) { [{ action: :create, file_path: new_path }] }
+
+ it_behaves_like 'returns an error', 'Snippet actions have invalid data'
+ end
+
+ context 'when file_path is not present or empty' do
+ let(:snippet_actions) { [{ action: :create, content: content }, { action: :create, file_path: '', content: content }] }
+
+ it 'generates the file path for the files' do
+ expect(blob('snippetfile1.txt')).to be_nil
+ expect(blob('snippetfile2.txt')).to be_nil
+
+ expect(subject).to be_success
+
+ expect(blob('snippetfile1.txt').data).to eq content
+ expect(blob('snippetfile2.txt').data).to eq content
+ end
+ end
+
+ context 'when file_path already exists in the repository' do
+ let(:snippet_actions) { [{ action: :create, file_path: file_path, content: content }] }
+
+ it_behaves_like 'returns an error', 'Repository Error updating the snippet'
+ end
+
+ context 'when previous_path is present' do
+ let(:snippet_actions) { [{ action: :create, previous_path: 'foo', file_path: new_path, content: content }] }
+
+ it 'creates the file' do
+ expect(subject).to be_success
+
+ blob = blob(new_path)
+ expect(blob).to be_present
+ expect(blob.data).to eq content
+ end
+ end
+ end
+
+ context 'combination of actions' do
+ let(:delete_file_path) { 'CHANGELOG' }
+ let(:create_file_path) { 'created_new_file' }
+ let(:update_file_path) { 'LICENSE' }
+ let(:move_previous_path) { 'VERSION' }
+ let(:move_file_path) { 'VERSION_new' }
+
+ let(:snippet_actions) do
+ [
+ { action: :create, file_path: create_file_path, content: content },
+ { action: :update, file_path: update_file_path, content: content },
+ { action: :delete, file_path: delete_file_path },
+ { action: :move, previous_path: move_previous_path, file_path: move_file_path, content: content }
+ ]
+ end
+
+ it 'performs all operations' do
+ expect(subject).to be_success
+
+ expect(blob(delete_file_path)).to be_nil
+
+ created_blob = blob(create_file_path)
+ expect(created_blob.data).to eq content
+
+ updated_blob = blob(update_file_path)
+ expect(updated_blob.data).to eq content
+
+ expect(blob(move_previous_path)).to be_nil
+
+ moved_blob = blob(move_file_path)
+ expect(moved_blob.data).to eq content
+ end
+ end
+
+ def blob(path)
+ snippet.repository.blob_at('master', path)
+ end
+ end
end
shared_examples 'only file_name is present' do
@@ -446,7 +678,7 @@ describe Snippets::UpdateService do
it_behaves_like 'updates repository content'
it_behaves_like 'commit operation fails'
it_behaves_like 'committable attributes'
- it_behaves_like 'when snippet_files param is present'
+ it_behaves_like 'when snippet_actions param is present'
it_behaves_like 'only file_name is present'
it_behaves_like 'only content is present'
it_behaves_like 'snippets spam check is performed' do
@@ -473,7 +705,7 @@ describe Snippets::UpdateService do
it_behaves_like 'updates repository content'
it_behaves_like 'commit operation fails'
it_behaves_like 'committable attributes'
- it_behaves_like 'when snippet_files param is present'
+ it_behaves_like 'when snippet_actions param is present'
it_behaves_like 'only file_name is present'
it_behaves_like 'only content is present'
it_behaves_like 'snippets spam check is performed' do
diff --git a/spec/services/snippets/update_statistics_service_spec.rb b/spec/services/snippets/update_statistics_service_spec.rb
new file mode 100644
index 00000000000..27ae054676a
--- /dev/null
+++ b/spec/services/snippets/update_statistics_service_spec.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Snippets::UpdateStatisticsService do
+ describe '#execute' do
+ subject { described_class.new(snippet).execute }
+
+ shared_examples 'updates statistics' do
+ it 'returns a successful response' do
+ expect(subject).to be_success
+ end
+
+ it 'expires statistics cache' do
+ expect(snippet.repository).to receive(:expire_statistics_caches)
+
+ subject
+ end
+
+ context 'when snippet statistics does not exist' do
+ it 'creates snippet statistics' do
+ snippet.statistics.delete
+ snippet.reload
+
+ expect do
+ subject
+ end.to change(SnippetStatistics, :count).by(1)
+
+ expect(snippet.statistics.commit_count).not_to be_zero
+ expect(snippet.statistics.file_count).not_to be_zero
+ expect(snippet.statistics.repository_size).not_to be_zero
+ end
+ end
+
+ context 'when snippet statistics exists' do
+ it 'updates snippet statistics' do
+ expect(snippet.statistics.commit_count).to be_zero
+ expect(snippet.statistics.file_count).to be_zero
+ expect(snippet.statistics.repository_size).to be_zero
+
+ subject
+
+ expect(snippet.statistics.commit_count).not_to be_zero
+ expect(snippet.statistics.file_count).not_to be_zero
+ expect(snippet.statistics.repository_size).not_to be_zero
+ end
+ end
+
+ context 'when snippet does not have a repository' do
+ it 'returns an error response' do
+ expect(snippet).to receive(:repository_exists?).and_return(false)
+
+ expect(subject).to be_error
+ end
+ end
+
+ it 'schedules a namespace storage statistics update' do
+ expect(Namespaces::ScheduleAggregationWorker)
+ .to receive(:perform_async).once
+
+ subject
+ end
+ end
+
+ context 'with PersonalSnippet' do
+ let!(:snippet) { create(:personal_snippet, :repository) }
+
+ it_behaves_like 'updates statistics'
+ end
+
+ context 'with ProjectSnippet' do
+ let!(:snippet) { create(:project_snippet, :repository) }
+ let(:project_statistics) { snippet.project.statistics }
+
+ it_behaves_like 'updates statistics'
+
+ it 'updates projects statistics "snippets_size"' do
+ expect(project_statistics.snippets_size).to be_zero
+
+ subject
+
+ expect(snippet.reload.statistics.repository_size).to eq project_statistics.reload.snippets_size
+ end
+ end
+ end
+end
diff --git a/spec/services/spam/akismet_service_spec.rb b/spec/services/spam/akismet_service_spec.rb
index 413b43d0156..f75b0216b78 100644
--- a/spec/services/spam/akismet_service_spec.rb
+++ b/spec/services/spam/akismet_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Spam::AkismetService do
+RSpec.describe Spam::AkismetService do
let(:fake_akismet_client) { double(:akismet_client) }
let_it_be(:text) { "Would you like to buy some tinned meat product?" }
diff --git a/spec/services/spam/ham_service_spec.rb b/spec/services/spam/ham_service_spec.rb
index 9848f48def2..c947de6cf92 100644
--- a/spec/services/spam/ham_service_spec.rb
+++ b/spec/services/spam/ham_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Spam::HamService do
+RSpec.describe Spam::HamService do
let_it_be(:user) { create(:user) }
let!(:spam_log) { create(:spam_log, user: user, submitted_as_ham: false) }
let(:fake_akismet_service) { double(:akismet_service) }
diff --git a/spec/services/spam/mark_as_spam_service_spec.rb b/spec/services/spam/mark_as_spam_service_spec.rb
index 9978005279a..308a66c3a48 100644
--- a/spec/services/spam/mark_as_spam_service_spec.rb
+++ b/spec/services/spam/mark_as_spam_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Spam::MarkAsSpamService do
+RSpec.describe Spam::MarkAsSpamService do
let(:user_agent_detail) { build(:user_agent_detail) }
let(:spammable) { build(:issue, user_agent_detail: user_agent_detail) }
let(:fake_akismet_service) { double(:akismet_service, submit_spam: true) }
diff --git a/spec/services/spam/spam_action_service_spec.rb b/spec/services/spam/spam_action_service_spec.rb
index 7b6b65c82b1..abb8e49ec52 100644
--- a/spec/services/spam/spam_action_service_spec.rb
+++ b/spec/services/spam/spam_action_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Spam::SpamActionService do
+RSpec.describe Spam::SpamActionService do
include_context 'includes Spam constants'
let(:fake_ip) { '1.2.3.4' }
diff --git a/spec/services/spam/spam_verdict_service_spec.rb b/spec/services/spam/spam_verdict_service_spec.rb
index f6d9cd96da5..d775e1bdfb5 100644
--- a/spec/services/spam/spam_verdict_service_spec.rb
+++ b/spec/services/spam/spam_verdict_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Spam::SpamVerdictService do
+RSpec.describe Spam::SpamVerdictService do
include_context 'includes Spam constants'
let(:fake_ip) { '1.2.3.4' }
@@ -27,7 +27,7 @@ describe Spam::SpamVerdictService do
before do
allow(service).to receive(:akismet_verdict).and_return(nil)
- allow(service).to receive(:spam_verdict_verdict).and_return(nil)
+ allow(service).to receive(:external_verdict).and_return(nil)
end
context 'if all services return nil' do
@@ -62,7 +62,7 @@ describe Spam::SpamVerdictService do
context 'and they are supported' do
before do
allow(service).to receive(:akismet_verdict).and_return(DISALLOW)
- allow(service).to receive(:spam_verdict).and_return(BLOCK_USER)
+ allow(service).to receive(:external_verdict).and_return(BLOCK_USER)
end
it 'renders the more restrictive verdict' do
@@ -73,18 +73,7 @@ describe Spam::SpamVerdictService do
context 'and one is supported' do
before do
allow(service).to receive(:akismet_verdict).and_return('nonsense')
- allow(service).to receive(:spam_verdict).and_return(BLOCK_USER)
- end
-
- it 'renders the more restrictive verdict' do
- expect(subject).to eq BLOCK_USER
- end
- end
-
- context 'and one is supported' do
- before do
- allow(service).to receive(:akismet_verdict).and_return('nonsense')
- allow(service).to receive(:spam_verdict).and_return(BLOCK_USER)
+ allow(service).to receive(:external_verdict).and_return(BLOCK_USER)
end
it 'renders the more restrictive verdict' do
@@ -95,7 +84,7 @@ describe Spam::SpamVerdictService do
context 'and none are supported' do
before do
allow(service).to receive(:akismet_verdict).and_return('nonsense')
- allow(service).to receive(:spam_verdict).and_return('rubbish')
+ allow(service).to receive(:external_verdict).and_return('rubbish')
end
it 'renders the more restrictive verdict' do
@@ -160,8 +149,8 @@ describe Spam::SpamVerdictService do
end
end
- describe '#spam_verdict' do
- subject { service.send(:spam_verdict) }
+ describe '#external_verdict' do
+ subject { service.send(:external_verdict) }
context 'if a Spam Check endpoint enabled and set to a URL' do
let(:spam_check_body) { {} }
@@ -192,8 +181,8 @@ describe Spam::SpamVerdictService do
context 'the verdict is an unexpected string' do
let(:verdict) { 'this is fine' }
- it 'returns nil' do
- expect(subject).to be_nil
+ it 'returns the string' do
+ expect(subject).to eq verdict
end
end
@@ -209,7 +198,7 @@ describe Spam::SpamVerdictService do
let(:verdict) { '' }
it 'returns nil' do
- expect(subject).to be_nil
+ expect(subject).to eq verdict
end
end
diff --git a/spec/services/submit_usage_ping_service_spec.rb b/spec/services/submit_usage_ping_service_spec.rb
index 981ea0dbec1..4885ef99c13 100644
--- a/spec/services/submit_usage_ping_service_spec.rb
+++ b/spec/services/submit_usage_ping_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SubmitUsagePingService do
+RSpec.describe SubmitUsagePingService do
include StubRequests
include UsageDataHelpers
diff --git a/spec/services/submodules/update_service_spec.rb b/spec/services/submodules/update_service_spec.rb
index 47b31d4bcbf..e7f92d5ba28 100644
--- a/spec/services/submodules/update_service_spec.rb
+++ b/spec/services/submodules/update_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Submodules::UpdateService do
+RSpec.describe Submodules::UpdateService do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:user) { create(:user, :commit_email) }
diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb
index 678e2129181..aa9caf35987 100644
--- a/spec/services/suggestions/apply_service_spec.rb
+++ b/spec/services/suggestions/apply_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Suggestions::ApplyService do
+RSpec.describe Suggestions::ApplyService do
include ProjectForksHelper
def build_position(**optional_args)
diff --git a/spec/services/suggestions/create_service_spec.rb b/spec/services/suggestions/create_service_spec.rb
index d95f9e3349b..54e7c5cc127 100644
--- a/spec/services/suggestions/create_service_spec.rb
+++ b/spec/services/suggestions/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Suggestions::CreateService do
+RSpec.describe Suggestions::CreateService do
let(:project_with_repo) { create(:project, :repository) }
let(:merge_request) do
create(:merge_request, source_project: project_with_repo,
diff --git a/spec/services/suggestions/outdate_service_spec.rb b/spec/services/suggestions/outdate_service_spec.rb
index bcc627013d8..e8891f88548 100644
--- a/spec/services/suggestions/outdate_service_spec.rb
+++ b/spec/services/suggestions/outdate_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Suggestions::OutdateService do
+RSpec.describe Suggestions::OutdateService do
describe '#execute' do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.target_project }
diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb
index d72e5cc2b16..bdc40a92e91 100644
--- a/spec/services/system_hooks_service_spec.rb
+++ b/spec/services/system_hooks_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SystemHooksService do
+RSpec.describe SystemHooksService do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:project_member) { create(:project_member) }
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 66f9b5d092f..58fa772fefb 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SystemNoteService do
+RSpec.describe SystemNoteService do
include Gitlab::Routing
include RepoHelpers
include AssetsHelpers
@@ -661,4 +661,48 @@ describe SystemNoteService do
described_class.design_discussion_added(discussion_note)
end
end
+
+ describe '.approve_mr' do
+ it 'calls MergeRequestsService' do
+ expect_next_instance_of(::SystemNotes::MergeRequestsService) do |service|
+ expect(service).to receive(:approve_mr)
+ end
+
+ described_class.approve_mr(noteable, author)
+ end
+ end
+
+ describe '.unapprove_mr' do
+ it 'calls MergeRequestsService' do
+ expect_next_instance_of(::SystemNotes::MergeRequestsService) do |service|
+ expect(service).to receive(:unapprove_mr)
+ end
+
+ described_class.unapprove_mr(noteable, author)
+ end
+ end
+
+ describe '.change_alert_status' do
+ let(:alert) { build(:alert_management_alert) }
+
+ it 'calls AlertManagementService' do
+ expect_next_instance_of(SystemNotes::AlertManagementService) do |service|
+ expect(service).to receive(:change_alert_status).with(alert)
+ end
+
+ described_class.change_alert_status(alert, author)
+ end
+ end
+
+ describe '.new_alert_issue' do
+ let(:alert) { build(:alert_management_alert, :with_issue) }
+
+ it 'calls AlertManagementService' do
+ expect_next_instance_of(SystemNotes::AlertManagementService) do |service|
+ expect(service).to receive(:new_alert_issue).with(alert, alert.issue)
+ end
+
+ described_class.new_alert_issue(alert, alert.issue, author)
+ end
+ end
end
diff --git a/spec/services/system_notes/alert_management_service_spec.rb b/spec/services/system_notes/alert_management_service_spec.rb
new file mode 100644
index 00000000000..403763d5fd9
--- /dev/null
+++ b/spec/services/system_notes/alert_management_service_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::SystemNotes::AlertManagementService do
+ let_it_be(:author) { create(:user) }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:noteable) { create(:alert_management_alert, :with_issue, :acknowledged, project: project) }
+
+ describe '#change_alert_status' do
+ subject { described_class.new(noteable: noteable, project: project, author: author).change_alert_status(noteable) }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'status' }
+ end
+
+ it 'has the appropriate message' do
+ expect(subject.note).to eq("changed the status to **Acknowledged**")
+ end
+ end
+
+ describe '#new_alert_issue' do
+ let_it_be(:issue) { noteable.issue }
+
+ subject { described_class.new(noteable: noteable, project: project, author: author).new_alert_issue(noteable, issue) }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'alert_issue_added' }
+ end
+
+ it 'has the appropriate message' do
+ expect(subject.note).to eq("created issue #{issue.to_reference(project)} for this alert")
+ end
+ end
+end
diff --git a/spec/services/system_notes/base_service_spec.rb b/spec/services/system_notes/base_service_spec.rb
index 96788b05829..efb165f8e4c 100644
--- a/spec/services/system_notes/base_service_spec.rb
+++ b/spec/services/system_notes/base_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SystemNotes::BaseService do
+RSpec.describe SystemNotes::BaseService do
let(:noteable) { double }
let(:project) { double }
let(:author) { double }
diff --git a/spec/services/system_notes/commit_service_spec.rb b/spec/services/system_notes/commit_service_spec.rb
index 5839a17e4a0..bd6b3ec953a 100644
--- a/spec/services/system_notes/commit_service_spec.rb
+++ b/spec/services/system_notes/commit_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SystemNotes::CommitService do
+RSpec.describe SystemNotes::CommitService do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :repository, group: group) }
let_it_be(:author) { create(:user) }
diff --git a/spec/services/system_notes/design_management_service_spec.rb b/spec/services/system_notes/design_management_service_spec.rb
index 08511e62341..6267ad2aaad 100644
--- a/spec/services/system_notes/design_management_service_spec.rb
+++ b/spec/services/system_notes/design_management_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SystemNotes::DesignManagementService do
+RSpec.describe SystemNotes::DesignManagementService do
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project) }
diff --git a/spec/services/system_notes/issuables_service_spec.rb b/spec/services/system_notes/issuables_service_spec.rb
index c3b3c877583..1b5b26d90da 100644
--- a/spec/services/system_notes/issuables_service_spec.rb
+++ b/spec/services/system_notes/issuables_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::SystemNotes::IssuablesService do
+RSpec.describe ::SystemNotes::IssuablesService do
include ProjectForksHelper
let_it_be(:group) { create(:group) }
@@ -161,7 +161,9 @@ describe ::SystemNotes::IssuablesService do
let(:status) { 'reopened' }
let(:source) { nil }
- it { is_expected.to be_nil }
+ it 'does not change note count' do
+ expect { subject }.not_to change { Note.count }
+ end
end
context 'with status reopened' do
@@ -660,25 +662,67 @@ describe ::SystemNotes::IssuablesService do
describe '#close_after_error_tracking_resolve' do
subject { service.close_after_error_tracking_resolve }
- it_behaves_like 'a system note' do
- let(:action) { 'closed' }
+ context 'when state tracking is enabled' do
+ before do
+ stub_feature_flags(track_resource_state_change_events: true)
+ end
+
+ it 'creates the expected state event' do
+ subject
+
+ event = ResourceStateEvent.last
+
+ expect(event.close_after_error_tracking_resolve).to eq(true)
+ expect(event.state).to eq('closed')
+ end
end
- it 'creates the expected system note' do
- expect(subject.note)
+ context 'when state tracking is disabled' do
+ before do
+ stub_feature_flags(track_resource_state_change_events: false)
+ end
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'closed' }
+ end
+
+ it 'creates the expected system note' do
+ expect(subject.note)
.to eq('resolved the corresponding error and closed the issue.')
+ end
end
end
describe '#auto_resolve_prometheus_alert' do
subject { service.auto_resolve_prometheus_alert }
- it_behaves_like 'a system note' do
- let(:action) { 'closed' }
+ context 'when state tracking is enabled' do
+ before do
+ stub_feature_flags(track_resource_state_change_events: true)
+ end
+
+ it 'creates the expected state event' do
+ subject
+
+ event = ResourceStateEvent.last
+
+ expect(event.close_auto_resolve_prometheus_alert).to eq(true)
+ expect(event.state).to eq('closed')
+ end
end
- it 'creates the expected system note' do
- expect(subject.note).to eq('automatically closed this issue because the alert resolved.')
+ context 'when state tracking is disabled' do
+ before do
+ stub_feature_flags(track_resource_state_change_events: false)
+ end
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'closed' }
+ end
+
+ it 'creates the expected system note' do
+ expect(subject.note).to eq('automatically closed this issue because the alert resolved.')
+ end
end
end
end
diff --git a/spec/services/system_notes/merge_requests_service_spec.rb b/spec/services/system_notes/merge_requests_service_spec.rb
index 13d6367a585..067e1cef64d 100644
--- a/spec/services/system_notes/merge_requests_service_spec.rb
+++ b/spec/services/system_notes/merge_requests_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::SystemNotes::MergeRequestsService do
+RSpec.describe ::SystemNotes::MergeRequestsService do
include Gitlab::Routing
let_it_be(:group) { create(:group) }
@@ -52,8 +52,8 @@ describe ::SystemNotes::MergeRequestsService do
end
describe '.handle_merge_request_wip' do
- context 'adding wip note' do
- let(:noteable) { create(:merge_request, source_project: project, title: 'WIP Lorem ipsum') }
+ context 'adding draft note' do
+ let(:noteable) { create(:merge_request, source_project: project, title: 'Draft: Lorem ipsum') }
subject { service.handle_merge_request_wip }
@@ -261,4 +261,18 @@ describe ::SystemNotes::MergeRequestsService do
expect(subject.commit_id).to eq(commit_sha)
end
end
+
+ describe '#approve_mr' do
+ subject { described_class.new(noteable: noteable, project: project, author: author).approve_mr }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'approved' }
+ end
+
+ context 'when merge request approved' do
+ it 'sets the note text' do
+ expect(subject.note).to eq "approved this merge request"
+ end
+ end
+ end
end
diff --git a/spec/services/system_notes/time_tracking_service_spec.rb b/spec/services/system_notes/time_tracking_service_spec.rb
index 7e3e6a75cdf..f671e66cdcd 100644
--- a/spec/services/system_notes/time_tracking_service_spec.rb
+++ b/spec/services/system_notes/time_tracking_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::SystemNotes::TimeTrackingService do
+RSpec.describe ::SystemNotes::TimeTrackingService do
let_it_be(:author) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/services/system_notes/zoom_service_spec.rb b/spec/services/system_notes/zoom_service_spec.rb
index 435cdb5748e..986324c9664 100644
--- a/spec/services/system_notes/zoom_service_spec.rb
+++ b/spec/services/system_notes/zoom_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::SystemNotes::ZoomService do
+RSpec.describe ::SystemNotes::ZoomService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:author) { create(:user) }
diff --git a/spec/services/tags/create_service_spec.rb b/spec/services/tags/create_service_spec.rb
index e505960d3c7..b1c6623308e 100644
--- a/spec/services/tags/create_service_spec.rb
+++ b/spec/services/tags/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Tags::CreateService do
+RSpec.describe Tags::CreateService do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:user) { create(:user) }
diff --git a/spec/services/tags/destroy_service_spec.rb b/spec/services/tags/destroy_service_spec.rb
index b46bd77eafe..6160f337552 100644
--- a/spec/services/tags/destroy_service_spec.rb
+++ b/spec/services/tags/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Tags::DestroyService do
+RSpec.describe Tags::DestroyService do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:user) { create(:user) }
@@ -11,6 +11,10 @@ describe Tags::DestroyService do
describe '#execute' do
subject { service.execute(tag_name) }
+ before do
+ allow(Ci::RefDeleteUnlockArtifactsWorker).to receive(:perform_async)
+ end
+
it 'removes the tag' do
expect(repository).to receive(:before_remove_tag)
expect(service).to receive(:success)
@@ -18,6 +22,12 @@ describe Tags::DestroyService do
service.execute('v1.1.0')
end
+ it 'calls the RefDeleteUnlockArtifactsWorker' do
+ expect(Ci::RefDeleteUnlockArtifactsWorker).to receive(:perform_async).with(project.id, user.id, 'refs/tags/v1.1.0')
+
+ service.execute('v1.1.0')
+ end
+
context 'when there is an associated release on the tag' do
let(:tag) { repository.tags.first }
let(:tag_name) { tag.name }
diff --git a/spec/services/task_list_toggle_service_spec.rb b/spec/services/task_list_toggle_service_spec.rb
index 82a5446dcb8..276f2ae435e 100644
--- a/spec/services/task_list_toggle_service_spec.rb
+++ b/spec/services/task_list_toggle_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TaskListToggleService do
+RSpec.describe TaskListToggleService do
let(:markdown) do
<<-EOT.strip_heredoc
* [ ] Task 1
diff --git a/spec/services/terraform/remote_state_handler_spec.rb b/spec/services/terraform/remote_state_handler_spec.rb
index f4e1831b2e8..c47367feb14 100644
--- a/spec/services/terraform/remote_state_handler_spec.rb
+++ b/spec/services/terraform/remote_state_handler_spec.rb
@@ -2,9 +2,12 @@
require 'spec_helper'
-describe Terraform::RemoteStateHandler do
+RSpec.describe Terraform::RemoteStateHandler do
let_it_be(:project) { create(:project) }
- let_it_be(:user) { create(:user) }
+ let_it_be(:developer) { create(:user, developer_projects: [project]) }
+ let_it_be(:maintainer) { create(:user, maintainer_projects: [project]) }
+
+ let_it_be(:user) { maintainer }
describe '#find_with_lock' do
context 'without a state name' do
@@ -34,33 +37,6 @@ describe Terraform::RemoteStateHandler do
end
end
- describe '#create_or_find!' do
- it 'requires passing a state name' do
- handler = described_class.new(project, user)
-
- expect { handler.create_or_find! }.to raise_error(ArgumentError)
- end
-
- it 'allows to create states with same name in different projects' do
- project_b = create(:project)
-
- state_a = described_class.new(project, user, name: 'my-state').create_or_find!
- state_b = described_class.new(project_b, user, name: 'my-state').create_or_find!
-
- expect(state_a).to be_persisted
- expect(state_b).to be_persisted
- expect(state_a.id).not_to eq state_b.id
- end
-
- it 'loads the same state upon subsequent call in the project scope' do
- state_a = described_class.new(project, user, name: 'my-state').create_or_find!
- state_b = described_class.new(project, user, name: 'my-state').create_or_find!
-
- expect(state_a).to be_persisted
- expect(state_a.id).to eq state_b.id
- end
- end
-
context 'when state locking is not being used' do
subject { described_class.new(project, user, name: 'my-state') }
@@ -74,7 +50,7 @@ describe Terraform::RemoteStateHandler do
end
it 'returns the state object itself' do
- state = subject.create_or_find!
+ state = subject.handle_with_lock
expect(state.name).to eq 'my-state'
end
@@ -89,10 +65,9 @@ describe Terraform::RemoteStateHandler do
context 'when using locking' do
describe '#handle_with_lock' do
- it 'handles a locked state using exclusive read lock' do
- handler = described_class
- .new(project, user, name: 'new-state', lock_id: 'abc-abc')
+ subject(:handler) { described_class.new(project, user, name: 'new-state', lock_id: 'abc-abc') }
+ it 'handles a locked state using exclusive read lock' do
handler.lock!
state = handler.handle_with_lock do |state|
@@ -101,20 +76,35 @@ describe Terraform::RemoteStateHandler do
expect(state.name).to eq 'new-name'
end
- end
- it 'raises exception if lock has not been acquired before' do
- handler = described_class
- .new(project, user, name: 'new-state', lock_id: 'abc-abc')
+ it 'raises exception if lock has not been acquired before' do
+ expect { handler.handle_with_lock }
+ .to raise_error(described_class::StateLockedError)
+ end
+
+ context 'user does not have permission to modify state' do
+ let(:user) { developer }
- expect { handler.handle_with_lock }
- .to raise_error(described_class::StateLockedError)
+ it 'raises an exception' do
+ expect { handler.handle_with_lock }
+ .to raise_error(described_class::UnauthorizedError)
+ end
+ end
end
describe '#lock!' do
- it 'allows to lock state if it does not exist yet' do
- handler = described_class.new(project, user, name: 'new-state', lock_id: 'abc-abc')
+ let(:lock_id) { 'abc-abc' }
+
+ subject(:handler) do
+ described_class.new(
+ project,
+ user,
+ name: 'new-state',
+ lock_id: lock_id
+ )
+ end
+ it 'allows to lock state if it does not exist yet' do
state = handler.lock!
expect(state).to be_persisted
@@ -122,22 +112,61 @@ describe Terraform::RemoteStateHandler do
end
it 'allows to lock state if it exists and is not locked' do
- state = described_class.new(project, user, name: 'new-state').create_or_find!
- handler = described_class.new(project, user, name: 'new-state', lock_id: 'abc-abc')
+ state = create(:terraform_state, project: project, name: 'new-state')
handler.lock!
- expect(state.reload.lock_xid).to eq 'abc-abc'
+ expect(state.reload.lock_xid).to eq lock_id
expect(state).to be_locked
end
it 'raises an exception when trying to unlocked state locked by someone else' do
- described_class.new(project, user, name: 'new-state', lock_id: 'abc-abc').lock!
-
- handler = described_class.new(project, user, name: 'new-state', lock_id: '12a-23f')
+ described_class.new(project, user, name: 'new-state', lock_id: '12a-23f').lock!
expect { handler.lock! }.to raise_error(described_class::StateLockedError)
end
end
+
+ describe '#unlock!' do
+ let(:lock_id) { 'abc-abc' }
+
+ subject(:handler) do
+ described_class.new(
+ project,
+ user,
+ name: 'new-state',
+ lock_id: lock_id
+ )
+ end
+
+ before do
+ create(:terraform_state, :locked, project: project, name: 'new-state', lock_xid: 'abc-abc')
+ end
+
+ it 'unlocks the state' do
+ state = handler.unlock!
+
+ expect(state.lock_xid).to be_nil
+ end
+
+ context 'with no lock ID (force-unlock)' do
+ let(:lock_id) { }
+
+ it 'unlocks the state' do
+ state = handler.unlock!
+
+ expect(state.lock_xid).to be_nil
+ end
+ end
+
+ context 'with different lock ID' do
+ let(:lock_id) { 'other' }
+
+ it 'raises an exception' do
+ expect { handler.unlock! }
+ .to raise_error(described_class::StateLockedError)
+ end
+ end
+ end
end
end
diff --git a/spec/services/test_hooks/project_service_spec.rb b/spec/services/test_hooks/project_service_spec.rb
index 3c5bc0d85f2..e4cc3a2d652 100644
--- a/spec/services/test_hooks/project_service_spec.rb
+++ b/spec/services/test_hooks/project_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TestHooks::ProjectService do
+RSpec.describe TestHooks::ProjectService do
let(:current_user) { create(:user) }
describe '#execute' do
diff --git a/spec/services/test_hooks/system_service_spec.rb b/spec/services/test_hooks/system_service_spec.rb
index 8a86b14a2a1..34dd2173b09 100644
--- a/spec/services/test_hooks/system_service_spec.rb
+++ b/spec/services/test_hooks/system_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TestHooks::SystemService do
+RSpec.describe TestHooks::SystemService do
let(:current_user) { create(:user) }
describe '#execute' do
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index f6e1608acbe..b187025eb11 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TodoService do
+RSpec.describe TodoService do
let(:author) { create(:user) }
let(:assignee) { create(:user) }
let(:non_member) { create(:user) }
diff --git a/spec/services/todos/destroy/confidential_issue_service_spec.rb b/spec/services/todos/destroy/confidential_issue_service_spec.rb
index 9f7e656f7d3..ddce45e7968 100644
--- a/spec/services/todos/destroy/confidential_issue_service_spec.rb
+++ b/spec/services/todos/destroy/confidential_issue_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Todos::Destroy::ConfidentialIssueService do
+RSpec.describe Todos::Destroy::ConfidentialIssueService do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
let(:author) { create(:user) }
diff --git a/spec/services/todos/destroy/entity_leave_service_spec.rb b/spec/services/todos/destroy/entity_leave_service_spec.rb
index 45e3bf381fb..ccafe3bb7a8 100644
--- a/spec/services/todos/destroy/entity_leave_service_spec.rb
+++ b/spec/services/todos/destroy/entity_leave_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Todos::Destroy::EntityLeaveService do
+RSpec.describe Todos::Destroy::EntityLeaveService do
let(:group) { create(:group, :private) }
let(:project) { create(:project, group: group) }
let(:user) { create(:user) }
diff --git a/spec/services/todos/destroy/group_private_service_spec.rb b/spec/services/todos/destroy/group_private_service_spec.rb
index 7dd495847b3..30d02cb7400 100644
--- a/spec/services/todos/destroy/group_private_service_spec.rb
+++ b/spec/services/todos/destroy/group_private_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Todos::Destroy::GroupPrivateService do
+RSpec.describe Todos::Destroy::GroupPrivateService do
let(:group) { create(:group, :public) }
let(:project) { create(:project, group: group) }
let(:user) { create(:user) }
diff --git a/spec/services/todos/destroy/private_features_service_spec.rb b/spec/services/todos/destroy/private_features_service_spec.rb
index dfe9f42e8b1..6dbd7574b80 100644
--- a/spec/services/todos/destroy/private_features_service_spec.rb
+++ b/spec/services/todos/destroy/private_features_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Todos::Destroy::PrivateFeaturesService do
+RSpec.describe Todos::Destroy::PrivateFeaturesService do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
let(:another_user) { create(:user) }
diff --git a/spec/services/todos/destroy/project_private_service_spec.rb b/spec/services/todos/destroy/project_private_service_spec.rb
index 7c0c76b6c29..1d1c010535d 100644
--- a/spec/services/todos/destroy/project_private_service_spec.rb
+++ b/spec/services/todos/destroy/project_private_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Todos::Destroy::ProjectPrivateService do
+RSpec.describe Todos::Destroy::ProjectPrivateService do
let(:group) { create(:group, :public) }
let(:project) { create(:project, :public, group: group) }
let(:user) { create(:user) }
diff --git a/spec/services/update_container_registry_info_service_spec.rb b/spec/services/update_container_registry_info_service_spec.rb
new file mode 100644
index 00000000000..740e53b0472
--- /dev/null
+++ b/spec/services/update_container_registry_info_service_spec.rb
@@ -0,0 +1,115 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe UpdateContainerRegistryInfoService do
+ let_it_be(:application_settings) { Gitlab::CurrentSettings }
+ let_it_be(:api_url) { 'http://registry.gitlab' }
+
+ describe '#execute' do
+ before do
+ stub_access_token
+ stub_container_registry_config(enabled: true, api_url: api_url)
+ end
+
+ subject { described_class.new.execute }
+
+ shared_examples 'invalid config' do
+ it 'does not update the application settings' do
+ expect(application_settings).not_to receive(:update!)
+
+ subject
+ end
+
+ it 'does not raise an error' do
+ expect { subject }.not_to raise_error
+ end
+ end
+
+ context 'when container registry is disabled' do
+ before do
+ stub_container_registry_config(enabled: false)
+ end
+
+ it_behaves_like 'invalid config'
+ end
+
+ context 'when container registry api_url is blank' do
+ before do
+ stub_container_registry_config(api_url: '')
+ end
+
+ it_behaves_like 'invalid config'
+ end
+
+ context 'when creating a registry client instance' do
+ let(:token) { 'foo' }
+ let(:client) { ContainerRegistry::Client.new(api_url, token: token) }
+
+ before do
+ stub_registry_info({})
+ end
+
+ it 'uses a token with no access permissions' do
+ expect(Auth::ContainerRegistryAuthenticationService)
+ .to receive(:access_token).with([], []).and_return(token)
+ expect(ContainerRegistry::Client)
+ .to receive(:new).with(api_url, token: token).and_return(client)
+
+ subject
+ end
+ end
+
+ context 'when unabled to detect the container registry type' do
+ it 'sets the application settings to their defaults' do
+ stub_registry_info({})
+
+ subject
+
+ application_settings.reload
+ expect(application_settings.container_registry_vendor).to be_blank
+ expect(application_settings.container_registry_version).to be_blank
+ expect(application_settings.container_registry_features).to eq([])
+ end
+ end
+
+ context 'when able to detect the container registry type' do
+ context 'when using the GitLab container registry' do
+ it 'updates application settings accordingly' do
+ stub_registry_info(vendor: 'gitlab', version: '2.9.1-gitlab', features: %w[a,b,c])
+
+ subject
+
+ application_settings.reload
+ expect(application_settings.container_registry_vendor).to eq('gitlab')
+ expect(application_settings.container_registry_version).to eq('2.9.1-gitlab')
+ expect(application_settings.container_registry_features).to eq(%w[a,b,c])
+ end
+ end
+
+ context 'when using a third-party container registry' do
+ it 'updates application settings accordingly' do
+ stub_registry_info(vendor: 'other', version: nil, features: nil)
+
+ subject
+
+ application_settings.reload
+ expect(application_settings.container_registry_vendor).to eq('other')
+ expect(application_settings.container_registry_version).to be_blank
+ expect(application_settings.container_registry_features).to eq([])
+ end
+ end
+ end
+ end
+
+ def stub_access_token
+ allow(Auth::ContainerRegistryAuthenticationService)
+ .to receive(:access_token).with([], []).and_return('foo')
+ end
+
+ def stub_registry_info(output)
+ allow_next_instance_of(ContainerRegistry::Client) do |client|
+ allow(client).to receive(:registry_info).and_return(output)
+ end
+ end
+end
diff --git a/spec/services/update_merge_request_metrics_service_spec.rb b/spec/services/update_merge_request_metrics_service_spec.rb
index 1aaf5e712f9..a07fcee91e4 100644
--- a/spec/services/update_merge_request_metrics_service_spec.rb
+++ b/spec/services/update_merge_request_metrics_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestMetricsService do
+RSpec.describe MergeRequestMetricsService do
let(:metrics) { create(:merge_request).metrics }
describe '#merge' do
diff --git a/spec/services/upload_service_spec.rb b/spec/services/upload_service_spec.rb
index 504e61f9903..89a28e6a098 100644
--- a/spec/services/upload_service_spec.rb
+++ b/spec/services/upload_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UploadService do
+RSpec.describe UploadService do
describe 'File service' do
before do
@user = create(:user)
diff --git a/spec/services/user_project_access_changed_service_spec.rb b/spec/services/user_project_access_changed_service_spec.rb
index e5ecdd123f7..070782992e7 100644
--- a/spec/services/user_project_access_changed_service_spec.rb
+++ b/spec/services/user_project_access_changed_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UserProjectAccessChangedService do
+RSpec.describe UserProjectAccessChangedService do
describe '#execute' do
it 'schedules the user IDs' do
expect(AuthorizedProjectsWorker).to receive(:bulk_perform_and_wait)
diff --git a/spec/services/users/activity_service_spec.rb b/spec/services/users/activity_service_spec.rb
index 5f068a2033c..4bbf6a2bcb8 100644
--- a/spec/services/users/activity_service_spec.rb
+++ b/spec/services/users/activity_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::ActivityService do
+RSpec.describe Users::ActivityService do
include ExclusiveLeaseHelpers
let(:user) { create(:user, last_activity_on: last_activity_on) }
diff --git a/spec/services/users/block_service_spec.rb b/spec/services/users/block_service_spec.rb
index c3a65a08c0d..e170a5494aa 100644
--- a/spec/services/users/block_service_spec.rb
+++ b/spec/services/users/block_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::BlockService do
+RSpec.describe Users::BlockService do
let(:current_user) { create(:admin) }
subject(:service) { described_class.new(current_user) }
diff --git a/spec/services/users/build_service_spec.rb b/spec/services/users/build_service_spec.rb
index 7588be833ae..c14fdb35bfa 100644
--- a/spec/services/users/build_service_spec.rb
+++ b/spec/services/users/build_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::BuildService do
+RSpec.describe Users::BuildService do
describe '#execute' do
let(:params) do
{ name: 'John Doe', username: 'jduser', email: 'jd@example.com', password: 'mydummypass' }
diff --git a/spec/services/users/create_service_spec.rb b/spec/services/users/create_service_spec.rb
index c783a1403df..69d2d6ca9ff 100644
--- a/spec/services/users/create_service_spec.rb
+++ b/spec/services/users/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::CreateService do
+RSpec.describe Users::CreateService do
describe '#execute' do
let(:admin_user) { create(:admin) }
diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb
index 3db5e66fe05..ff919257b3c 100644
--- a/spec/services/users/destroy_service_spec.rb
+++ b/spec/services/users/destroy_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::DestroyService do
+RSpec.describe Users::DestroyService do
describe "Deletes a user and all their personal projects" do
let!(:user) { create(:user) }
let!(:admin) { create(:admin) }
diff --git a/spec/services/users/keys_count_service_spec.rb b/spec/services/users/keys_count_service_spec.rb
index 83af051e728..aff267cce5e 100644
--- a/spec/services/users/keys_count_service_spec.rb
+++ b/spec/services/users/keys_count_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::KeysCountService, :use_clean_rails_memory_store_caching do
+RSpec.describe Users::KeysCountService, :use_clean_rails_memory_store_caching do
let(:user) { create(:user) }
subject { described_class.new(user) }
diff --git a/spec/services/users/last_push_event_service_spec.rb b/spec/services/users/last_push_event_service_spec.rb
index 424e9e2f8ef..5b755db407f 100644
--- a/spec/services/users/last_push_event_service_spec.rb
+++ b/spec/services/users/last_push_event_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::LastPushEventService do
+RSpec.describe Users::LastPushEventService do
let(:user) { build(:user, id: 1) }
let(:project) { build(:project, id: 2) }
let(:event) { build(:push_event, id: 3, author: user, project: project) }
diff --git a/spec/services/users/migrate_to_ghost_user_service_spec.rb b/spec/services/users/migrate_to_ghost_user_service_spec.rb
index c2a793b2368..c9c8f9a74d3 100644
--- a/spec/services/users/migrate_to_ghost_user_service_spec.rb
+++ b/spec/services/users/migrate_to_ghost_user_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::MigrateToGhostUserService do
+RSpec.describe Users::MigrateToGhostUserService do
let!(:user) { create(:user) }
let!(:project) { create(:project, :repository) }
let(:service) { described_class.new(user) }
diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb
index d7ba7f5f69e..e45cb05a6c5 100644
--- a/spec/services/users/refresh_authorized_projects_service_spec.rb
+++ b/spec/services/users/refresh_authorized_projects_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::RefreshAuthorizedProjectsService do
+RSpec.describe Users::RefreshAuthorizedProjectsService do
include ExclusiveLeaseHelpers
# We're using let! here so that any expectations for the service class are not
diff --git a/spec/services/users/repair_ldap_blocked_service_spec.rb b/spec/services/users/repair_ldap_blocked_service_spec.rb
index bf80cc79d62..b33dcb92f45 100644
--- a/spec/services/users/repair_ldap_blocked_service_spec.rb
+++ b/spec/services/users/repair_ldap_blocked_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::RepairLdapBlockedService do
+RSpec.describe Users::RepairLdapBlockedService do
let(:user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') }
let(:identity) { user.ldap_identity }
diff --git a/spec/services/users/respond_to_terms_service_spec.rb b/spec/services/users/respond_to_terms_service_spec.rb
index d840706e8a5..1997dcd0e04 100644
--- a/spec/services/users/respond_to_terms_service_spec.rb
+++ b/spec/services/users/respond_to_terms_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::RespondToTermsService do
+RSpec.describe Users::RespondToTermsService do
let(:user) { create(:user) }
let(:term) { create(:term) }
diff --git a/spec/services/users/set_status_service_spec.rb b/spec/services/users/set_status_service_spec.rb
index 554f5e9dc5e..54489adceb0 100644
--- a/spec/services/users/set_status_service_spec.rb
+++ b/spec/services/users/set_status_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::SetStatusService do
+RSpec.describe Users::SetStatusService do
let(:current_user) { create(:user) }
subject(:service) { described_class.new(current_user, params) }
diff --git a/spec/services/users/signup_service_spec.rb b/spec/services/users/signup_service_spec.rb
index 7d3cd614142..cc234309817 100644
--- a/spec/services/users/signup_service_spec.rb
+++ b/spec/services/users/signup_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::SignupService do
+RSpec.describe Users::SignupService do
let(:user) { create(:user, setup_for_company: true) }
describe '#execute' do
diff --git a/spec/services/users/update_canonical_email_service_spec.rb b/spec/services/users/update_canonical_email_service_spec.rb
index 68ba1b75b6c..1dead13d338 100644
--- a/spec/services/users/update_canonical_email_service_spec.rb
+++ b/spec/services/users/update_canonical_email_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::UpdateCanonicalEmailService do
+RSpec.describe Users::UpdateCanonicalEmailService do
let(:other_email) { "differentaddress@includeddomain.com" }
before do
diff --git a/spec/services/users/update_highest_member_role_service_spec.rb b/spec/services/users/update_highest_member_role_service_spec.rb
index 8063abffc2a..89ddd635bb6 100644
--- a/spec/services/users/update_highest_member_role_service_spec.rb
+++ b/spec/services/users/update_highest_member_role_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::UpdateHighestMemberRoleService do
+RSpec.describe Users::UpdateHighestMemberRoleService do
let(:user) { create(:user) }
let(:execute_service) { described_class.new(user).execute }
diff --git a/spec/services/users/update_service_spec.rb b/spec/services/users/update_service_spec.rb
index 8e13e7d9c0c..274c44394f3 100644
--- a/spec/services/users/update_service_spec.rb
+++ b/spec/services/users/update_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::UpdateService do
+RSpec.describe Users::UpdateService do
let(:user) { create(:user) }
describe '#execute' do
diff --git a/spec/services/verify_pages_domain_service_spec.rb b/spec/services/verify_pages_domain_service_spec.rb
index 3f08ae84c14..29ad85a16ce 100644
--- a/spec/services/verify_pages_domain_service_spec.rb
+++ b/spec/services/verify_pages_domain_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe VerifyPagesDomainService do
+RSpec.describe VerifyPagesDomainService do
using RSpec::Parameterized::TableSyntax
include EmailHelpers
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index 4a917ecdbb5..2be481c5b62 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WebHookService do
+RSpec.describe WebHookService do
include StubRequests
let(:project) { create(:project) }
diff --git a/spec/services/wiki_pages/base_service_spec.rb b/spec/services/wiki_pages/base_service_spec.rb
index fede86a5192..6ccc796014c 100644
--- a/spec/services/wiki_pages/base_service_spec.rb
+++ b/spec/services/wiki_pages/base_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WikiPages::BaseService do
+RSpec.describe WikiPages::BaseService do
let(:project) { double('project') }
let(:user) { double('user') }
diff --git a/spec/services/wiki_pages/create_service_spec.rb b/spec/services/wiki_pages/create_service_spec.rb
index 2a17805110e..44b57088319 100644
--- a/spec/services/wiki_pages/create_service_spec.rb
+++ b/spec/services/wiki_pages/create_service_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe WikiPages::CreateService do
+RSpec.describe WikiPages::CreateService do
it_behaves_like 'WikiPages::CreateService#execute', :project
end
diff --git a/spec/services/wiki_pages/destroy_service_spec.rb b/spec/services/wiki_pages/destroy_service_spec.rb
index b6fee1fd896..9384ea1cd43 100644
--- a/spec/services/wiki_pages/destroy_service_spec.rb
+++ b/spec/services/wiki_pages/destroy_service_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe WikiPages::DestroyService do
+RSpec.describe WikiPages::DestroyService do
it_behaves_like 'WikiPages::DestroyService#execute', :project
end
diff --git a/spec/services/wiki_pages/event_create_service_spec.rb b/spec/services/wiki_pages/event_create_service_spec.rb
index c725c67d7a7..abf3bcb4c4d 100644
--- a/spec/services/wiki_pages/event_create_service_spec.rb
+++ b/spec/services/wiki_pages/event_create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WikiPages::EventCreateService do
+RSpec.describe WikiPages::EventCreateService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
@@ -14,21 +14,6 @@ describe WikiPages::EventCreateService do
let(:action) { :created }
let(:response) { subject.execute(slug, page, action) }
- context 'feature flag is not enabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it 'does not error' do
- expect(response).to be_success
- .and have_attributes(message: /No event created/)
- end
-
- it 'does not create an event' do
- expect { response }.not_to change(Event, :count)
- end
- end
-
context 'the user is nil' do
subject { described_class.new(nil) }
diff --git a/spec/services/wiki_pages/update_service_spec.rb b/spec/services/wiki_pages/update_service_spec.rb
index ac629a96f9a..33ac98e764d 100644
--- a/spec/services/wiki_pages/update_service_spec.rb
+++ b/spec/services/wiki_pages/update_service_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe WikiPages::UpdateService do
+RSpec.describe WikiPages::UpdateService do
it_behaves_like 'WikiPages::UpdateService#execute', :project
end
diff --git a/spec/services/wikis/create_attachment_service_spec.rb b/spec/services/wikis/create_attachment_service_spec.rb
index 4adfaa24874..50cb9ac111c 100644
--- a/spec/services/wikis/create_attachment_service_spec.rb
+++ b/spec/services/wikis/create_attachment_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Wikis::CreateAttachmentService do
+RSpec.describe Wikis::CreateAttachmentService do
let(:container) { create(:project, :wiki_repo) }
let(:user) { create(:user) }
let(:file_name) { 'filename.txt' }
diff --git a/spec/services/x509_certificate_revoke_service_spec.rb b/spec/services/x509_certificate_revoke_service_spec.rb
index c2b2576904c..adad3281c13 100644
--- a/spec/services/x509_certificate_revoke_service_spec.rb
+++ b/spec/services/x509_certificate_revoke_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe X509CertificateRevokeService do
+RSpec.describe X509CertificateRevokeService do
describe '#execute' do
let(:service) { described_class.new }
let!(:x509_signature_1) { create(:x509_commit_signature, x509_certificate: x509_certificate, verification_status: :verified ) }
diff --git a/spec/sidekiq/cron/job_gem_dependency_spec.rb b/spec/sidekiq/cron/job_gem_dependency_spec.rb
index 20347b4d306..2924c86c8d0 100644
--- a/spec/sidekiq/cron/job_gem_dependency_spec.rb
+++ b/spec/sidekiq/cron/job_gem_dependency_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Sidekiq::Cron::Job do
+RSpec.describe Sidekiq::Cron::Job do
describe 'cron jobs' do
context 'when Fugit depends on ZoTime or EoTime' do
before do
diff --git a/spec/simplecov_env.rb b/spec/simplecov_env.rb
index c5b8a6db605..17b76205d9e 100644
--- a/spec/simplecov_env.rb
+++ b/spec/simplecov_env.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'simplecov'
+require 'simplecov-cobertura'
require 'active_support/core_ext/numeric/time'
require_relative '../lib/gitlab/utils'
@@ -12,10 +13,19 @@ module SimpleCovEnv
configure_profile
configure_job
+ configure_formatter
SimpleCov.start
end
+ def configure_formatter
+ SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new([
+ SimpleCov::Formatter::SimpleFormatter,
+ SimpleCov::Formatter::HTMLFormatter,
+ SimpleCov::Formatter::CoberturaFormatter
+ ])
+ end
+
def configure_job
SimpleCov.configure do
if ENV['CI_JOB_NAME']
@@ -37,28 +47,33 @@ module SimpleCovEnv
def configure_profile
SimpleCov.configure do
load_profile 'test_frameworks'
- track_files '{app,lib}/**/*.rb'
+ track_files '{app,config/initializers,config/initializers_before_autoloader,db/post_migrate,haml_lint,lib,rubocop,tooling}/**/*.rb'
add_filter '/vendor/ruby/'
- add_filter 'app/controllers/sherlock/'
- add_filter 'config/initializers/'
- add_filter 'db/fixtures/'
- add_filter 'lib/gitlab/sidekiq_middleware/'
- add_filter 'lib/system_check/'
+ add_filter '/app/controllers/sherlock/'
+ add_filter '/bin/'
+ add_filter 'db/fixtures/' # Matches EE files as well
+ add_filter '/lib/gitlab/sidekiq_middleware/'
+ add_filter '/lib/system_check/'
- add_group 'Controllers', 'app/controllers'
- add_group 'Finders', 'app/finders'
- add_group 'Helpers', 'app/helpers'
- add_group 'Libraries', 'lib'
- add_group 'Mailers', 'app/mailers'
- add_group 'Models', 'app/models'
- add_group 'Policies', 'app/policies'
- add_group 'Presenters', 'app/presenters'
- add_group 'Serializers', 'app/serializers'
- add_group 'Services', 'app/services'
- add_group 'Uploaders', 'app/uploaders'
- add_group 'Validators', 'app/validators'
- add_group 'Workers', %w(app/jobs app/workers)
+ add_group 'Channels', 'app/channels' # Matches EE files as well
+ add_group 'Controllers', 'app/controllers' # Matches EE files as well
+ add_group 'Finders', 'app/finders' # Matches EE files as well
+ add_group 'GraphQL', 'app/graphql' # Matches EE files as well
+ add_group 'Helpers', 'app/helpers' # Matches EE files as well
+ add_group 'Mailers', 'app/mailers' # Matches EE files as well
+ add_group 'Models', 'app/models' # Matches EE files as well
+ add_group 'Policies', 'app/policies' # Matches EE files as well
+ add_group 'Presenters', 'app/presenters' # Matches EE files as well
+ add_group 'Serializers', 'app/serializers' # Matches EE files as well
+ add_group 'Services', 'app/services' # Matches EE files as well
+ add_group 'Uploaders', 'app/uploaders' # Matches EE files as well
+ add_group 'Validators', 'app/validators' # Matches EE files as well
+ add_group 'Workers', %w[app/jobs app/workers] # Matches EE files as well
+ add_group 'Initializers', %w[config/initializers config/initializers_before_autoloader] # Matches EE files as well
+ add_group 'Migrations', %w[db/migrate db/optional_migrations db/post_migrate] # Matches EE files as well
+ add_group 'Libraries', %w[/lib /ee/lib]
+ add_group 'Tooling', %w[/haml_lint /rubocop /tooling]
merge_timeout 365.days
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 84de5119505..ed3211a9c87 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -103,7 +103,6 @@ RSpec.configure do |config|
config.include ActiveJob::TestHelper
config.include ActiveSupport::Testing::TimeHelpers
config.include CycleAnalyticsHelpers
- config.include ExpectOffense
config.include FactoryBot::Syntax::Methods
config.include FixtureHelpers
config.include NonExistingRecordsHelpers
@@ -156,6 +155,9 @@ RSpec.configure do |config|
config.before(:suite) do
Timecop.safe_mode = true
TestEnv.init
+
+ # Reload all feature flags definitions
+ Feature.register_definitions
end
config.after(:all) do
@@ -332,6 +334,8 @@ RSpec.configure do |config|
Ability.allowed?(*args)
end
end
+
+ config.disable_monkey_patching!
end
ActiveRecord::Migration.maintain_test_schema!
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index 38f9ccf23f5..66fce4fddf1 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -9,6 +9,9 @@ require 'selenium-webdriver'
# Give CI some extra time
timeout = ENV['CI'] || ENV['CI_SERVER'] ? 60 : 30
+# Support running Capybara on a specific port to allow saving commonly used pages
+Capybara.server_port = ENV['CAPYBARA_PORT'] if ENV['CAPYBARA_PORT']
+
# Define an error class for JS console messages
JSConsoleError = Class.new(StandardError)
@@ -153,11 +156,20 @@ RSpec.configure do |config|
# fixed. If we raised the `JSException` the fixed test would be marked as
# failed again.
if example.exception && !example.exception.is_a?(RSpec::Core::Pending::PendingExampleFixedError)
- console = page.driver.browser.manage.logs.get(:browser)&.reject { |log| log.message =~ JS_CONSOLE_FILTER }
-
- if console.present?
- message = "Unexpected browser console output:\n" + console.map(&:message).join("\n")
- raise JSConsoleError, message
+ begin
+ console = page.driver.browser.manage.logs.get(:browser)&.reject { |log| log.message =~ JS_CONSOLE_FILTER }
+
+ if console.present?
+ message = "Unexpected browser console output:\n" + console.map(&:message).join("\n")
+ raise JSConsoleError, message
+ end
+ rescue Selenium::WebDriver::Error::WebDriverError => error
+ if error.message =~ /unknown command: session\/[0-9a-zA-Z]+(?:\/se)?\/log/
+ message = "Unable to access Chrome javascript console logs. You may be using an outdated version of ChromeDriver."
+ raise JSConsoleError, message
+ else
+ raise error
+ end
end
end
diff --git a/spec/support/controllers/project_import_rate_limiter_shared_examples.rb b/spec/support/controllers/project_import_rate_limiter_shared_examples.rb
index 9a543dd00d4..66d753a4010 100644
--- a/spec/support/controllers/project_import_rate_limiter_shared_examples.rb
+++ b/spec/support/controllers/project_import_rate_limiter_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'project import rate limiter' do
+RSpec.shared_examples 'project import rate limiter' do
let(:user) { create(:user) }
before do
diff --git a/spec/support/helpers/expect_offense.rb b/spec/support/helpers/expect_offense.rb
deleted file mode 100644
index 76301fe19ff..00000000000
--- a/spec/support/helpers/expect_offense.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-require 'rubocop/rspec/support'
-
-# https://github.com/backus/rubocop-rspec/blob/master/spec/support/expect_offense.rb
-# rubocop-rspec gem extension of RuboCop's ExpectOffense module.
-#
-# This mixin is the same as rubocop's ExpectOffense except the default
-# filename ends with `_spec.rb`
-module ExpectOffense
- include RuboCop::RSpec::ExpectOffense
-
- DEFAULT_FILENAME = 'example_spec.rb'.freeze
-
- def expect_offense(source, filename = DEFAULT_FILENAME)
- super
- end
-
- def expect_no_offenses(source, filename = DEFAULT_FILENAME)
- super
- end
-end
diff --git a/spec/support/helpers/fast_rails_root.rb b/spec/support/helpers/fast_rails_root.rb
new file mode 100644
index 00000000000..1510fe0825c
--- /dev/null
+++ b/spec/support/helpers/fast_rails_root.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+# For specs which don't load Rails, provide a path to Rails root
+module FastRailsRoot
+ RAILS_ROOT = File.absolute_path("#{__dir__}/../../..")
+
+ def rails_root_join(*args)
+ File.join(RAILS_ROOT, *args)
+ end
+end
diff --git a/spec/support/helpers/filtered_search_helpers.rb b/spec/support/helpers/filtered_search_helpers.rb
index 99a5e043825..1847a8f8a06 100644
--- a/spec/support/helpers/filtered_search_helpers.rb
+++ b/spec/support/helpers/filtered_search_helpers.rb
@@ -45,9 +45,8 @@ module FilteredSearchHelpers
all_count = open_count + closed_count
expect(page).to have_issuable_counts(open: open_count, closed: closed_count, all: all_count)
- page.within '.issues-list' do
- expect(page).to have_selector('.issue', count: open_count)
- end
+
+ expect(page).to have_selector('.issue', count: open_count)
end
# Enables input to be added character by character
diff --git a/spec/support/helpers/jira_service_helper.rb b/spec/support/helpers/jira_service_helper.rb
index 198bedfe3bc..9072c41fe66 100644
--- a/spec/support/helpers/jira_service_helper.rb
+++ b/spec/support/helpers/jira_service_helper.rb
@@ -5,14 +5,13 @@ module JiraServiceHelper
JIRA_API = JIRA_URL + "/rest/api/2"
def jira_service_settings
- title = "Jira tracker"
url = JIRA_URL
username = 'jira-user'
password = 'my-secret-password'
jira_issue_transition_id = '1'
jira_tracker.update(
- title: title, url: url, username: username, password: password,
+ url: url, username: username, password: password,
jira_issue_transition_id: jira_issue_transition_id, active: true
)
end
diff --git a/spec/support/helpers/metrics_dashboard_helpers.rb b/spec/support/helpers/metrics_dashboard_helpers.rb
index b8a641d5911..b2dd8ead7dd 100644
--- a/spec/support/helpers/metrics_dashboard_helpers.rb
+++ b/spec/support/helpers/metrics_dashboard_helpers.rb
@@ -7,6 +7,12 @@ module MetricsDashboardHelpers
create(:project, :custom_repo, files: { dashboard_path => dashboard_yml })
end
+ def project_with_dashboard_namespace(dashboard_path, dashboard_yml = nil)
+ dashboard_yml ||= fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml')
+
+ create(:project, :custom_repo, namespace: namespace, path: 'monitor-project', files: { dashboard_path => dashboard_yml })
+ end
+
def delete_project_dashboard(project, user, dashboard_path)
project.repository.delete_file(
user,
@@ -18,6 +24,14 @@ module MetricsDashboardHelpers
project.repository.refresh_method_caches([:metrics_dashboard])
end
+ def load_sample_dashboard
+ load_dashboard_yaml(fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml'))
+ end
+
+ def load_dashboard_yaml(data)
+ ::Gitlab::Config::Loader::Yaml.new(data).load_raw!
+ end
+
def system_dashboard_path
Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH
end
diff --git a/spec/support/helpers/notification_helpers.rb b/spec/support/helpers/notification_helpers.rb
index b3e0e7d811b..887d68de4e1 100644
--- a/spec/support/helpers/notification_helpers.rb
+++ b/spec/support/helpers/notification_helpers.rb
@@ -38,26 +38,26 @@ module NotificationHelpers
end
def expect_delivery_jobs_count(count)
- expect(ActionMailer::DeliveryJob).to have_been_enqueued.exactly(count).times
+ expect(ActionMailer::MailDeliveryJob).to have_been_enqueued.exactly(count).times
end
def expect_no_delivery_jobs
- expect(ActionMailer::DeliveryJob).not_to have_been_enqueued
+ expect(ActionMailer::MailDeliveryJob).not_to have_been_enqueued
end
def expect_any_delivery_jobs
- expect(ActionMailer::DeliveryJob).to have_been_enqueued.at_least(:once)
+ expect(ActionMailer::MailDeliveryJob).to have_been_enqueued.at_least(:once)
end
def have_enqueued_email(*args, mailer: "Notify", mail: "", delivery: "deliver_now")
- have_enqueued_job(ActionMailer::DeliveryJob).with(mailer, mail, delivery, *args)
+ have_enqueued_job(ActionMailer::MailDeliveryJob).with(mailer, mail, delivery, args: args)
end
def expect_enqueud_email(*args, mailer: "Notify", mail: "", delivery: "deliver_now")
- expect(ActionMailer::DeliveryJob).to have_been_enqueued.with(mailer, mail, delivery, *args)
+ expect(ActionMailer::MailDeliveryJob).to have_been_enqueued.with(mailer, mail, delivery, args: args)
end
def expect_not_enqueud_email(*args, mailer: "Notify", mail: "")
- expect(ActionMailer::DeliveryJob).not_to have_been_enqueued.with(mailer, mail, *args, any_args)
+ expect(ActionMailer::MailDeliveryJob).not_to have_been_enqueued.with(mailer, mail, args: any_args)
end
end
diff --git a/spec/support/helpers/packages_manager_api_spec_helper.rb b/spec/support/helpers/packages_manager_api_spec_helper.rb
new file mode 100644
index 00000000000..e5a690e1680
--- /dev/null
+++ b/spec/support/helpers/packages_manager_api_spec_helper.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module PackagesManagerApiSpecHelpers
+ def build_auth_headers(value)
+ { 'HTTP_AUTHORIZATION' => value }
+ end
+
+ def build_basic_auth_header(username, password)
+ build_auth_headers(ActionController::HttpAuthentication::Basic.encode_credentials(username, password))
+ end
+
+ def build_token_auth_header(token)
+ build_auth_headers("Bearer #{token}")
+ end
+
+ def build_jwt(personal_access_token, secret: jwt_secret, user_id: nil)
+ JSONWebToken::HMACToken.new(secret).tap do |jwt|
+ jwt['access_token'] = personal_access_token.id
+ jwt['user_id'] = user_id || personal_access_token.user_id
+ end
+ end
+
+ def build_jwt_from_job(job, secret: jwt_secret)
+ JSONWebToken::HMACToken.new(secret).tap do |jwt|
+ jwt['access_token'] = job.token
+ jwt['user_id'] = job.user.id
+ end
+ end
+
+ def build_jwt_from_deploy_token(deploy_token, secret: jwt_secret)
+ JSONWebToken::HMACToken.new(secret).tap do |jwt|
+ jwt['access_token'] = deploy_token.token
+ jwt['user_id'] = deploy_token.username
+ end
+ end
+
+ def temp_file(package_tmp)
+ upload_path = ::Packages::PackageFileUploader.workhorse_local_upload_path
+ file_path = "#{upload_path}/#{package_tmp}"
+
+ FileUtils.mkdir_p(upload_path)
+ File.write(file_path, 'test')
+
+ UploadedFile.new(file_path, filename: File.basename(file_path))
+ end
+end
diff --git a/spec/support/helpers/partitioning_helpers.rb b/spec/support/helpers/partitioning_helpers.rb
index 98a13915d76..8981fea04d5 100644
--- a/spec/support/helpers/partitioning_helpers.rb
+++ b/spec/support/helpers/partitioning_helpers.rb
@@ -9,13 +9,36 @@ module PartitioningHelpers
end
def expect_range_partition_of(partition_name, table_name, min_value, max_value)
- definition = find_partition_definition(partition_name)
+ definition = find_partition_definition(partition_name, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA)
expect(definition).not_to be_nil
expect(definition['base_table']).to eq(table_name.to_s)
expect(definition['condition']).to eq("FOR VALUES FROM (#{min_value}) TO (#{max_value})")
end
+ def expect_total_partitions(table_name, count, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA)
+ partitions = find_partitions(table_name, schema: schema)
+
+ expect(partitions.size).to eq(count)
+ end
+
+ def expect_range_partitions_for(table_name, partitions)
+ partitions.each do |suffix, (min_value, max_value)|
+ partition_name = "#{table_name}_#{suffix}"
+ expect_range_partition_of(partition_name, table_name, min_value, max_value)
+ end
+
+ expect_total_partitions(table_name, partitions.size, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA)
+ end
+
+ def expect_hash_partition_of(partition_name, table_name, modulus, remainder)
+ definition = find_partition_definition(partition_name, schema: Gitlab::Database::STATIC_PARTITIONS_SCHEMA)
+
+ expect(definition).not_to be_nil
+ expect(definition['base_table']).to eq(table_name.to_s)
+ expect(definition['condition']).to eq("FOR VALUES WITH (modulus #{modulus}, remainder #{remainder})")
+ end
+
private
def find_partitioned_columns(table)
@@ -40,7 +63,7 @@ module PartitioningHelpers
SQL
end
- def find_partition_definition(partition)
+ def find_partition_definition(partition, schema: )
connection.select_one(<<~SQL)
select
parent_class.relname as base_table,
@@ -48,7 +71,24 @@ module PartitioningHelpers
from pg_class
inner join pg_inherits i on pg_class.oid = inhrelid
inner join pg_class parent_class on parent_class.oid = inhparent
- where pg_class.relname = '#{partition}' and pg_class.relispartition;
+ inner join pg_namespace ON pg_namespace.oid = pg_class.relnamespace
+ where pg_namespace.nspname = '#{schema}'
+ and pg_class.relname = '#{partition}'
+ and pg_class.relispartition
+ SQL
+ end
+
+ def find_partitions(partition, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA)
+ connection.select_rows(<<~SQL)
+ select
+ pg_class.relname
+ from pg_class
+ inner join pg_inherits i on pg_class.oid = inhrelid
+ inner join pg_class parent_class on parent_class.oid = inhparent
+ inner join pg_namespace ON pg_namespace.oid = pg_class.relnamespace
+ where pg_namespace.nspname = '#{schema}'
+ and parent_class.relname = '#{partition}'
+ and pg_class.relispartition
SQL
end
end
diff --git a/spec/support/helpers/rack_attack_spec_helpers.rb b/spec/support/helpers/rack_attack_spec_helpers.rb
index e0cedb5a57b..65082ec690f 100644
--- a/spec/support/helpers/rack_attack_spec_helpers.rb
+++ b/spec/support/helpers/rack_attack_spec_helpers.rb
@@ -30,4 +30,16 @@ module RackAttackSpecHelpers
expect(response).to have_gitlab_http_status(:too_many_requests)
end
+
+ def expect_ok(&block)
+ yield
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ def random_next_ip
+ allow_next_instance_of(Rack::Attack::Request) do |instance|
+ allow(instance).to receive(:ip).and_return(FFaker::Internet.ip_v4_address)
+ end
+ end
end
diff --git a/spec/support/helpers/reference_parser_helpers.rb b/spec/support/helpers/reference_parser_helpers.rb
index 9084265b587..e65cb8c96db 100644
--- a/spec/support/helpers/reference_parser_helpers.rb
+++ b/spec/support/helpers/reference_parser_helpers.rb
@@ -10,7 +10,7 @@ module ReferenceParserHelpers
expect(result[:not_visible].count).to eq(not_visible_count)
end
- shared_examples 'no project N+1 queries' do
+ RSpec.shared_examples 'no project N+1 queries' do
it 'avoids N+1 queries in #nodes_visible_to_user', :request_store do
context = Banzai::RenderContext.new(project, user)
@@ -28,7 +28,7 @@ module ReferenceParserHelpers
end
end
- shared_examples 'no N+1 queries' do
+ RSpec.shared_examples 'no N+1 queries' do
it_behaves_like 'no project N+1 queries'
it 'avoids N+1 queries in #records_for_nodes', :request_store do
diff --git a/spec/support/helpers/snippet_helpers.rb b/spec/support/helpers/snippet_helpers.rb
new file mode 100644
index 00000000000..de64ad7d3e2
--- /dev/null
+++ b/spec/support/helpers/snippet_helpers.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module SnippetHelpers
+ def sign_in_as(user)
+ sign_in(public_send(user)) if user
+ end
+
+ def snippet_blob_file(blob)
+ {
+ "path" => blob.path,
+ "raw_url" => gitlab_raw_snippet_blob_url(blob.container, blob.path)
+ }
+ end
+end
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index 6a832ca97d1..e19f230d8df 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -113,6 +113,14 @@ module StubConfiguration
allow(Gitlab.config.rack_attack.git_basic_auth).to receive_messages(to_settings(messages))
end
+ def stub_service_desk_email_setting(messages)
+ allow(::Gitlab.config.service_desk_email).to receive_messages(to_settings(messages))
+ end
+
+ def stub_packages_setting(messages)
+ allow(::Gitlab.config.packages).to receive_messages(to_settings(messages))
+ end
+
private
# Modifies stubbed messages to also stub possible predicate versions
diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb
index b473cdaefc1..6056359d026 100644
--- a/spec/support/helpers/stub_object_storage.rb
+++ b/spec/support/helpers/stub_object_storage.rb
@@ -1,6 +1,25 @@
# frozen_string_literal: true
module StubObjectStorage
+ def stub_packages_object_storage(**params)
+ stub_object_storage_uploader(config: ::Gitlab.config.packages.object_store,
+ uploader: ::Packages::PackageFileUploader,
+ remote_directory: 'packages',
+ **params)
+ end
+
+ def stub_dependency_proxy_object_storage(**params)
+ stub_object_storage_uploader(config: ::Gitlab.config.dependency_proxy.object_store,
+ uploader: ::DependencyProxy::FileUploader,
+ remote_directory: 'dependency_proxy',
+ **params)
+ end
+
+ def stub_object_storage_pseudonymizer
+ stub_object_storage(connection_params: Pseudonymizer::Uploader.object_store_credentials,
+ remote_directory: Pseudonymizer::Uploader.remote_directory)
+ end
+
def stub_object_storage_uploader(
config:,
uploader:,
@@ -73,7 +92,7 @@ module StubObjectStorage
def stub_terraform_state_object_storage(uploader = described_class, **params)
stub_object_storage_uploader(config: Gitlab.config.terraform_state.object_store,
uploader: uploader,
- remote_directory: 'terraform_state',
+ remote_directory: 'terraform',
**params)
end
@@ -89,8 +108,3 @@ module StubObjectStorage
EOS
end
end
-
-require_relative '../../../ee/spec/support/helpers/ee/stub_object_storage' if
- Dir.exist?("#{__dir__}/../../../ee")
-
-StubObjectStorage.prepend_if_ee('EE::StubObjectStorage')
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 130650b7e2e..f787aedf7aa 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -6,6 +6,8 @@ module TestEnv
ComponentFailedToInstallError = Class.new(StandardError)
+ SHA_REGEX = /\A[0-9a-f]{5,40}\z/i.freeze
+
# When developing the seed repository, comment out the branch you will modify.
BRANCH_SHA = {
'signed-commits' => '6101e87',
@@ -29,6 +31,10 @@ module TestEnv
'gitattributes' => '5a62481',
'expand-collapse-diffs' => '4842455',
'symlink-expand-diff' => '81e6355',
+ 'diff-files-symlink-to-image' => '8cfca84',
+ 'diff-files-image-to-symlink' => '3e94fda',
+ 'diff-files-symlink-to-text' => '689815e',
+ 'diff-files-text-to-symlink' => '5e2c270',
'expand-collapse-files' => '025db92',
'expand-collapse-lines' => '238e82d',
'pages-deploy' => '7897d5b',
@@ -165,8 +171,9 @@ module TestEnv
task: "gitlab:gitaly:install[#{install_gitaly_args}]") do
Gitlab::SetupHelper::Gitaly.create_configuration(gitaly_dir, { 'default' => repos_path }, force: true)
Gitlab::SetupHelper::Praefect.create_configuration(gitaly_dir, { 'praefect' => repos_path }, force: true)
- start_gitaly(gitaly_dir)
end
+
+ start_gitaly(gitaly_dir)
end
def gitaly_socket_path
@@ -459,7 +466,6 @@ module TestEnv
end
def component_timed_setup(component, install_dir:, version:, task:)
- puts "\n==> Setting up #{component}..."
start = Time.now
ensure_component_dir_name_is_correct!(component, install_dir)
@@ -468,22 +474,22 @@ module TestEnv
return if File.exist?(install_dir) && ci?
if component_needs_update?(install_dir, version)
+ puts "\n==> Setting up #{component}..."
# Cleanup the component entirely to ensure we start fresh
FileUtils.rm_rf(install_dir)
unless system('rake', task)
raise ComponentFailedToInstallError
end
- end
- yield if block_given?
+ yield if block_given?
+ puts " #{component} set up in #{Time.now - start} seconds...\n"
+ end
rescue ComponentFailedToInstallError
puts "\n#{component} failed to install, cleaning up #{install_dir}!\n"
FileUtils.rm_rf(install_dir)
exit 1
- ensure
- puts " #{component} set up in #{Time.now - start} seconds...\n"
end
def ci?
@@ -504,6 +510,8 @@ module TestEnv
# Allow local overrides of the component for tests during development
return false if Rails.env.test? && File.symlink?(component_folder)
+ return false if component_matches_git_sha?(component_folder, expected_version)
+
version = File.read(File.join(component_folder, 'VERSION')).strip
# Notice that this will always yield true when using branch versions
@@ -513,6 +521,16 @@ module TestEnv
rescue Errno::ENOENT
true
end
+
+ def component_matches_git_sha?(component_folder, expected_version)
+ # Not a git SHA, so return early
+ return false unless expected_version =~ SHA_REGEX
+
+ sha, exit_status = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} rev-parse HEAD), component_folder)
+ return false if exit_status != 0
+
+ expected_version == sha.chomp
+ end
end
require_relative('../../../ee/spec/support/helpers/ee/test_env') if Gitlab.ee?
diff --git a/spec/support/helpers/trigger_helpers.rb b/spec/support/helpers/trigger_helpers.rb
index fa4f499b900..67c62cf4869 100644
--- a/spec/support/helpers/trigger_helpers.rb
+++ b/spec/support/helpers/trigger_helpers.rb
@@ -27,7 +27,10 @@ module TriggerHelpers
expected_timing, expected_events = fires_on.first
expect(timing).to eq(expected_timing.to_s)
expect(events).to match_array(Array.wrap(expected_events))
- expect(definition).to eq("execute procedure #{fn_name}()")
+
+ # TODO: Update CREATE TRIGGER syntax to use EXECUTE FUNCTION
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/227089
+ expect(definition).to match(%r{execute (?:procedure|function) #{fn_name}()})
end
private
diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb
index f6c415a75bc..a4f40a4af0a 100644
--- a/spec/support/helpers/usage_data_helpers.rb
+++ b/spec/support/helpers/usage_data_helpers.rb
@@ -78,7 +78,6 @@ module UsageDataHelpers
labels
lfs_objects
merge_requests
- merge_requests_users
milestone_lists
milestones
notes
@@ -89,8 +88,6 @@ module UsageDataHelpers
projects_jira_active
projects_jira_server_active
projects_jira_cloud_active
- projects_slack_notifications_active
- projects_slack_slash_active
projects_slack_active
projects_slack_slash_commands_active
projects_custom_issue_tracker_active
@@ -231,6 +228,15 @@ module UsageDataHelpers
def allow_prometheus_queries
allow_next_instance_of(Gitlab::PrometheusClient) do |client|
allow(client).to receive(:aggregate).and_return({})
+ allow(client).to receive(:query).and_return({})
+ end
+ end
+
+ def for_defined_days_back(days: [29, 2])
+ days.each do |n|
+ Timecop.travel(n.days.ago) do
+ yield
+ end
end
end
end
diff --git a/spec/support/matchers/background_migrations_matchers.rb b/spec/support/matchers/background_migrations_matchers.rb
index 8735dac8b2a..0144a044f6c 100644
--- a/spec/support/matchers/background_migrations_matchers.rb
+++ b/spec/support/matchers/background_migrations_matchers.rb
@@ -10,7 +10,8 @@ RSpec::Matchers.define :be_scheduled_delayed_migration do |delay, *expected|
failure_message do |migration|
"Migration `#{migration}` with args `#{expected.inspect}` " \
- 'not scheduled in expected time!'
+ "not scheduled in expected time! Expected any of `#{BackgroundMigrationWorker.jobs.map { |j| j['args'] }}` to be `#{[migration, expected]}` " \
+ "and any of `#{BackgroundMigrationWorker.jobs.map { |j| j['at'].to_i }}` to be `#{delay.to_i + Time.now.to_i}` (`#{delay.to_i}` + `#{Time.now.to_i}`)."
end
end
diff --git a/spec/support/matchers/jsonb_matchers.rb b/spec/support/matchers/jsonb_matchers.rb
new file mode 100644
index 00000000000..823888708f3
--- /dev/null
+++ b/spec/support/matchers/jsonb_matchers.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+RSpec::Matchers.define :validate_jsonb_schema do |jsonb_columns|
+ match do |actual|
+ next true if jsonb_columns.blank?
+
+ expect(actual.validators).to include(a_kind_of(JsonSchemaValidator))
+ end
+
+ failure_message do
+ <<~FAILURE_MESSAGE
+ Expected #{actual.name} to validate the schema of #{jsonb_columns.join(', ')}.
+
+ Use JsonSchemaValidator in your model when using a jsonb column.
+ See doc/development/migration_style_guide.html#storing-json-in-database for more information.
+
+ To fix this, please add `validates :#{jsonb_columns.first}, json_schema: { filename: "filename" }` in your model file, for example:
+
+ class #{actual.name}
+ validates :#{jsonb_columns.first}, json_schema: { filename: "filename" }
+ end
+ FAILURE_MESSAGE
+ end
+end
diff --git a/spec/support/rspec.rb b/spec/support/rspec.rb
index 7d011c5eb95..861b57c9efa 100644
--- a/spec/support/rspec.rb
+++ b/spec/support/rspec.rb
@@ -4,7 +4,8 @@ require_relative "helpers/stub_configuration"
require_relative "helpers/stub_metrics"
require_relative "helpers/stub_object_storage"
require_relative "helpers/stub_env"
-require_relative "helpers/expect_offense"
+require_relative "helpers/fast_rails_root"
+require 'rubocop/rspec/support'
RSpec.configure do |config|
config.mock_with :rspec
@@ -14,6 +15,7 @@ RSpec.configure do |config|
config.include StubMetrics
config.include StubObjectStorage
config.include StubENV
+ config.include FastRailsRoot
- config.include ExpectOffense, type: :rubocop
+ config.include RuboCop::RSpec::ExpectOffense, type: :rubocop
end
diff --git a/spec/support/services/clusters/create_service_shared.rb b/spec/support/services/clusters/create_service_shared.rb
index 31aee08baec..f8a58a828ce 100644
--- a/spec/support/services/clusters/create_service_shared.rb
+++ b/spec/support/services/clusters/create_service_shared.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
RSpec.shared_context 'valid cluster create params' do
+ let(:clusterable) { Clusters::Instance.new }
let(:params) do
{
name: 'test-cluster',
@@ -11,12 +12,14 @@ RSpec.shared_context 'valid cluster create params' do
num_nodes: 1,
machine_type: 'machine_type-a',
legacy_abac: 'true'
- }
+ },
+ clusterable: clusterable
}
end
end
RSpec.shared_context 'invalid cluster create params' do
+ let(:clusterable) { Clusters::Instance.new }
let(:params) do
{
name: 'test-cluster',
@@ -26,7 +29,9 @@ RSpec.shared_context 'invalid cluster create params' do
zone: 'us-central1-a',
num_nodes: 1,
machine_type: 'machine_type-a'
- }
+ },
+ clusterable: clusterable
+
}
end
end
diff --git a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb b/spec/support/services/issuable_description_quick_actions_shared_examples.rb
index 3d45fe06134..1970301e4c9 100644
--- a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
+++ b/spec/support/services/issuable_description_quick_actions_shared_examples.rb
@@ -3,20 +3,25 @@
# Specifications for behavior common to all objects with executable attributes.
# It can take a `default_params`.
-RSpec.shared_examples 'new issuable record that supports quick actions' do
- let!(:project) { create(:project, :repository) }
- let(:user) { create(:user).tap { |u| project.add_maintainer(u) } }
- let(:assignee) { create(:user) }
- let!(:milestone) { create(:milestone, project: project) }
- let!(:labels) { create_list(:label, 3, project: project) }
+RSpec.shared_examples 'issuable record that supports quick actions' do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:assignee) { create(:user) }
+ let_it_be(:milestone) { create(:milestone, project: project) }
+ let_it_be(:labels) { create_list(:label, 3, project: project) }
+
let(:base_params) { { title: 'My issuable title' } }
let(:params) { base_params.merge(defined?(default_params) ? default_params : {}).merge(example_params) }
- let(:issuable) { described_class.new(project, user, params).execute }
- before do
+ before_all do
+ project.add_maintainer(user)
project.add_maintainer(assignee)
end
+ before do
+ issuable.reload
+ end
+
context 'with labels in command only' do
let(:example_params) do
{
@@ -25,7 +30,6 @@ RSpec.shared_examples 'new issuable record that supports quick actions' do
end
it 'attaches labels to issuable' do
- expect(issuable).to be_persisted
expect(issuable.label_ids).to match_array([labels.first.id, labels.second.id])
end
end
@@ -39,7 +43,6 @@ RSpec.shared_examples 'new issuable record that supports quick actions' do
end
it 'attaches all labels to issuable' do
- expect(issuable).to be_persisted
expect(issuable.label_ids).to match_array([labels.first.id, labels.second.id])
end
end
@@ -52,22 +55,8 @@ RSpec.shared_examples 'new issuable record that supports quick actions' do
end
it 'assigns and sets milestone to issuable' do
- expect(issuable).to be_persisted
expect(issuable.assignees).to eq([assignee])
expect(issuable.milestone).to eq(milestone)
end
end
-
- describe '/close' do
- let(:example_params) do
- {
- description: '/close'
- }
- end
-
- it 'returns an open issue' do
- expect(issuable).to be_persisted
- expect(issuable).to be_open
- end
- end
end
diff --git a/spec/support/shared_contexts/cache_allowed_users_in_namespace_shared_context.rb b/spec/support/shared_contexts/cache_allowed_users_in_namespace_shared_context.rb
index 04f49e94647..e3c1d0afa53 100644
--- a/spec/support/shared_contexts/cache_allowed_users_in_namespace_shared_context.rb
+++ b/spec/support/shared_contexts/cache_allowed_users_in_namespace_shared_context.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'allowed user IDs are cached' do
+RSpec.shared_examples 'allowed user IDs are cached' do
it 'caches the allowed user IDs in cache', :use_clean_rails_memory_store_caching do
expect do
expect(described_class.l1_cache_backend).to receive(:fetch).and_call_original
diff --git a/spec/support/shared_contexts/design_management_shared_contexts.rb b/spec/support/shared_contexts/design_management_shared_contexts.rb
index 2866effb3a8..3ff6a521338 100644
--- a/spec/support/shared_contexts/design_management_shared_contexts.rb
+++ b/spec/support/shared_contexts/design_management_shared_contexts.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'four designs in three versions' do
+RSpec.shared_context 'four designs in three versions' do
include DesignManagementTestHelpers
let_it_be(:issue) { create(:issue) }
diff --git a/spec/support/shared_contexts/features/error_tracking_shared_context.rb b/spec/support/shared_contexts/features/error_tracking_shared_context.rb
index 102cf7c9b11..1f4eb3a6df9 100644
--- a/spec/support/shared_contexts/features/error_tracking_shared_context.rb
+++ b/spec/support/shared_contexts/features/error_tracking_shared_context.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'sentry error tracking context feature' do
+RSpec.shared_context 'sentry error tracking context feature' do
include ReactiveCachingHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
index 2b8daa80ab4..07b6b98222f 100644
--- a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
+++ b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
@@ -23,12 +23,12 @@ RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests
# We cannot use `let_it_be` here otherwise we get:
# Failure/Error: allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
# The use of doubles or partial doubles from rspec-mocks outside of the per-test lifecycle is not supported.
- let(:project2) do
+ let!(:project2) do
allow_gitaly_n_plus_1 do
fork_project(project1, user)
end
end
- let(:project3) do
+ let!(:project3) do
allow_gitaly_n_plus_1 do
fork_project(project1, user).tap do |project|
project.update!(archived: true)
@@ -45,6 +45,9 @@ RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests
allow_gitaly_n_plus_1 { create(:project, group: subgroup) }
end
+ let!(:label) { create(:label, project: project1) }
+ let!(:label2) { create(:label, project: project1) }
+
let!(:merge_request1) do
create(:merge_request, assignees: [user], author: user,
source_project: project2, target_project: project1,
@@ -72,6 +75,9 @@ RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests
title: '[WIP]')
end
+ let!(:label_link) { create(:label_link, label: label, target: merge_request2) }
+ let!(:label_link2) { create(:label_link, label: label2, target: merge_request3) }
+
before do
project1.add_maintainer(user)
project2.add_developer(user)
diff --git a/spec/support/shared_contexts/issuable/merge_request_shared_context.rb b/spec/support/shared_contexts/issuable/merge_request_shared_context.rb
index 05ffb934c34..79fc42e73c7 100644
--- a/spec/support/shared_contexts/issuable/merge_request_shared_context.rb
+++ b/spec/support/shared_contexts/issuable/merge_request_shared_context.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'merge request show action' do
+RSpec.shared_context 'merge request show action' do
include Devise::Test::ControllerHelpers
include ProjectForksHelper
diff --git a/spec/support/shared_contexts/issuable/project_shared_context.rb b/spec/support/shared_contexts/issuable/project_shared_context.rb
index 6dbf3154977..5e5f6f2b7a6 100644
--- a/spec/support/shared_contexts/issuable/project_shared_context.rb
+++ b/spec/support/shared_contexts/issuable/project_shared_context.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'project show action' do
+RSpec.shared_context 'project show action' do
let(:project) { create(:project, :repository) }
let(:issue) { create(:issue, project: project, author: user) }
let(:user) { create(:user) }
diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb
index 79b5ff44d4f..d9a72f2b54a 100644
--- a/spec/support/shared_contexts/navbar_structure_context.rb
+++ b/spec/support/shared_contexts/navbar_structure_context.rb
@@ -42,6 +42,7 @@ RSpec.shared_context 'project navbar structure' do
_('List'),
_('Boards'),
_('Labels'),
+ _('Service Desk'),
_('Milestones')
]
},
diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
index a0d54666dff..4b0c7afab6d 100644
--- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
@@ -18,7 +18,7 @@ RSpec.shared_context 'GroupPolicy context' do
]
end
let(:read_group_permissions) { %i[read_label read_list read_milestone read_board] }
- let(:reporter_permissions) { %i[admin_label read_container_image read_metrics_dashboard_annotation] }
+ let(:reporter_permissions) { %i[admin_label read_container_image read_metrics_dashboard_annotation read_prometheus] }
let(:developer_permissions) { %i[admin_milestone create_metrics_dashboard_annotation delete_metrics_dashboard_annotation update_metrics_dashboard_annotation] }
let(:maintainer_permissions) do
%i[
@@ -38,6 +38,7 @@ RSpec.shared_context 'GroupPolicy context' do
:update_default_branch_protection
].compact
end
+ let(:admin_permissions) { %i[read_confidential_issues] }
before_all do
group.add_guest(guest)
diff --git a/spec/support/shared_contexts/presenters/nuget_shared_context.rb b/spec/support/shared_contexts/presenters/nuget_shared_context.rb
new file mode 100644
index 00000000000..dd381db5a8b
--- /dev/null
+++ b/spec/support/shared_contexts/presenters/nuget_shared_context.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'with expected presenters dependency groups' do
+ def expected_dependency_groups(project_id, package_name, package_version)
+ [
+ {
+ id: "http://localhost/api/v4/projects/#{project_id}/packages/nuget/metadata/#{package_name}/#{package_version}.json#dependencyGroup/.netstandard2.0",
+ target_framework: '.NETStandard2.0',
+ type: 'PackageDependencyGroup',
+ dependencies: [
+ {
+ id: "http://localhost/api/v4/projects/#{project_id}/packages/nuget/metadata/#{package_name}/#{package_version}.json#dependencyGroup/.netstandard2.0/newtonsoft.json",
+ range: '12.0.3',
+ name: 'Newtonsoft.Json',
+ type: 'PackageDependency'
+ }
+ ]
+ },
+ {
+ id: "http://localhost/api/v4/projects/#{project_id}/packages/nuget/metadata/#{package_name}/#{package_version}.json#dependencyGroup",
+ type: 'PackageDependencyGroup',
+ dependencies: [
+ {
+ id: "http://localhost/api/v4/projects/#{project_id}/packages/nuget/metadata/#{package_name}/#{package_version}.json#dependencyGroup/castle.core",
+ range: '4.4.1',
+ name: 'Castle.Core',
+ type: 'PackageDependency'
+ }
+ ]
+ }
+ ]
+ end
+
+ def create_dependencies_for(package)
+ dependency1 = Packages::Dependency.find_by(name: 'Newtonsoft.Json', version_pattern: '12.0.3') || create(:packages_dependency, name: 'Newtonsoft.Json', version_pattern: '12.0.3')
+ dependency2 = Packages::Dependency.find_by(name: 'Castle.Core', version_pattern: '4.4.1') || create(:packages_dependency, name: 'Castle.Core', version_pattern: '4.4.1')
+
+ create(:packages_dependency_link, :with_nuget_metadatum, package: package, dependency: dependency1)
+ create(:packages_dependency_link, package: package, dependency: dependency2)
+ end
+end
diff --git a/spec/support/shared_contexts/project_service_jira_context.rb b/spec/support/shared_contexts/project_service_jira_context.rb
new file mode 100644
index 00000000000..4ca5c99323a
--- /dev/null
+++ b/spec/support/shared_contexts/project_service_jira_context.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'project service Jira context' do
+ let(:url) { 'http://jira.example.com' }
+ let(:test_url) { 'http://jira.example.com/rest/api/2/serverInfo' }
+
+ def fill_form(disable: false)
+ click_active_toggle if disable
+
+ fill_in 'service_url', with: url
+ fill_in 'service_username', with: 'username'
+ fill_in 'service_password', with: 'password'
+ fill_in 'service_jira_issue_transition_id', with: '25'
+ end
+end
diff --git a/spec/support/shared_contexts/project_service_shared_context.rb b/spec/support/shared_contexts/project_service_shared_context.rb
index 5b0dd26bd7b..a72d4901b72 100644
--- a/spec/support/shared_contexts/project_service_shared_context.rb
+++ b/spec/support/shared_contexts/project_service_shared_context.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'project service activation' do
+RSpec.shared_context 'project service activation' do
let(:project) { create(:project) }
let(:user) { create(:user) }
diff --git a/spec/support/shared_contexts/prometheus/alert_shared_context.rb b/spec/support/shared_contexts/prometheus/alert_shared_context.rb
new file mode 100644
index 00000000000..330d2c4515f
--- /dev/null
+++ b/spec/support/shared_contexts/prometheus/alert_shared_context.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+# These contexts expect a `project` to be defined.
+# It is expected that these contexts are used to create an
+# alert.
+RSpec.shared_context 'self-managed prometheus alert attributes' do
+ let_it_be(:environment) { create(:environment, project: project, name: 'production') }
+
+ let(:starts_at) { '2018-03-12T09:06:00Z' }
+ let(:title) { 'title' }
+ let(:y_label) { 'y_label' }
+ let(:query) { 'avg(metric) > 1.0' }
+
+ let(:embed_content) do
+ {
+ panel_groups: [{
+ panels: [{
+ type: 'line-graph',
+ title: title,
+ y_label: y_label,
+ metrics: [{ query_range: query }]
+ }]
+ }]
+ }.to_json
+ end
+
+ let(:payload) do
+ {
+ 'startsAt' => starts_at,
+ 'generatorURL' => "http://host?g0.expr=#{CGI.escape(query)}",
+ 'labels' => {
+ 'gitlab_environment_name' => 'production'
+ },
+ 'annotations' => {
+ 'title' => title,
+ 'gitlab_y_label' => y_label
+ }
+ }
+ end
+
+ let(:dashboard_url_for_alert) do
+ Gitlab::Routing.url_helpers.metrics_dashboard_project_environment_url(
+ project,
+ environment,
+ embed_json: embed_content,
+ embedded: true,
+ end: '2018-03-12T09:36:00Z',
+ start: '2018-03-12T08:36:00Z'
+ )
+ end
+end
+
+RSpec.shared_context 'gitlab-managed prometheus alert attributes' do
+ let_it_be(:prometheus_alert) { create(:prometheus_alert, project: project) }
+ let(:prometheus_metric_id) { prometheus_alert.prometheus_metric_id }
+
+ let(:payload) do
+ {
+ 'startsAt' => '2018-03-12T09:06:00Z',
+ 'labels' => {
+ 'gitlab_alert_id' => prometheus_metric_id
+ }
+ }
+ end
+
+ let(:dashboard_url_for_alert) do
+ Gitlab::Routing.url_helpers.metrics_dashboard_project_prometheus_alert_url(
+ project,
+ prometheus_metric_id,
+ environment_id: prometheus_alert.environment_id,
+ embedded: true,
+ end: '2018-03-12T09:36:00Z',
+ start: '2018-03-12T08:36:00Z'
+ )
+ end
+end
diff --git a/spec/support/shared_contexts/requests/api/graphql/jira_import/jira_projects_context.rb b/spec/support/shared_contexts/requests/api/graphql/jira_import/jira_projects_context.rb
index f0722beb3ed..7f150bed43d 100644
--- a/spec/support/shared_contexts/requests/api/graphql/jira_import/jira_projects_context.rb
+++ b/spec/support/shared_contexts/requests/api/graphql/jira_import/jira_projects_context.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'jira projects request context' do
+RSpec.shared_context 'jira projects request context' do
let(:url) { 'https://jira.example.com' }
let(:username) { 'jira-username' }
let(:password) { 'jira-password' }
@@ -74,6 +74,48 @@ shared_context 'jira projects request context' do
}'
end
+ let_it_be(:all_jira_projects_json) do
+ '[{
+ "expand": "description,lead,issueTypes,url,projectKeys,permissions,insight",
+ "self": "https://gitlab-jira.atlassian.net/rest/api/2/project/10000",
+ "id": "10000",
+ "key": "EX",
+ "name": "Example",
+ "avatarUrls": {
+ "48x48": "https://gitlab-jira.atlassian.net/secure/projectavatar?pid=10000&avatarId=10425",
+ "24x24": "https://gitlab-jira.atlassian.net/secure/projectavatar?size=small&s=small&pid=10000&avatarId=10425",
+ "16x16": "https://gitlab-jira.atlassian.net/secure/projectavatar?size=xsmall&s=xsmall&pid=10000&avatarId=10425",
+ "32x32": "https://gitlab-jira.atlassian.net/secure/projectavatar?size=medium&s=medium&pid=10000&avatarId=10425"
+ },
+ "projectTypeKey": "software",
+ "simplified": false,
+ "style": "classic",
+ "isPrivate": false,
+ "properties": {
+ }
+ },
+ {
+ "expand": "description,lead,issueTypes,url,projectKeys,permissions,insight",
+ "self": "https://gitlab-jira.atlassian.net/rest/api/2/project/10001",
+ "id": "10001",
+ "key": "ABC",
+ "name": "Alphabetical",
+ "avatarUrls": {
+ "48x48": "https://gitlab-jira.atlassian.net/secure/projectavatar?pid=10001&avatarId=10405",
+ "24x24": "https://gitlab-jira.atlassian.net/secure/projectavatar?size=small&s=small&pid=10001&avatarId=10405",
+ "16x16": "https://gitlab-jira.atlassian.net/secure/projectavatar?size=xsmall&s=xsmall&pid=10001&avatarId=10405",
+ "32x32": "https://gitlab-jira.atlassian.net/secure/projectavatar?size=medium&s=medium&pid=10001&avatarId=10405"
+ },
+ "projectTypeKey": "software",
+ "simplified": true,
+ "style": "next-gen",
+ "isPrivate": false,
+ "properties": {
+ },
+ "entityId": "14935009-f8aa-481e-94bc-f7251f320b0e",
+ "uuid": "14935009-f8aa-481e-94bc-f7251f320b0e"
+ }]'
+ end
let_it_be(:empty_jira_projects_json) do
'{
"self": "https://your-domain.atlassian.net/rest/api/2/project/search?startAt=0&maxResults=2",
@@ -86,10 +128,32 @@ shared_context 'jira projects request context' do
}'
end
+ let(:server_info_json) do
+ '{
+ "baseUrl": "https://gitlab-jira.atlassian.net",
+ "version": "1001.0.0-SNAPSHOT",
+ "versionNumbers": [
+ 1001,
+ 0,
+ 0
+ ],
+ "deploymentType": "Cloud",
+ "buildNumber": 100128,
+ "buildDate": "2020-06-03T01:58:44.000-0700",
+ "serverTime": "2020-06-04T06:15:13.686-0700",
+ "scmInfo": "e736ab140ddb281c7cf5dcf9062c9ce2c08b3c1c",
+ "serverTitle": "Jira",
+ "defaultLocale": {
+ "locale": "en_US"
+ }
+ }'
+ end
+
let(:test_url) { "#{url}/rest/api/2/project/search?maxResults=50&query=&startAt=0" }
let(:start_at_20_url) { "#{url}/rest/api/2/project/search?maxResults=50&query=&startAt=20" }
let(:start_at_1_url) { "#{url}/rest/api/2/project/search?maxResults=50&query=&startAt=1" }
let(:max_results_1_url) { "#{url}/rest/api/2/project/search?maxResults=1&query=&startAt=0" }
+ let(:all_projects_url) { "#{url}/rest/api/2/project" }
before do
WebMock.stub_request(:get, test_url).with(basic_auth: [username, password])
@@ -100,5 +164,9 @@ shared_context 'jira projects request context' do
.to_return(body: jira_projects_json, headers: { "Content-Type": "application/json" })
WebMock.stub_request(:get, max_results_1_url).with(basic_auth: [username, password])
.to_return(body: jira_projects_json, headers: { "Content-Type": "application/json" })
+ WebMock.stub_request(:get, all_projects_url).with(basic_auth: [username, password])
+ .to_return(body: all_jira_projects_json, headers: { "Content-Type": "application/json" })
+ WebMock.stub_request(:get, 'https://jira.example.com/rest/api/2/serverInfo')
+ .to_return(status: 200, body: server_info_json, headers: {})
end
end
diff --git a/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb b/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb
index f06de53f0c1..3453f954c9d 100644
--- a/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb
+++ b/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'sentry error tracking context' do
+RSpec.shared_context 'sentry error tracking context' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
diff --git a/spec/support/shared_contexts/services_shared_context.rb b/spec/support/shared_contexts/services_shared_context.rb
index bf4eac8f324..899b43ade01 100644
--- a/spec/support/shared_contexts/services_shared_context.rb
+++ b/spec/support/shared_contexts/services_shared_context.rb
@@ -12,6 +12,8 @@ Service.available_services_names.each do |service|
service_attrs_list.inject({}) do |hash, k|
if k =~ /^(token*|.*_token|.*_key)/
hash.merge!(k => 'secrettoken')
+ elsif service == 'confluence' && k == :confluence_url
+ hash.merge!(k => 'https://example.atlassian.net/wiki')
elsif k =~ /^(.*_url|url|webhook)/
hash.merge!(k => "http://example.com")
elsif service_klass.method_defined?("#{k}?")
diff --git a/spec/support/shared_contexts/spam_constants.rb b/spec/support/shared_contexts/spam_constants.rb
index 32371f4b92f..813f9d00123 100644
--- a/spec/support/shared_contexts/spam_constants.rb
+++ b/spec/support/shared_contexts/spam_constants.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_context 'includes Spam constants' do
+RSpec.shared_context 'includes Spam constants' do
before do
stub_const('CONDITIONAL_ALLOW', Spam::SpamConstants::CONDITIONAL_ALLOW)
stub_const('DISALLOW', Spam::SpamConstants::DISALLOW)
diff --git a/spec/support/shared_examples/controllers/import_controller_new_import_ui_shared_examples.rb b/spec/support/shared_examples/controllers/import_controller_new_import_ui_shared_examples.rb
deleted file mode 100644
index 88ad1f6cde2..00000000000
--- a/spec/support/shared_examples/controllers/import_controller_new_import_ui_shared_examples.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'import controller with new_import_ui feature flag' do
- include ImportSpecHelper
-
- context 'with new_import_ui feature flag enabled' do
- let(:group) { create(:group) }
-
- before do
- stub_feature_flags(new_import_ui: true)
- group.add_owner(user)
- end
-
- it "returns variables for json request" do
- project = create(:project, import_type: provider_name, creator_id: user.id)
- stub_client(client_repos_field => [repo])
-
- get :status, format: :json
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
- expect(json_response.dig("provider_repos", 0, "id")).to eq(repo_id)
- expect(json_response.dig("namespaces", 0, "id")).to eq(group.id)
- end
-
- it "does not show already added project" do
- project = create(:project, import_type: provider_name, namespace: user.namespace, import_status: :finished, import_source: import_source)
- stub_client(client_repos_field => [repo])
-
- get :status, format: :json
-
- expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
- expect(json_response.dig("provider_repos")).to eq([])
- end
- end
-end
diff --git a/spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb b/spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb
new file mode 100644
index 00000000000..ecb9abc5c46
--- /dev/null
+++ b/spec/support/shared_examples/controllers/import_controller_status_shared_examples.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'import controller status' do
+ include ImportSpecHelper
+
+ let(:group) { create(:group) }
+
+ before do
+ group.add_owner(user)
+ end
+
+ it "returns variables for json request" do
+ project = create(:project, import_type: provider_name, creator_id: user.id)
+ stub_client(client_repos_field => [repo])
+
+ get :status, format: :json
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
+ expect(json_response.dig("provider_repos", 0, "id")).to eq(repo_id)
+ expect(json_response.dig("namespaces", 0, "id")).to eq(group.id)
+ end
+
+ it "does not show already added project" do
+ project = create(:project, import_type: provider_name, namespace: user.namespace, import_status: :finished, import_source: import_source)
+ stub_client(client_repos_field => [repo])
+
+ get :status, format: :json
+
+ expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
+ expect(json_response.dig("provider_repos")).to eq([])
+ end
+end
diff --git a/spec/support/shared_examples/controllers/known_sign_in_shared_examples.rb b/spec/support/shared_examples/controllers/known_sign_in_shared_examples.rb
index 60abb76acec..7f26155f9d6 100644
--- a/spec/support/shared_examples/controllers/known_sign_in_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/known_sign_in_shared_examples.rb
@@ -9,13 +9,38 @@ RSpec.shared_examples 'known sign in' do
user.update!(current_sign_in_ip: ip)
end
- context 'with a valid post' do
- context 'when remote IP does not match user last sign in IP' do
- before do
- stub_user_ip('127.0.0.1')
- stub_remote_ip('169.0.0.1')
- end
+ def stub_cookie(value = user.id)
+ cookies.encrypted[KnownSignIn::KNOWN_SIGN_IN_COOKIE] = {
+ value: value, expires: KnownSignIn::KNOWN_SIGN_IN_COOKIE_EXPIRY
+ }
+ end
+
+ context 'when the remote IP and the last sign in IP match' do
+ before do
+ stub_user_ip('169.0.0.1')
+ stub_remote_ip('169.0.0.1')
+ end
+
+ it 'does not notify the user' do
+ expect(NotificationService).not_to receive(:new)
+ post_action
+ end
+
+ it 'sets/updates the encrypted cookie' do
+ post_action
+
+ expect(cookies.encrypted[KnownSignIn::KNOWN_SIGN_IN_COOKIE]).to eq(user.id)
+ end
+ end
+
+ context 'when the remote IP and the last sign in IP do not match' do
+ before do
+ stub_user_ip('127.0.0.1')
+ stub_remote_ip('169.0.0.1')
+ end
+
+ context 'when the cookie is not previously set' do
it 'notifies the user' do
expect_next_instance_of(NotificationService) do |instance|
expect(instance).to receive(:unknown_sign_in)
@@ -23,37 +48,68 @@ RSpec.shared_examples 'known sign in' do
post_action
end
- end
-
- context 'when remote IP matches an active session' do
- before do
- existing_sessions = ActiveSession.session_ids_for_user(user.id)
- existing_sessions.each { |sessions| ActiveSession.destroy(user, sessions) }
- stub_user_ip('169.0.0.1')
- stub_remote_ip('127.0.0.1')
+ it 'sets the encrypted cookie' do
+ post_action
- ActiveSession.set(user, request)
+ expect(cookies.encrypted[KnownSignIn::KNOWN_SIGN_IN_COOKIE]).to eq(user.id)
end
+ end
- it 'does not notify the user' do
- expect_any_instance_of(NotificationService).not_to receive(:unknown_sign_in)
+ it 'notifies the user when the cookie is expired' do
+ stub_cookie
+
+ Timecop.freeze((KnownSignIn::KNOWN_SIGN_IN_COOKIE_EXPIRY + 1.day).from_now) do
+ expect_next_instance_of(NotificationService) do |instance|
+ expect(instance).to receive(:unknown_sign_in)
+ end
post_action
end
end
- context 'when remote IP address matches last sign in IP' do
+ context 'when notify_on_unknown_sign_in global setting is false' do
before do
- stub_user_ip('127.0.0.1')
- stub_remote_ip('127.0.0.1')
+ stub_application_setting(notify_on_unknown_sign_in: false)
end
it 'does not notify the user' do
- expect_any_instance_of(NotificationService).not_to receive(:unknown_sign_in)
+ expect(NotificationService).not_to receive(:new)
+
+ post_action
+ end
+ it 'does not set a cookie' do
post_action
+
+ expect(cookies.encrypted[KnownSignIn::KNOWN_SIGN_IN_COOKIE]).to be_nil
+ end
+ end
+
+ it 'notifies the user when the cookie is for another user' do
+ stub_cookie(create(:user).id)
+
+ expect_next_instance_of(NotificationService) do |instance|
+ expect(instance).to receive(:unknown_sign_in)
end
+
+ post_action
+ end
+
+ it 'does not notify the user when remote IP matches an active session' do
+ ActiveSession.set(user, request)
+
+ expect(NotificationService).not_to receive(:new)
+
+ post_action
+ end
+
+ it 'does not notify the user when the cookie is present and not expired' do
+ stub_cookie
+
+ expect(NotificationService).not_to receive(:new)
+
+ post_action
end
end
end
diff --git a/spec/support/shared_examples/controllers/metrics/dashboard/prometheus_api_proxy_shared_examples.rb b/spec/support/shared_examples/controllers/metrics/dashboard/prometheus_api_proxy_shared_examples.rb
new file mode 100644
index 00000000000..94cd6971f7c
--- /dev/null
+++ b/spec/support/shared_examples/controllers/metrics/dashboard/prometheus_api_proxy_shared_examples.rb
@@ -0,0 +1,147 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'metrics dashboard prometheus api proxy' do
+ let(:service_params) { [proxyable, 'GET', 'query', expected_params] }
+ let(:service_result) { { status: :success, body: prometheus_body } }
+ let(:prometheus_proxy_service) { instance_double(Prometheus::ProxyService) }
+ let(:proxyable_params) do
+ {
+ id: proxyable.id.to_s
+ }
+ end
+ let(:expected_params) do
+ ActionController::Parameters.new(
+ prometheus_proxy_params(
+ proxy_path: 'query',
+ controller: described_class.controller_path,
+ action: 'prometheus_proxy'
+ )
+ ).permit!
+ end
+
+ before do
+ allow_next_instance_of(Prometheus::ProxyService, *service_params) do |proxy_service|
+ allow(proxy_service).to receive(:execute).and_return(service_result)
+ end
+ end
+
+ context 'with valid requests' do
+ context 'with success result' do
+ let(:prometheus_body) { '{"status":"success"}' }
+ let(:prometheus_json_body) { Gitlab::Json.parse(prometheus_body) }
+
+ it 'returns prometheus response' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(Prometheus::ProxyService).to have_received(:new).with(*service_params)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to eq(prometheus_json_body)
+ end
+
+ context 'with nil query' do
+ let(:params_without_query) do
+ prometheus_proxy_params.except(:query)
+ end
+
+ before do
+ expected_params.delete(:query)
+ end
+
+ it 'does not raise error' do
+ get :prometheus_proxy, params: params_without_query
+
+ expect(Prometheus::ProxyService).to have_received(:new).with(*service_params)
+ end
+ end
+ end
+
+ context 'with nil result' do
+ let(:service_result) { nil }
+
+ it 'returns 204 no_content' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(json_response['status']).to eq(_('processing'))
+ expect(json_response['message']).to eq(_('Not ready yet. Try again later.'))
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+ end
+
+ context 'with 404 result' do
+ let(:service_result) { { http_status: 404, status: :success, body: '{"body": "value"}' } }
+
+ it 'returns body' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['body']).to eq('value')
+ end
+ end
+
+ context 'with error result' do
+ context 'with http_status' do
+ let(:service_result) do
+ { http_status: :service_unavailable, status: :error, message: 'error message' }
+ end
+
+ it 'sets the http response status code' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(response).to have_gitlab_http_status(:service_unavailable)
+ expect(json_response['status']).to eq('error')
+ expect(json_response['message']).to eq('error message')
+ end
+ end
+
+ context 'without http_status' do
+ let(:service_result) { { status: :error, message: 'error message' } }
+
+ it 'returns bad_request' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['status']).to eq('error')
+ expect(json_response['message']).to eq('error message')
+ end
+ end
+ end
+ end
+
+ context 'with inappropriate requests' do
+ let(:prometheus_body) { nil }
+
+ context 'without correct permissions' do
+ let(:user2) { create(:user) }
+
+ before do
+ sign_out(user)
+ sign_in(user2)
+ end
+
+ it 'returns 404' do
+ get :prometheus_proxy, params: prometheus_proxy_params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'with invalid proxyable id' do
+ let(:prometheus_body) { nil }
+
+ it 'returns 404' do
+ get :prometheus_proxy, params: prometheus_proxy_params(id: proxyable.id + 1)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ private
+
+ def prometheus_proxy_params(params = {})
+ {
+ proxy_path: 'query',
+ query: '1'
+ }.merge(proxyable_params).merge(params)
+ end
+end
diff --git a/spec/support/shared_examples/controllers/metrics_dashboard_shared_examples.rb b/spec/support/shared_examples/controllers/metrics_dashboard_shared_examples.rb
new file mode 100644
index 00000000000..cb8f6721d66
--- /dev/null
+++ b/spec/support/shared_examples/controllers/metrics_dashboard_shared_examples.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'GET #metrics_dashboard correctly formatted response' do
+ it 'returns a json object with the correct keys' do
+ get :metrics_dashboard, params: metrics_dashboard_req_params, format: :json
+
+ # Exclude `all_dashboards` to handle separately, at spec/controllers/projects/environments_controller_spec.rb:565
+ # because `all_dashboards` key is not part of expected shared behavior
+ found_keys = json_response.keys - ['all_dashboards']
+
+ expect(response).to have_gitlab_http_status(status_code)
+ expect(found_keys).to contain_exactly(*expected_keys)
+ end
+end
+
+RSpec.shared_examples_for 'GET #metrics_dashboard for dashboard' do |dashboard_name|
+ let(:expected_keys) { %w(dashboard status metrics_data) }
+ let(:status_code) { :ok }
+
+ it_behaves_like 'GET #metrics_dashboard correctly formatted response'
+
+ it 'returns correct dashboard' do
+ get :metrics_dashboard, params: metrics_dashboard_req_params, format: :json
+
+ expect(json_response['dashboard']['dashboard']).to eq(dashboard_name)
+ end
+end
diff --git a/spec/support/shared_examples/controllers/namespace_storage_limit_alert_shared_examples.rb b/spec/support/shared_examples/controllers/namespace_storage_limit_alert_shared_examples.rb
deleted file mode 100644
index 7885eb6c1f8..00000000000
--- a/spec/support/shared_examples/controllers/namespace_storage_limit_alert_shared_examples.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'namespace storage limit alert' do
- let(:alert_level) { :info }
-
- before do
- allow_next_instance_of(Namespaces::CheckStorageSizeService, namespace, user) do |check_storage_size_service|
- expect(check_storage_size_service).to receive(:execute).and_return(
- ServiceResponse.success(
- payload: {
- alert_level: alert_level,
- usage_message: "Usage",
- explanation_message: "Explanation",
- root_namespace: namespace
- }
- )
- )
- end
-
- allow(controller).to receive(:current_user).and_return(user)
- end
-
- render_views
-
- it 'does render' do
- subject
-
- expect(response.body).to match(/Explanation/)
- expect(response.body).to have_css('.js-namespace-storage-alert-dismiss')
- end
-
- context 'when alert_level is error' do
- let(:alert_level) { :error }
-
- it 'does not render a dismiss button' do
- subject
-
- expect(response.body).not_to have_css('.js-namespace-storage-alert-dismiss')
- end
- end
-
- context 'when cookie is set' do
- before do
- cookies["hide_storage_limit_alert_#{namespace.id}_info"] = 'true'
- end
-
- it 'does not render alert' do
- subject
-
- expect(response.body).not_to match(/Explanation/)
- end
- end
-end
diff --git a/spec/support/shared_examples/controllers/snippet_blob_shared_examples.rb b/spec/support/shared_examples/controllers/snippet_blob_shared_examples.rb
new file mode 100644
index 00000000000..c3e8f807afb
--- /dev/null
+++ b/spec/support/shared_examples/controllers/snippet_blob_shared_examples.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'raw snippet blob' do
+ context 'with valid params' do
+ before do
+ subject
+ end
+
+ it 'delivers file with correct Workhorse headers' do
+ expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
+ expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq 'true'
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'with invalid file path' do
+ let(:filepath) { 'doesnotexist' }
+
+ it_behaves_like 'returning response status', :not_found
+ end
+
+ context 'with invalid ref' do
+ let(:ref) { 'doesnotexist' }
+
+ it_behaves_like 'returning response status', :not_found
+ end
+
+ it_behaves_like 'content disposition headers'
+end
+
+RSpec.shared_examples 'raw snippet without repository' do |unauthorized_status|
+ context 'when authorized' do
+ it 'returns a 422' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'when unauthorized' do
+ let(:visibility) { :private }
+
+ it_behaves_like 'returning response status', unauthorized_status
+ end
+end
diff --git a/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb b/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb
new file mode 100644
index 00000000000..aa4d78b23f4
--- /dev/null
+++ b/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'snippets sort order' do
+ let(:params) { {} }
+ let(:sort_argument) { {} }
+ let(:sort_params) { params.merge(sort_argument)}
+
+ before do
+ sign_in(user)
+
+ stub_snippet_counter
+ end
+
+ subject { get :index, params: sort_params }
+
+ context 'when no sort param is provided' do
+ it 'calls SnippetsFinder with updated_at sort option' do
+ expect(SnippetsFinder).to receive(:new).with(user,
+ hash_including(sort: 'updated_desc')).and_call_original
+
+ subject
+ end
+ end
+
+ context 'when sort param is provided' do
+ let(:order) { 'created_desc' }
+ let(:sort_argument) { { sort: order } }
+
+ it 'calls SnippetsFinder with the given sort param' do
+ expect(SnippetsFinder).to receive(:new).with(user,
+ hash_including(sort: order)).and_call_original
+
+ subject
+ end
+ end
+
+ def stub_snippet_counter
+ allow(Snippets::CountService)
+ .to receive(:new).and_return(double(:count_service, execute: {}))
+ end
+end
diff --git a/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb b/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb
new file mode 100644
index 00000000000..90588756eb0
--- /dev/null
+++ b/spec/support/shared_examples/controllers/unique_visits_shared_examples.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'tracking unique visits' do |method|
+ it 'tracks unique visit if the format is HTML' do
+ expect_any_instance_of(Gitlab::Analytics::UniqueVisits).to receive(:track_visit).with(instance_of(String), target_id)
+
+ get method, params: request_params, format: :html
+ end
+
+ it 'tracks unique visit if DNT is not enabled' do
+ expect_any_instance_of(Gitlab::Analytics::UniqueVisits).to receive(:track_visit).with(instance_of(String), target_id)
+ request.headers['DNT'] = '0'
+
+ get method, params: request_params, format: :html
+ end
+
+ it 'does not track unique visit if DNT is enabled' do
+ expect_any_instance_of(Gitlab::Analytics::UniqueVisits).not_to receive(:track_visit)
+ request.headers['DNT'] = '1'
+
+ get method, params: request_params, format: :html
+ end
+
+ it 'does not track unique visit if the format is JSON' do
+ expect_any_instance_of(Gitlab::Analytics::UniqueVisits).not_to receive(:track_visit)
+
+ get method, params: request_params, format: :json
+ end
+end
diff --git a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
index b5f2c0d07bf..4df3139d56e 100644
--- a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb
@@ -104,6 +104,35 @@ RSpec.shared_examples 'wiki controller actions' do
end
end
+ describe 'GET #diff' do
+ context 'when commit exists' do
+ it 'renders the diff' do
+ get :diff, params: routing_params.merge(id: wiki_title, version_id: wiki.repository.commit.id)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('shared/wikis/diff')
+ expect(assigns(:diffs)).to be_a(Gitlab::Diff::FileCollection::Base)
+ expect(assigns(:diff_notes_disabled)).to be(true)
+ end
+ end
+
+ context 'when commit does not exist' do
+ it 'returns a 404 error' do
+ get :diff, params: routing_params.merge(id: wiki_title, version_id: 'invalid')
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when page does not exist' do
+ it 'returns a 404 error' do
+ get :diff, params: routing_params.merge(id: 'invalid')
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
describe 'GET #show' do
render_views
@@ -118,6 +147,7 @@ RSpec.shared_examples 'wiki controller actions' do
subject
expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('shared/wikis/show')
expect(assigns(:page).title).to eq(wiki_title)
expect(assigns(:sidebar_wiki_entries)).to contain_exactly(an_instance_of(WikiPage))
expect(assigns(:sidebar_limited)).to be(false)
@@ -130,6 +160,7 @@ RSpec.shared_examples 'wiki controller actions' do
subject
expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('shared/wikis/show')
expect(flash[:notice]).to eq(_('The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.'))
end
end
@@ -138,19 +169,37 @@ RSpec.shared_examples 'wiki controller actions' do
context 'when the page does not exist' do
let(:id) { 'does not exist' }
- before do
- subject
- end
+ context 'when the user can create pages' do
+ before do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('shared/wikis/edit')
+ end
+
+ it 'builds a new wiki page with the id as the title' do
+ expect(assigns(:page).title).to eq(id)
+ end
+
+ context 'when a random_title param is present' do
+ let(:random_title) { true }
- it 'builds a new wiki page with the id as the title' do
- expect(assigns(:page).title).to eq(id)
+ it 'builds a new wiki page with no title' do
+ expect(assigns(:page).title).to be_empty
+ end
+ end
end
- context 'when a random_title param is present' do
- let(:random_title) { true }
+ context 'when the user cannot create pages' do
+ before do
+ sign_out(:user)
+ end
- it 'builds a new wiki page with no title' do
- expect(assigns(:page).title).to be_empty
+ it 'shows the empty state' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('shared/wikis/empty')
end
end
end
@@ -166,6 +215,7 @@ RSpec.shared_examples 'wiki controller actions' do
it 'delivers the file with the correct headers' do
subject
+ expect(response).to have_gitlab_http_status(:ok)
expect(response.headers['Content-Disposition']).to match(/^inline/)
expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq('true')
expect(response.cache_control[:public]).to be(false)
@@ -179,12 +229,31 @@ RSpec.shared_examples 'wiki controller actions' do
it 'renders json in a correct format' do
post :preview_markdown, params: routing_params.merge(id: 'page/path', text: '*Markdown* text')
+ expect(response).to have_gitlab_http_status(:ok)
expect(json_response.keys).to match_array(%w(body references))
end
end
- describe 'GET #edit' do
- subject { get(:edit, params: routing_params.merge(id: wiki_title)) }
+ shared_examples 'edit action' do
+ context 'when the page does not exist' do
+ let(:id_param) { 'invalid' }
+
+ it 'redirects to show' do
+ subject
+
+ expect(response).to redirect_to_wiki(wiki, 'invalid')
+ end
+ end
+
+ context 'when id param is blank' do
+ let(:id_param) { ' ' }
+
+ it 'redirects to the home page' do
+ subject
+
+ expect(response).to redirect_to_wiki(wiki, 'home')
+ end
+ end
context 'when page content encoding is invalid' do
it 'redirects to show' do
@@ -208,6 +277,14 @@ RSpec.shared_examples 'wiki controller actions' do
expect(response).to redirect_to_wiki(wiki, page)
end
end
+ end
+
+ describe 'GET #edit' do
+ let(:id_param) { wiki_title }
+
+ subject { get(:edit, params: routing_params.merge(id: id_param)) }
+
+ it_behaves_like 'edit action'
context 'when page content encoding is valid' do
render_views
@@ -224,23 +301,17 @@ RSpec.shared_examples 'wiki controller actions' do
describe 'PATCH #update' do
let(:new_title) { 'New title' }
let(:new_content) { 'New content' }
+ let(:id_param) { wiki_title }
subject do
patch(:update,
params: routing_params.merge(
- id: wiki_title,
+ id: id_param,
wiki: { title: new_title, content: new_content }
))
end
- context 'when page content encoding is invalid' do
- it 'redirects to show' do
- allow(controller).to receive(:valid_encoding?).and_return(false)
-
- subject
- expect(response).to redirect_to_wiki(wiki, wiki.list_pages.first)
- end
- end
+ it_behaves_like 'edit action'
context 'when page content encoding is valid' do
render_views
diff --git a/spec/support/shared_examples/create_alert_issue_shared_examples.rb b/spec/support/shared_examples/create_alert_issue_shared_examples.rb
new file mode 100644
index 00000000000..9f4e1c4335a
--- /dev/null
+++ b/spec/support/shared_examples/create_alert_issue_shared_examples.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'create alert issue sets issue labels' do
+ let(:title) { IncidentManagement::CreateIncidentLabelService::LABEL_PROPERTIES[:title] }
+ let!(:label) { create(:label, project: project, title: title) }
+ let(:label_service) { instance_double(IncidentManagement::CreateIncidentLabelService, execute: label_service_response) }
+
+ before do
+ allow(IncidentManagement::CreateIncidentLabelService).to receive(:new).with(project, user).and_return(label_service)
+ end
+
+ context 'when create incident label responds with success' do
+ let(:label_service_response) { ServiceResponse.success(payload: { label: label }) }
+
+ it 'adds label to issue' do
+ expect(issue.labels).to eq([label])
+ end
+ end
+
+ context 'when create incident label responds with error' do
+ let(:label_service_response) { ServiceResponse.error(payload: { label: label }, message: 'label error') }
+
+ it 'creates an issue without labels' do
+ expect(issue.labels).to be_empty
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/discussion_comments_shared_example.rb b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
index 6007798c290..9fc5d8933e5 100644
--- a/spec/support/shared_examples/features/discussion_comments_shared_example.rb
+++ b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
@@ -266,7 +266,7 @@ RSpec.shared_examples 'thread comments' do |resource_name|
end
end
- it 'has "Comment" selected when opening the menu', quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/196825' do
+ it 'has "Comment" selected when opening the menu', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/196825' do
find(toggle_selector).click
find("#{menu_selector} li", match: :first)
diff --git a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
index 964c80007b0..487c38da7da 100644
--- a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
@@ -53,13 +53,17 @@ RSpec.shared_examples 'an editable merge request' do
find('#merge_request_description').native.send_keys('')
fill_in 'merge_request_description', with: user.to_reference[0..4]
- wait_for_requests
-
page.within('.atwho-view') do
expect(page).to have_content(user2.name)
end
end
+ it 'description has quick action autocomplete', :js do
+ find('#merge_request_description').native.send_keys('/')
+
+ expect(page).to have_selector('.atwho-container')
+ end
+
it 'has class js-quick-submit in form' do
expect(page).to have_selector('.js-quick-submit')
end
diff --git a/spec/support/shared_examples/features/error_tracking_shared_example.rb b/spec/support/shared_examples/features/error_tracking_shared_example.rb
index 1cd05b22ae9..ae7d62f31a2 100644
--- a/spec/support/shared_examples/features/error_tracking_shared_example.rb
+++ b/spec/support/shared_examples/features/error_tracking_shared_example.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'error tracking index page' do
+RSpec.shared_examples 'error tracking index page' do
it 'renders the error index page', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217810' } do
within('div.js-title-container') do
expect(page).to have_content(project.namespace.name)
@@ -33,7 +33,7 @@ shared_examples 'error tracking index page' do
end
end
-shared_examples 'expanded stack trace context' do |selected_line: nil, expected_line: 1|
+RSpec.shared_examples 'expanded stack trace context' do |selected_line: nil, expected_line: 1|
it 'expands the stack trace context', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217810' } do
within('div.stacktrace') do
find("div.file-holder:nth-child(#{selected_line}) svg.ic-chevron-right").click if selected_line
@@ -48,7 +48,7 @@ shared_examples 'expanded stack trace context' do |selected_line: nil, expected_
end
end
-shared_examples 'error tracking show page' do
+RSpec.shared_examples 'error tracking show page' do
it 'renders the error details', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217810' } do
content = page.find(".content")
nav = page.find("nav.breadcrumbs")
diff --git a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
index 98010150e65..00ce690d2e3 100644
--- a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
+++ b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
@@ -35,7 +35,12 @@ RSpec.shared_examples 'Maintainer manages access requests' do
expect_visible_access_request(entity, user)
- accept_confirm { click_on 'Deny access' }
+ # Open modal
+ click_on 'Deny access request'
+
+ expect(page).not_to have_field "Also unassign this user from related issues and merge requests"
+
+ click_on 'Deny access request'
expect_no_visible_access_request(entity, user)
expect(page).not_to have_content user.name
diff --git a/spec/support/shared_examples/graphql/jira_import/jira_import_resolver_shared_examples.rb b/spec/support/shared_examples/graphql/jira_import/jira_import_resolver_shared_examples.rb
index 2b96010477c..b2047f1d32c 100644
--- a/spec/support/shared_examples/graphql/jira_import/jira_import_resolver_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/jira_import/jira_import_resolver_shared_examples.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
-shared_examples 'no Jira import data present' do
+RSpec.shared_examples 'no Jira import data present' do
it 'returns none' do
expect(resolve_imports).to eq JiraImportState.none
end
end
-shared_examples 'no Jira import access' do
+RSpec.shared_examples 'no Jira import access' do
it 'raises error' do
expect do
resolve_imports
diff --git a/spec/support/shared_examples/graphql/mutation_shared_examples.rb b/spec/support/shared_examples/graphql/mutation_shared_examples.rb
index 022d41c0bdd..86d2bb6c747 100644
--- a/spec/support/shared_examples/graphql/mutation_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/mutation_shared_examples.rb
@@ -7,13 +7,23 @@
#
# There must be a method or let called `mutation` defined that executes
# the mutation.
-RSpec.shared_examples 'a mutation that returns top-level errors' do |errors:|
+RSpec.shared_examples 'a mutation that returns top-level errors' do |errors: []|
+ let(:match_errors) { eq(errors) }
+
it do
post_graphql_mutation(mutation, current_user: current_user)
error_messages = graphql_errors.map { |e| e['message'] }
- expect(error_messages).to eq(errors)
+ expect(error_messages).to match_errors
+ end
+end
+
+RSpec.shared_examples 'an invalid argument to the mutation' do |argument_name:|
+ it_behaves_like 'a mutation that returns top-level errors' do
+ let(:match_errors) do
+ contain_exactly(include("invalid value for #{GraphqlHelpers.fieldnamerize(argument_name)}"))
+ end
end
end
diff --git a/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb b/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb
index 4bed322564a..94b7ed1618d 100644
--- a/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/projects/services_resolver_shared_examples.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
-shared_examples 'no project services' do
+RSpec.shared_examples 'no project services' do
it 'returns empty collection' do
expect(resolve_services).to eq []
end
end
-shared_examples 'cannot access project services' do
+RSpec.shared_examples 'cannot access project services' do
it 'raises error' do
expect do
resolve_services
diff --git a/spec/support/shared_examples/graphql/resolves_issuable_shared_examples.rb b/spec/support/shared_examples/graphql/resolves_issuable_shared_examples.rb
index 58cd3d21f66..67d1c2a8254 100644
--- a/spec/support/shared_examples/graphql/resolves_issuable_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/resolves_issuable_shared_examples.rb
@@ -1,7 +1,12 @@
# frozen_string_literal: true
RSpec.shared_examples 'resolving an issuable in GraphQL' do |type|
- subject { mutation.resolve_issuable(type: type, parent_path: parent.full_path, iid: issuable.iid) }
+ include GraphqlHelpers
+
+ let(:parent_path) { parent.full_path }
+ let(:iid) { issuable.iid }
+
+ subject(:result) { mutation.resolve_issuable(type: type, parent_path: parent_path, iid: iid) }
context 'when user has access' do
before do
@@ -9,37 +14,23 @@ RSpec.shared_examples 'resolving an issuable in GraphQL' do |type|
end
it 'resolves issuable by iid' do
- result = type == :merge_request ? subject.sync : subject
expect(result).to eq(issuable)
end
- it 'uses the correct Resolver to resolve issuable' do
- resolver_class = "Resolvers::#{type.to_s.classify.pluralize}Resolver".constantize
- resolve_method = type == :epic ? :resolve_group : :resolve_project
- resolved_parent = mutation.send(resolve_method, full_path: parent.full_path)
-
- allow(mutation).to receive(resolve_method)
- .with(full_path: parent.full_path)
- .and_return(resolved_parent)
-
- expect(resolver_class.single).to receive(:new)
- .with(object: resolved_parent, context: context, field: nil)
- .and_call_original
-
- subject
- end
-
- it 'returns nil if issuable is not found' do
- result = mutation.resolve_issuable(type: type, parent_path: parent.full_path, iid: "100")
- result = result.respond_to?(:sync) ? result.sync : result
+ context 'the IID does not refer to a valid issuable' do
+ let(:iid) { '100' }
- expect(result).to be_nil
+ it 'returns nil' do
+ expect(result).to be_nil
+ end
end
- it 'returns nil if parent path is not present' do
- result = mutation.resolve_issuable(type: type, parent_path: "", iid: issuable.iid)
+ context 'the parent path is not present' do
+ let(:parent_path) { '' }
- expect(result).to be_nil
+ it 'returns nil' do
+ expect(result).to be_nil
+ end
end
end
end
diff --git a/spec/support/shared_examples/helm_commands_shared_examples.rb b/spec/support/shared_examples/helm_commands_shared_examples.rb
index f0624fbf29f..0a94c6648cc 100644
--- a/spec/support/shared_examples/helm_commands_shared_examples.rb
+++ b/spec/support/shared_examples/helm_commands_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'helm command generator' do
+RSpec.shared_examples 'helm command generator' do
describe '#generate_script' do
let(:helm_setup) do
<<~EOS
@@ -14,7 +14,7 @@ shared_examples 'helm command generator' do
end
end
-shared_examples 'helm command' do
+RSpec.shared_examples 'helm command' do
describe '#rbac?' do
subject { command.rbac? }
diff --git a/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb
index a40c38106e2..af65b61021c 100644
--- a/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'resource mentions migration' do |migration_class, resource_class|
+RSpec.shared_examples 'resource mentions migration' do |migration_class, resource_class|
it 'migrates resource mentions' do
join = migration_class::JOIN
conditions = migration_class::QUERY_CONDITIONS
@@ -21,7 +21,7 @@ shared_examples 'resource mentions migration' do |migration_class, resource_clas
end
end
-shared_examples 'resource notes mentions migration' do |migration_class, resource_class|
+RSpec.shared_examples 'resource notes mentions migration' do |migration_class, resource_class|
it 'migrates mentions from note' do
join = migration_class::JOIN
conditions = migration_class::QUERY_CONDITIONS
@@ -56,7 +56,7 @@ shared_examples 'resource notes mentions migration' do |migration_class, resourc
end
end
-shared_examples 'schedules resource mentions migration' do |resource_class, is_for_notes|
+RSpec.shared_examples 'schedules resource mentions migration' do |resource_class, is_for_notes|
before do
stub_const("#{described_class.name}::BATCH_SIZE", 1)
end
diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/base_stage_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/base_stage_shared_examples.rb
index 14292f70228..d76089d56dd 100644
--- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/base_stage_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/base_stage_shared_examples.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
ISSUES_MEDIAN = 30.minutes.to_i
-shared_examples 'base stage' do
+RSpec.shared_examples 'base stage' do
let(:stage) { described_class.new(options: { project: double }) }
before do
@@ -35,7 +35,7 @@ shared_examples 'base stage' do
end
end
-shared_examples 'calculate #median with date range' do
+RSpec.shared_examples 'calculate #median with date range' do
context 'when valid date range is given' do
before do
stage_options[:from] = 5.days.ago
@@ -55,7 +55,7 @@ shared_examples 'calculate #median with date range' do
end
end
-shared_examples 'Gitlab::Analytics::CycleAnalytics::DataCollector backend examples' do
+RSpec.shared_examples 'Gitlab::Analytics::CycleAnalytics::DataCollector backend examples' do
let(:stage_params) { Gitlab::Analytics::CycleAnalytics::DefaultStages.send("params_for_#{stage_name}_stage").merge(project: project) }
let(:stage) { Analytics::CycleAnalytics::ProjectStage.new(stage_params) }
let(:data_collector) { Gitlab::Analytics::CycleAnalytics::DataCollector.new(stage: stage, params: { from: stage_options[:from], current_user: project.creator }) }
diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/default_query_config_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/default_query_config_shared_examples.rb
index c053af010b3..4f648b27ea2 100644
--- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/default_query_config_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/default_query_config_shared_examples.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-shared_examples 'default query config' do
+RSpec.shared_examples 'default query config' do
let(:project) { create(:project) }
let(:event) { described_class.new(stage: stage_name, options: { from: 1.day.ago, project: project }) }
diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
index a00359ce979..d0e41605e00 100644
--- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
@@ -8,6 +8,7 @@ RSpec.shared_examples_for 'cycle analytics event' do
it { expect(described_class.identifier).to be_a_kind_of(Symbol) }
it { expect(instance.object_type.ancestors).to include(ApplicationRecord) }
it { expect(instance).to respond_to(:timestamp_projection) }
+ it { expect(instance.column_list).to be_a_kind_of(Array) }
describe '#apply_query_customization' do
it 'expects an ActiveRecord::Relation object as argument and returns a modified version of it' do
diff --git a/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb
index a1cdd054f32..e43ce936b90 100644
--- a/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb
@@ -10,7 +10,7 @@ RSpec.shared_examples 'diff statistics' do |test_include_stats_flag: true|
end
end
- context 'when should request diff stats' do
+ context 'when include_stats is true' do
it 'Repository#diff_stats is called' do
expect(diffable.project.repository)
.to receive(:diff_stats)
@@ -59,43 +59,87 @@ RSpec.shared_examples 'unfoldable diff' do
end
RSpec.shared_examples 'cacheable diff collection' do
- let(:cache) { instance_double(Gitlab::Diff::HighlightCache) }
+ let(:highlight_cache) { instance_double(Gitlab::Diff::HighlightCache, write_if_empty: true, clear: nil, decorate: nil) }
+ let(:stats_cache) { instance_double(Gitlab::Diff::StatsCache, read: nil, write_if_empty: true, clear: nil) }
before do
- expect(Gitlab::Diff::HighlightCache).to receive(:new).with(subject) { cache }
+ expect(Gitlab::Diff::HighlightCache).to receive(:new).with(subject) { highlight_cache }
end
describe '#write_cache' do
+ before do
+ expect(Gitlab::Diff::StatsCache).to receive(:new).with(cachable_key: diffable.cache_key) { stats_cache }
+ end
+
it 'calls Gitlab::Diff::HighlightCache#write_if_empty' do
- expect(cache).to receive(:write_if_empty).once
+ expect(highlight_cache).to receive(:write_if_empty).once
+
+ subject.write_cache
+ end
+
+ it 'calls Gitlab::Diff::StatsCache#write_if_empty with diff stats' do
+ diff_stats = Gitlab::Git::DiffStatsCollection.new([])
+
+ expect(diffable.project.repository)
+ .to receive(:diff_stats).and_return(diff_stats)
+
+ expect(stats_cache).to receive(:write_if_empty).once.with(diff_stats)
subject.write_cache
end
end
describe '#clear_cache' do
+ before do
+ expect(Gitlab::Diff::StatsCache).to receive(:new).with(cachable_key: diffable.cache_key) { stats_cache }
+ end
+
it 'calls Gitlab::Diff::HighlightCache#clear' do
- expect(cache).to receive(:clear).once
+ expect(highlight_cache).to receive(:clear).once
subject.clear_cache
end
- end
- describe '#cache_key' do
- it 'calls Gitlab::Diff::HighlightCache#key' do
- expect(cache).to receive(:key).once
+ it 'calls Gitlab::Diff::StatsCache#clear' do
+ expect(stats_cache).to receive(:clear).once
- subject.cache_key
+ subject.clear_cache
end
end
describe '#diff_files' do
+ before do
+ expect(Gitlab::Diff::StatsCache).to receive(:new).with(cachable_key: diffable.cache_key) { stats_cache }
+ end
+
it 'calls Gitlab::Diff::HighlightCache#decorate' do
- expect(cache).to receive(:decorate)
+ expect(highlight_cache).to receive(:decorate)
.with(instance_of(Gitlab::Diff::File))
.exactly(cacheable_files_count).times
subject.diff_files
end
+
+ context 'when there are stats cached' do
+ before do
+ allow(stats_cache).to receive(:read).and_return(Gitlab::Git::DiffStatsCollection.new([]))
+ end
+
+ it 'does not make a diff stats rpc call' do
+ expect(diffable.project.repository).not_to receive(:diff_stats)
+
+ subject.diff_files
+ end
+ end
+
+ context 'when there are no stats cached' do
+ it 'makes a diff stats rpc call' do
+ expect(diffable.project.repository)
+ .to receive(:diff_stats)
+ .with(diffable.diff_refs.base_sha, diffable.diff_refs.head_sha)
+
+ subject.diff_files
+ end
+ end
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/gl_repository_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/gl_repository_shared_examples.rb
index 97f4341340d..28137530038 100644
--- a/spec/support/shared_examples/lib/gitlab/gl_repository_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/gl_repository_shared_examples.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
RSpec.shared_examples 'parsing gl_repository identifier' do
- subject { described_class.new(identifier) }
+ subject { described_class.parse(identifier) }
it 'returns correct information' do
- aggregate_failures do
- expect(subject.repo_type).to eq(expected_type)
- expect(subject.fetch_container!).to eq(expected_container)
- end
+ expect(subject).to have_attributes(
+ repo_type: expected_type,
+ container: expected_container
+ )
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/import/stuck_import_job_workers_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/import/stuck_import_job_workers_shared_examples.rb
index 06ea540706a..222390cf9cd 100644
--- a/spec/support/shared_examples/lib/gitlab/import/stuck_import_job_workers_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/import/stuck_import_job_workers_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'stuck import job detection' do
+RSpec.shared_examples 'stuck import job detection' do
context 'when the job has completed' do
context 'when the import status was already updated' do
before do
diff --git a/spec/support/shared_examples/lib/gitlab/jira_import/base_importer_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/jira_import/base_importer_shared_examples.rb
index 85dcc053447..b1788bb5912 100644
--- a/spec/support/shared_examples/lib/gitlab/jira_import/base_importer_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/jira_import/base_importer_shared_examples.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-shared_examples 'raise exception if not implemented' do
+RSpec.shared_examples 'raise exception if not implemented' do
it { expect { described_class.new(project).imported_items_cache_key }.not_to raise_error }
end
diff --git a/spec/support/shared_examples/lib/wikis_api_examples.rb b/spec/support/shared_examples/lib/wikis_api_examples.rb
new file mode 100644
index 00000000000..2e4c667d37e
--- /dev/null
+++ b/spec/support/shared_examples/lib/wikis_api_examples.rb
@@ -0,0 +1,174 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'wikis API returns list of wiki pages' do
+ context 'when wiki has pages' do
+ let!(:pages) do
+ [create(:wiki_page, wiki: wiki, title: 'page1', content: 'content of page1'),
+ create(:wiki_page, wiki: wiki, title: 'page2.with.dot', content: 'content of page2')]
+ end
+
+ it 'returns the list of wiki pages without content' do
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.size).to eq(2)
+
+ json_response.each_with_index do |page, index|
+ expect(page.keys).to match_array(expected_keys_without_content)
+ expect(page['slug']).to eq(pages[index].slug)
+ expect(page['title']).to eq(pages[index].title)
+ end
+ end
+
+ it 'returns the list of wiki pages with content' do
+ get api(url, user), params: { with_content: 1 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.size).to eq(2)
+
+ json_response.each_with_index do |page, index|
+ expect(page.keys).to match_array(expected_keys_with_content)
+ expect(page['content']).to eq(pages[index].content)
+ expect(page['slug']).to eq(pages[index].slug)
+ expect(page['title']).to eq(pages[index].title)
+ end
+ end
+ end
+
+ it 'return the empty list of wiki pages' do
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.size).to eq(0)
+ end
+end
+
+RSpec.shared_examples_for 'wikis API returns wiki page' do
+ it 'returns the wiki page' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.size).to eq(4)
+ expect(json_response.keys).to match_array(expected_keys_with_content)
+ expect(json_response['content']).to eq(page.content)
+ expect(json_response['slug']).to eq(page.slug)
+ expect(json_response['title']).to eq(page.title)
+ end
+end
+
+RSpec.shared_examples_for 'wikis API creates wiki page' do
+ it 'creates the wiki page' do
+ post(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response.size).to eq(4)
+ expect(json_response.keys).to match_array(expected_keys_with_content)
+ expect(json_response['content']).to eq(payload[:content])
+ expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
+ expect(json_response['title']).to eq(payload[:title])
+ expect(json_response['rdoc']).to eq(payload[:rdoc])
+ end
+
+ [:title, :content].each do |part|
+ it "responds with validation error on empty #{part}" do
+ payload.delete(part)
+
+ post(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response.size).to eq(1)
+ expect(json_response['error']).to eq("#{part} is missing")
+ end
+ end
+end
+
+RSpec.shared_examples_for 'wikis API updates wiki page' do
+ it 'updates the wiki page' do
+ put(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.size).to eq(4)
+ expect(json_response.keys).to match_array(expected_keys_with_content)
+ expect(json_response['content']).to eq(payload[:content])
+ expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
+ expect(json_response['title']).to eq(payload[:title])
+ end
+
+ [:title, :content, :format].each do |part|
+ it "updates with wiki with missing #{part}" do
+ payload.delete(part)
+
+ put(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+end
+
+RSpec.shared_examples_for 'wiki API 403 Forbidden' do
+ it 'returns 403 Forbidden' do
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(json_response.size).to eq(1)
+ expect(json_response['message']).to eq('403 Forbidden')
+ end
+end
+
+RSpec.shared_examples_for 'wiki API 404 Wiki Page Not Found' do
+ it 'returns 404 Wiki Page Not Found' do
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response.size).to eq(1)
+ expect(json_response['message']).to eq('404 Wiki Page Not Found')
+ end
+end
+
+RSpec.shared_examples_for 'wiki API 404 Not Found' do |what|
+ it "returns 404 #{what} Not Found" do
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response.size).to eq(1)
+ expect(json_response['message']).to eq("404 #{what} Not Found")
+ end
+end
+
+RSpec.shared_examples_for 'wiki API 204 No Content' do
+ it 'returns 204 No Content' do
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+end
+
+RSpec.shared_examples_for 'wiki API uploads wiki attachment' do
+ it 'pushes attachment to the wiki repository' do
+ allow(SecureRandom).to receive(:hex).and_return('fixed_hex')
+
+ workhorse_post_with_file(api(url, user), file_key: :file, params: payload)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to eq result_hash.deep_stringify_keys
+ end
+
+ it 'responds with validation error on empty file' do
+ payload.delete(:file)
+
+ post(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response.size).to eq(1)
+ expect(json_response['error']).to eq('file is missing')
+ end
+
+ it 'responds with validation error on invalid temp file' do
+ payload[:file] = { tempfile: '/etc/hosts' }
+
+ post(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response.size).to eq(1)
+ expect(json_response['error']).to eq('file is invalid')
+ end
+
+ it 'is backward compatible with regular multipart uploads' do
+ allow(SecureRandom).to receive(:hex).and_return('fixed_hex')
+
+ post(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to eq result_hash.deep_stringify_keys
+ end
+end
diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
index 0efa5e56199..f80ca235220 100644
--- a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
@@ -88,16 +88,6 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
end
end
- it 'sets the correct version of the application' do
- subject.update!(version: '0.0.0')
-
- subject.make_installed!
-
- subject.reload
-
- expect(subject.version).to eq(subject.class.const_get(:VERSION, false))
- end
-
context 'application is updating' do
subject { create(application_name, :updating) }
@@ -146,16 +136,6 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
end
end
end
-
- it 'updates the version of the application' do
- subject.update!(version: '0.0.0')
-
- subject.make_installed!
-
- subject.reload
-
- expect(subject.version).to eq(subject.class.const_get(:VERSION, false))
- end
end
end
diff --git a/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb
index cf7010c48c2..ed2e4fee2de 100644
--- a/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb
@@ -19,4 +19,32 @@ RSpec.shared_examples 'cluster application version specs' do |application_name|
it { is_expected.to be_falsey }
end
end
+
+ describe '#make_installed' do
+ subject { create(application_name, :installing) }
+
+ it 'sets the correct version of the application' do
+ subject.update!(version: '0.0.0')
+
+ subject.make_installed!
+
+ subject.reload
+
+ expect(subject.version).to eq(subject.class.const_get(:VERSION, false))
+ end
+
+ context 'application is updating' do
+ subject { create(application_name, :updating) }
+
+ it 'updates the version of the application' do
+ subject.update!(version: '0.0.0')
+
+ subject.make_installed!
+
+ subject.reload
+
+ expect(subject.version).to eq(subject.class.const_get(:VERSION, false))
+ end
+ end
+ end
end
diff --git a/spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb b/spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb
index 7bcd6191f1d..3db5d7a8d7d 100644
--- a/spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb
@@ -7,17 +7,17 @@ RSpec.shared_examples 'a BulkInsertSafe model' do |klass|
let(:target_class) { klass.dup }
# We consider all callbacks unsafe for bulk insertions unless we have explicitly
- # whitelisted them (esp. anything related to :save, :create, :commit etc.)
- let(:callback_method_blacklist) do
+ # allowed them (especially anything related to :save, :create, :commit, etc.)
+ let(:unsafe_callbacks) do
ActiveRecord::Callbacks::CALLBACKS.reject do |callback|
cb_name = callback.to_s.gsub(/(before_|after_|around_)/, '').to_sym
- BulkInsertSafe::CALLBACK_NAME_WHITELIST.include?(cb_name)
+ BulkInsertSafe::ALLOWED_CALLBACKS.include?(cb_name)
end.to_set
end
context 'when calling class methods directly' do
it 'raises an error when method is not bulk-insert safe' do
- callback_method_blacklist.each do |m|
+ unsafe_callbacks.each do |m|
expect { target_class.send(m, nil) }.to(
raise_error(BulkInsertSafe::MethodNotAllowedError),
"Expected call to #{m} to raise an error, but it didn't"
@@ -26,7 +26,7 @@ RSpec.shared_examples 'a BulkInsertSafe model' do |klass|
end
it 'does not raise an error when method is bulk-insert safe' do
- BulkInsertSafe::CALLBACK_NAME_WHITELIST.each do |name|
+ BulkInsertSafe::ALLOWED_CALLBACKS.each do |name|
expect { target_class.set_callback(name) {} }.not_to raise_error
end
end
diff --git a/spec/support/shared_examples/models/jira_import_state_shared_examples.rb b/spec/support/shared_examples/models/jira_import_state_shared_examples.rb
index f4643375c8e..1999f0f3c49 100644
--- a/spec/support/shared_examples/models/jira_import_state_shared_examples.rb
+++ b/spec/support/shared_examples/models/jira_import_state_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'multiple running imports not allowed' do
+RSpec.shared_examples 'multiple running imports not allowed' do
it 'returns not valid' do
new_import = build(:jira_import_state, project: project)
@@ -9,21 +9,21 @@ shared_examples 'multiple running imports not allowed' do
end
end
-shared_examples 'in progress' do |status|
+RSpec.shared_examples 'in progress' do |status|
it 'returns true' do
jira_import_state = build(:jira_import_state, status: status)
expect(jira_import_state).to be_in_progress
end
end
-shared_examples 'not in progress' do |status|
+RSpec.shared_examples 'not in progress' do |status|
it 'returns false' do
jira_import_state = build(:jira_import_state, status: status)
expect(jira_import_state).not_to be_in_progress
end
end
-shared_examples 'can transition' do |states|
+RSpec.shared_examples 'can transition' do |states|
states.each do |state|
it 'returns true' do
expect(jira_import.send(state)).to be true
@@ -31,7 +31,7 @@ shared_examples 'can transition' do |states|
end
end
-shared_examples 'cannot transition' do |states|
+RSpec.shared_examples 'cannot transition' do |states|
states.each do |state|
it 'returns false' do
expect(jira_import.send(state)).to be false
diff --git a/spec/support/shared_examples/models/note_access_check_shared_examples.rb b/spec/support/shared_examples/models/note_access_check_shared_examples.rb
index 3bafad202f6..44edafe9091 100644
--- a/spec/support/shared_examples/models/note_access_check_shared_examples.rb
+++ b/spec/support/shared_examples/models/note_access_check_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'users with note access' do
+RSpec.shared_examples 'users with note access' do
it 'returns true' do
users.each do |user|
expect(note.system_note_with_references_visible_for?(user)).to be_truthy
@@ -9,7 +9,7 @@ shared_examples 'users with note access' do
end
end
-shared_examples 'users without note access' do
+RSpec.shared_examples 'users without note access' do
it 'returns false' do
users.each do |user|
expect(note.system_note_with_references_visible_for?(user)).to be_falsy
diff --git a/spec/support/shared_examples/models/services_fields_shared_examples.rb b/spec/support/shared_examples/models/services_fields_shared_examples.rb
deleted file mode 100644
index cb36f74460d..00000000000
--- a/spec/support/shared_examples/models/services_fields_shared_examples.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'issue tracker fields' do
- let(:title) { 'custom title' }
- let(:description) { 'custom description' }
- let(:url) { 'http://issue_tracker.example.com' }
-
- context 'when data are stored in the properties' do
- describe '#update' do
- before do
- service.update(title: 'new_title', description: 'new description')
- end
-
- it 'removes title and description from properties' do
- expect(service.reload.properties).not_to include('title', 'description')
- end
-
- it 'stores title & description in services table' do
- expect(service.read_attribute(:title)).to eq('new_title')
- expect(service.read_attribute(:description)).to eq('new description')
- end
- end
-
- describe 'reading fields' do
- it 'returns correct values' do
- expect(service.title).to eq(title)
- expect(service.description).to eq(description)
- end
- end
- end
-end
diff --git a/spec/support/shared_examples/models/synthetic_note_shared_examples.rb b/spec/support/shared_examples/models/synthetic_note_shared_examples.rb
new file mode 100644
index 00000000000..a41ade2950a
--- /dev/null
+++ b/spec/support/shared_examples/models/synthetic_note_shared_examples.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a synthetic note' do |action|
+ it_behaves_like 'a system note', exclude_project: true do
+ let(:action) { action }
+ end
+
+ describe '#discussion_id' do
+ before do
+ allow(event).to receive(:discussion_id).and_return('foobar42')
+ end
+
+ it 'returns the expected discussion id' do
+ expect(subject.discussion_id(nil)).to eq('foobar42')
+ end
+ end
+end
diff --git a/spec/support/shared_examples/namespaces/hierarchy_examples.rb b/spec/support/shared_examples/namespaces/hierarchy_examples.rb
new file mode 100644
index 00000000000..d5754f47be2
--- /dev/null
+++ b/spec/support/shared_examples/namespaces/hierarchy_examples.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'hierarchy with traversal_ids' do
+ # A convenient null node to represent the parent of root.
+ let(:null_node) { double(traversal_ids: []) }
+
+ # Walk the tree to assert that the current_node's traversal_id is always
+ # present and equal to it's parent's traversal_ids plus it's own ID.
+ def validate_traversal_ids(current_node, parent = null_node)
+ expect(current_node.traversal_ids).to be_present
+ expect(current_node.traversal_ids).to eq parent.traversal_ids + [current_node.id]
+
+ current_node.children.each do |child|
+ validate_traversal_ids(child, current_node)
+ end
+ end
+
+ it 'will be valid' do
+ validate_traversal_ids(root)
+ end
+end
diff --git a/spec/support/shared_examples/policies/namespace_policy_shared_examples.rb b/spec/support/shared_examples/policies/namespace_policy_shared_examples.rb
new file mode 100644
index 00000000000..ddec1ba5e3d
--- /dev/null
+++ b/spec/support/shared_examples/policies/namespace_policy_shared_examples.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'update namespace limit policy' do
+ describe 'update_subscription_limit' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:policy) { :update_subscription_limit }
+
+ where(:role, :is_com, :allowed) do
+ :user | true | false
+ :owner | true | false
+ :admin | true | true
+ :user | false | false
+ :owner | false | false
+ :admin | false | false
+ end
+
+ with_them do
+ let(:current_user) { build_stubbed(role) }
+
+ before do
+ allow(Gitlab).to receive(:com?).and_return(is_com)
+ end
+
+ context 'when admin mode enabled', :enable_admin_mode do
+ it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) }
+ end
+
+ context 'when admin mode disabled' do
+ it { is_expected.to be_disallowed(policy) }
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/policies/project_policy_shared_examples.rb b/spec/support/shared_examples/policies/project_policy_shared_examples.rb
index 4dd0152e3d1..f8526ec68dc 100644
--- a/spec/support/shared_examples/policies/project_policy_shared_examples.rb
+++ b/spec/support/shared_examples/policies/project_policy_shared_examples.rb
@@ -41,6 +41,28 @@ RSpec.shared_examples 'archived project policies' do
end
end
+RSpec.shared_examples 'project private features with read_all_resources ability' do
+ subject { described_class.new(user, project) }
+
+ before do
+ project.project_feature.update!(
+ repository_access_level: ProjectFeature::PRIVATE,
+ merge_requests_access_level: ProjectFeature::PRIVATE,
+ builds_access_level: ProjectFeature::PRIVATE
+ )
+ end
+
+ [:public, :internal, :private].each do |visibility|
+ context "for #{visibility} projects" do
+ let(:project) { create(:project, visibility, namespace: owner.namespace) }
+
+ it 'allows the download_code ability' do
+ expect_allowed(:download_code)
+ end
+ end
+ end
+end
+
RSpec.shared_examples 'project policies as anonymous' do
context 'abilities for public projects' do
context 'when a project has pending invites' do
@@ -231,6 +253,12 @@ RSpec.shared_examples 'project policies as admin with admin mode' do
let(:regular_abilities) { owner_permissions }
end
end
+
+ context 'abilities for all project visibility', :enable_admin_mode do
+ it_behaves_like 'project private features with read_all_resources ability' do
+ let(:user) { admin }
+ end
+ end
end
RSpec.shared_examples 'project policies as admin without admin mode' do
diff --git a/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb
index 159660e7d1d..910805dbdea 100644
--- a/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb
@@ -47,7 +47,7 @@ RSpec.shared_examples 'create_merge_request quick action' do
expect(created_mr.source_branch).to eq(issue.to_branch_name)
visit project_merge_request_path(project, created_mr)
- expect(page).to have_content %{WIP: Resolve "#{issue.title}"}
+ expect(page).to have_content %{Draft: Resolve "#{issue.title}"}
end
it 'creates a merge request using the given branch name' do
@@ -60,7 +60,7 @@ RSpec.shared_examples 'create_merge_request quick action' do
expect(created_mr.source_branch).to eq(branch_name)
visit project_merge_request_path(project, created_mr)
- expect(page).to have_content %{WIP: Resolve "#{issue.title}"}
+ expect(page).to have_content %{Draft: Resolve "#{issue.title}"}
end
end
end
diff --git a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
index e0edbc5637a..258d9ab85e4 100644
--- a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
@@ -1,68 +1,85 @@
# frozen_string_literal: true
RSpec.shared_examples 'merge quick action' do
- context 'when the current user can merge the MR' do
+ context 'when updating the description' do
before do
sign_in(user)
- visit project_merge_request_path(project, merge_request)
+ visit edit_project_merge_request_path(project, merge_request)
end
- it 'merges the MR', :sidekiq_might_not_need_inline do
- add_note("/merge")
-
- expect(page).to have_content 'Merged this merge request.'
+ it 'merges the MR', :sidekiq_inline do
+ fill_in('Description', with: '/merge')
+ click_button('Save changes')
+ expect(page).to have_content('Merged')
expect(merge_request.reload).to be_merged
end
+ end
- context 'when auto merge is avialable' do
+ context 'when creating a new note' do
+ context 'when the current user can merge the MR' do
before do
- create(:ci_pipeline, :detached_merge_request_pipeline,
- project: project, merge_request: merge_request)
- merge_request.update_head_pipeline
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
end
- it 'schedules to merge the MR' do
+ it 'merges the MR', :sidekiq_inline do
add_note("/merge")
- expect(page).to have_content "Scheduled to merge this merge request (Merge when pipeline succeeds)."
+ expect(page).to have_content 'Merged this merge request.'
- expect(merge_request.reload).to be_auto_merge_enabled
- expect(merge_request.reload).not_to be_merged
+ expect(merge_request.reload).to be_merged
end
- end
- end
- context 'when the head diff changes in the meanwhile' do
- before do
- merge_request.source_branch = 'another_branch'
- merge_request.save
- sign_in(user)
- visit project_merge_request_path(project, merge_request)
- end
+ context 'when auto merge is available' do
+ before do
+ create(:ci_pipeline, :detached_merge_request_pipeline,
+ project: project, merge_request: merge_request)
+ merge_request.update_head_pipeline
+ end
- it 'does not merge the MR' do
- add_note("/merge")
+ it 'schedules to merge the MR' do
+ add_note("/merge")
- expect(page).not_to have_content 'Your commands have been executed!'
+ expect(page).to have_content "Scheduled to merge this merge request (Merge when pipeline succeeds)."
- expect(merge_request.reload).not_to be_merged
+ expect(merge_request.reload).to be_auto_merge_enabled
+ expect(merge_request.reload).not_to be_merged
+ end
+ end
end
- end
- context 'when the current user cannot merge the MR' do
- before do
- project.add_guest(guest)
- sign_in(guest)
- visit project_merge_request_path(project, merge_request)
+ context 'when the head diff changes in the meanwhile' do
+ before do
+ merge_request.source_branch = 'another_branch'
+ merge_request.save
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'does not merge the MR' do
+ add_note("/merge")
+
+ expect(page).not_to have_content 'Your commands have been executed!'
+
+ expect(merge_request.reload).not_to be_merged
+ end
end
- it 'does not merge the MR' do
- add_note("/merge")
+ context 'when the current user cannot merge the MR' do
+ before do
+ project.add_guest(guest)
+ sign_in(guest)
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'does not merge the MR' do
+ add_note("/merge")
- expect(page).not_to have_content 'Your commands have been executed!'
+ expect(page).not_to have_content 'Your commands have been executed!'
- expect(merge_request.reload).not_to be_merged
+ expect(merge_request.reload).not_to be_merged
+ end
end
end
end
diff --git a/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb
new file mode 100644
index 00000000000..5257980d7df
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb
@@ -0,0 +1,138 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'Composer user type' do |user_type, add_member|
+ before do
+ group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+end
+
+RSpec.shared_examples 'Composer package index' do |user_type, status, add_member = true|
+ include_context 'Composer user type', user_type, add_member do
+ it 'returns the package index' do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+ expect(response).to match_response_schema('public_api/v4/packages/composer/index')
+ end
+ end
+end
+
+RSpec.shared_examples 'Composer empty provider index' do |user_type, status, add_member = true|
+ include_context 'Composer user type', user_type, add_member do
+ it 'returns the package index' do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+ expect(response).to match_response_schema('public_api/v4/packages/composer/provider')
+ expect(json_response['providers']).to eq({})
+ end
+ end
+end
+
+RSpec.shared_examples 'Composer provider index' do |user_type, status, add_member = true|
+ include_context 'Composer user type', user_type, add_member do
+ it 'returns the package index' do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+ expect(response).to match_response_schema('public_api/v4/packages/composer/provider')
+ expect(json_response['providers']).to include(package.name)
+ end
+ end
+end
+
+RSpec.shared_examples 'Composer package api request' do |user_type, status, add_member = true|
+ include_context 'Composer user type', user_type, add_member do
+ it 'returns the package index' do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+ expect(response).to match_response_schema('public_api/v4/packages/composer/package')
+ expect(json_response['packages']).to include(package.name)
+ expect(json_response['packages'][package.name]).to include(package.version)
+ end
+ end
+end
+
+RSpec.shared_examples 'Composer package creation' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it 'creates package files' do
+ expect { subject }
+ .to change { project.packages.composer.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(status)
+ end
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'register_package'
+ end
+end
+
+RSpec.shared_examples 'process Composer api request' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+ end
+end
+
+RSpec.shared_context 'Composer auth headers' do |user_role, user_token|
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+end
+
+RSpec.shared_context 'Composer api project access' do |project_visibility_level, user_role, user_token|
+ include_context 'Composer auth headers', user_role, user_token do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+ end
+end
+
+RSpec.shared_context 'Composer api group access' do |project_visibility_level, user_role, user_token|
+ include_context 'Composer auth headers', user_role, user_token do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+ end
+end
+
+RSpec.shared_examples 'rejects Composer access with unknown group id' do
+ context 'with an unknown group' do
+ let(:group) { double(id: non_existing_record_id) }
+
+ context 'as anonymous' do
+ it_behaves_like 'process Composer api request', :anonymous, :not_found
+ end
+
+ context 'as authenticated user' do
+ subject { get api(url), headers: build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'process Composer api request', :anonymous, :not_found
+ end
+ end
+end
+
+RSpec.shared_examples 'rejects Composer access with unknown project id' do
+ context 'with an unknown project' do
+ let(:project) { double(id: non_existing_record_id) }
+
+ context 'as anonymous' do
+ it_behaves_like 'process Composer api request', :anonymous, :not_found
+ end
+
+ context 'as authenticated user' do
+ subject { get api(url), headers: build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'process Composer api request', :anonymous, :not_found
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/graphql/projects/services_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/projects/services_shared_examples.rb
index 246f1850c3c..da1caef63ba 100644
--- a/spec/support/shared_examples/requests/api/graphql/projects/services_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/projects/services_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'unauthorized users cannot read services' do
+RSpec.shared_examples 'unauthorized users cannot read services' do
before do
post_graphql(query, current_user: current_user)
end
diff --git a/spec/support/shared_examples/requests/api/notes_shared_examples.rb b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
index 60ed61269df..a34c48a5ba4 100644
--- a/spec/support/shared_examples/requests/api/notes_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
@@ -132,6 +132,16 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
expect(response).to have_gitlab_http_status(:created)
expect(json_response['body']).to eq('hi!')
+ expect(json_response['confidential']).to be_falsey
+ expect(json_response['author']['username']).to eq(user.username)
+ end
+
+ it "creates a confidential note if confidential is set to true" do
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: { body: 'hi!', confidential: true }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['body']).to eq('hi!')
+ expect(json_response['confidential']).to be_truthy
expect(json_response['author']['username']).to eq(user.username)
end
diff --git a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
new file mode 100644
index 00000000000..8d8483cae72
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
@@ -0,0 +1,408 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'rejects nuget packages access' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ if status == :unauthorized
+ it 'has the correct response header' do
+ subject
+
+ expect(response.headers['Www-Authenticate: Basic realm']).to eq 'GitLab Packages Registry'
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget service index request' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'nuget_service_index'
+
+ it 'returns a valid json response' do
+ subject
+
+ expect(response.media_type).to eq('application/json')
+ expect(json_response).to match_schema('public_api/v4/packages/nuget/service_index')
+ expect(json_response).to be_a(Hash)
+ end
+
+ context 'with invalid format' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/index.xls" }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+ end
+end
+
+RSpec.shared_examples 'returning nuget metadata json response with json schema' do |json_schema|
+ it 'returns a valid json response' do
+ subject
+
+ expect(response.media_type).to eq('application/json')
+ expect(json_response).to match_schema(json_schema)
+ expect(json_response).to be_a(Hash)
+ end
+end
+
+RSpec.shared_examples 'process nuget metadata request at package name level' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returning nuget metadata json response with json schema', 'public_api/v4/packages/nuget/packages_metadata'
+
+ context 'with invalid format' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/index.xls" }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+
+ context 'with lower case package name' do
+ let_it_be(:package_name) { 'dummy.package' }
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returning nuget metadata json response with json schema', 'public_api/v4/packages/nuget/packages_metadata'
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget metadata request at package name and package version level' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returning nuget metadata json response with json schema', 'public_api/v4/packages/nuget/package_metadata'
+
+ context 'with invalid format' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/#{package.version}.xls" }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+
+ context 'with lower case package name' do
+ let_it_be(:package_name) { 'dummy.package' }
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returning nuget metadata json response with json schema', 'public_api/v4/packages/nuget/package_metadata'
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget workhorse authorization' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it 'has the proper content type' do
+ subject
+
+ expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ end
+
+ context 'with a request that bypassed gitlab-workhorse' do
+ let(:headers) do
+ build_basic_auth_header(user.username, personal_access_token.token)
+ .merge(workhorse_header)
+ .tap { |h| h.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER) }
+ end
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ it_behaves_like 'returning response status', :forbidden
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member = true|
+ RSpec.shared_examples 'creates nuget package files' do
+ it 'creates package files' do
+ expect(::Packages::Nuget::ExtractionWorker).to receive(:perform_async).once
+ expect { subject }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+ expect(response).to have_gitlab_http_status(status)
+
+ package_file = project.packages.last.package_files.reload.last
+ expect(package_file.file_name).to eq('package.nupkg')
+ end
+ end
+
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ context 'with object storage disabled' do
+ before do
+ stub_package_file_object_storage(enabled: false)
+ end
+
+ context 'without a file from workhorse' do
+ let(:send_rewritten_field) { false }
+
+ it_behaves_like 'returning response status', :bad_request
+ end
+
+ context 'with correct params' do
+ it_behaves_like 'package workhorse uploads'
+ it_behaves_like 'creates nuget package files'
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'push_package'
+ end
+ end
+
+ context 'with object storage enabled' do
+ let(:tmp_object) do
+ fog_connection.directories.new(key: 'packages').files.create(
+ key: "tmp/uploads/#{file_name}",
+ body: 'content'
+ )
+ end
+ let(:fog_file) { fog_to_uploaded_file(tmp_object) }
+ let(:params) { { package: fog_file, 'package.remote_id' => file_name } }
+
+ context 'and direct upload enabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: true)
+ end
+
+ it_behaves_like 'creates nuget package files'
+
+ ['123123', '../../123123'].each do |remote_id|
+ context "with invalid remote_id: #{remote_id}" do
+ let(:params) do
+ {
+ package: fog_file,
+ 'package.remote_id' => remote_id
+ }
+ end
+
+ it_behaves_like 'returning response status', :forbidden
+ end
+ end
+
+ context 'with crafted package.path param' do
+ let(:crafted_file) { Tempfile.new('nuget.crafted.package.path') }
+ let(:url) { "/projects/#{project.id}/packages/nuget?package.path=#{crafted_file.path}" }
+ let(:params) { { file: temp_file(file_name) } }
+ let(:file_key) { :file }
+
+ it 'does not create a package file' do
+ expect { subject }.to change { ::Packages::PackageFile.count }.by(0)
+ end
+
+ it_behaves_like 'returning response status', :bad_request
+ end
+ end
+
+ context 'and direct upload disabled' do
+ context 'and background upload disabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false, background_upload: false)
+ end
+
+ it_behaves_like 'creates nuget package files'
+ end
+
+ context 'and background upload enabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false, background_upload: true)
+ end
+
+ it_behaves_like 'creates nuget package files'
+ end
+ end
+ end
+
+ it_behaves_like 'background upload schedules a file migration'
+ end
+end
+
+RSpec.shared_examples 'process nuget download versions request' do |user_type, status, add_member = true|
+ RSpec.shared_examples 'returns a valid nuget download versions json response' do
+ it 'returns a valid json response' do
+ subject
+
+ expect(response.media_type).to eq('application/json')
+ expect(json_response).to match_schema('public_api/v4/packages/nuget/download_versions')
+ expect(json_response).to be_a(Hash)
+ expect(json_response['versions']).to match_array(packages.map(&:version).sort)
+ end
+ end
+
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returns a valid nuget download versions json response'
+
+ context 'with invalid format' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package_name}/index.xls" }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+
+ context 'with lower case package name' do
+ let_it_be(:package_name) { 'dummy.package' }
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returns a valid nuget download versions json response'
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget download content request' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'pull_package'
+
+ it 'returns a valid package archive' do
+ subject
+
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ context 'with invalid format' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package.name}/#{package.version}/#{package.name}.#{package.version}.xls" }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+
+ context 'with lower case package name' do
+ let_it_be(:package_name) { 'dummy.package' }
+
+ it_behaves_like 'returning response status', status
+
+ it 'returns a valid package archive' do
+ subject
+
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget search request' do |user_type, status, add_member = true|
+ RSpec.shared_examples 'returns a valid json search response' do |status, total_hits, versions|
+ it_behaves_like 'returning response status', status
+
+ it 'returns a valid json response' do
+ subject
+
+ expect(response.media_type).to eq('application/json')
+ expect(json_response).to be_a(Hash)
+ expect(json_response).to match_schema('public_api/v4/packages/nuget/search')
+ expect(json_response['totalHits']).to eq total_hits
+ expect(json_response['data'].map { |e| e['versions'].size }).to match_array(versions)
+ end
+ end
+
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returns a valid json search response', status, 4, [1, 5, 5, 1]
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'search_package'
+
+ context 'with skip set to 2' do
+ let(:skip) { 2 }
+
+ it_behaves_like 'returns a valid json search response', status, 4, [5, 1]
+ end
+
+ context 'with take set to 2' do
+ let(:take) { 2 }
+
+ it_behaves_like 'returns a valid json search response', status, 4, [1, 5]
+ end
+
+ context 'without prereleases' do
+ let(:include_prereleases) { false }
+
+ it_behaves_like 'returns a valid json search response', status, 3, [1, 5, 5]
+ end
+
+ context 'with empty search term' do
+ let(:search_term) { '' }
+
+ it_behaves_like 'returns a valid json search response', status, 5, [1, 5, 5, 1, 1]
+ end
+
+ context 'with nil search term' do
+ let(:search_term) { nil }
+
+ it_behaves_like 'returns a valid json search response', status, 5, [1, 5, 5, 1, 1]
+ end
+ end
+end
+
+RSpec.shared_examples 'rejects nuget access with invalid project id' do
+ context 'with a project id with invalid integers' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:project) { OpenStruct.new(id: id) }
+
+ where(:id, :status) do
+ '/../' | :unauthorized
+ '' | :not_found
+ '%20' | :unauthorized
+ '%2e%2e%2f' | :unauthorized
+ 'NaN' | :unauthorized
+ 00002345 | :unauthorized
+ 'anything25' | :unauthorized
+ end
+
+ with_them do
+ it_behaves_like 'rejects nuget packages access', :anonymous, params[:status]
+ end
+ end
+end
+
+RSpec.shared_examples 'rejects nuget access with unknown project id' do
+ context 'with an unknown project' do
+ let(:project) { OpenStruct.new(id: 1234567890) }
+
+ context 'as anonymous' do
+ it_behaves_like 'rejects nuget packages access', :anonymous, :unauthorized
+ end
+
+ context 'as authenticated user' do
+ subject { get api(url), headers: build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
new file mode 100644
index 00000000000..ec15d7a4d2e
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'deploy token for package GET requests' do
+ context 'with deploy token headers' do
+ let(:headers) { build_basic_auth_header(deploy_token.username, deploy_token.token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ context 'valid token' do
+ it_behaves_like 'returning response status', :success
+ end
+
+ context 'invalid token' do
+ let(:headers) { build_basic_auth_header(deploy_token.username, 'bar') }
+
+ it_behaves_like 'returning response status', :unauthorized
+ end
+ end
+end
+
+RSpec.shared_examples 'deploy token for package uploads' do
+ context 'with deploy token headers' do
+ let(:headers) { build_basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_header) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ context 'valid token' do
+ it_behaves_like 'returning response status', :success
+ end
+
+ context 'invalid token' do
+ let(:headers) { build_basic_auth_header(deploy_token.username, 'bar').merge(workhorse_header) }
+
+ it_behaves_like 'returning response status', :unauthorized
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/packages_tags_shared_examples.rb b/spec/support/shared_examples/requests/api/packages_tags_shared_examples.rb
new file mode 100644
index 00000000000..a371d380f47
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/packages_tags_shared_examples.rb
@@ -0,0 +1,185 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'rejects package tags access' do |user_type, status|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ it_behaves_like 'returning response status', status
+ end
+end
+
+RSpec.shared_examples 'returns package tags' do |user_type|
+ using RSpec::Parameterized::TableSyntax
+
+ before do
+ stub_application_setting(npm_package_requests_forwarding: false)
+ project.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ it_behaves_like 'returning response status', :success
+
+ it 'returns a valid json response' do
+ subject
+
+ expect(response.media_type).to eq('application/json')
+ expect(json_response).to be_a(Hash)
+ end
+
+ it 'returns two package tags' do
+ subject
+
+ expect(json_response).to match_schema('public_api/v4/packages/npm_package_tags')
+ expect(json_response.length).to eq(3) # two tags + latest (auto added)
+ expect(json_response[package_tag1.name]).to eq(package.version)
+ expect(json_response[package_tag2.name]).to eq(package.version)
+ expect(json_response['latest']).to eq(package.version)
+ end
+
+ context 'with invalid package name' do
+ where(:package_name, :status) do
+ '%20' | :bad_request
+ nil | :forbidden
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+end
+
+RSpec.shared_examples 'create package tag' do |user_type|
+ using RSpec::Parameterized::TableSyntax
+
+ before do
+ project.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ it_behaves_like 'returning response status', :no_content
+
+ it 'creates the package tag' do
+ expect { subject }.to change { Packages::Tag.count }.by(1)
+
+ last_tag = Packages::Tag.last
+ expect(last_tag.name).to eq(tag_name)
+ expect(last_tag.package).to eq(package)
+ end
+
+ it 'returns a valid response' do
+ subject
+
+ expect(response.body).to be_empty
+ end
+
+ context 'with already existing tag' do
+ let_it_be(:package2) { create(:npm_package, project: project, name: package.name, version: '5.5.55') }
+ let_it_be(:tag) { create(:packages_tag, package: package2, name: tag_name) }
+
+ it_behaves_like 'returning response status', :no_content
+
+ it 'reuses existing tag' do
+ expect(package.tags).to be_empty
+ expect(package2.tags).to eq([tag])
+ expect { subject }.to not_change { Packages::Tag.count }
+ expect(package.reload.tags).to eq([tag])
+ expect(package2.reload.tags).to be_empty
+ end
+
+ it 'returns a valid response' do
+ subject
+
+ expect(response.body).to be_empty
+ end
+ end
+
+ context 'with invalid package name' do
+ where(:package_name, :status) do
+ 'unknown' | :forbidden
+ '' | :not_found
+ '%20' | :bad_request
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+
+ context 'with invalid tag name' do
+ where(:tag_name, :status) do
+ '' | :not_found
+ '%20' | :bad_request
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+
+ context 'with invalid version' do
+ where(:version, :status) do
+ ' ' | :bad_request
+ '' | :bad_request
+ nil | :bad_request
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+end
+
+RSpec.shared_examples 'delete package tag' do |user_type|
+ using RSpec::Parameterized::TableSyntax
+
+ before do
+ project.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ context "for #{user_type} user" do
+ it_behaves_like 'returning response status', :no_content
+
+ it 'returns a valid response' do
+ subject
+
+ expect(response.body).to be_empty
+ end
+
+ it 'destroy the package tag' do
+ expect(package.tags).to eq([package_tag])
+ expect { subject }.to change { Packages::Tag.count }.by(-1)
+ expect(package.reload.tags).to be_empty
+ end
+
+ context 'with tag from other package' do
+ let(:package2) { create(:npm_package, project: project) }
+ let(:package_tag) { create(:packages_tag, package: package2) }
+
+ it_behaves_like 'returning response status', :not_found
+ end
+
+ context 'with invalid package name' do
+ where(:package_name, :status) do
+ 'unknown' | :forbidden
+ '' | :not_found
+ '%20' | :bad_request
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+
+ context 'with invalid tag name' do
+ where(:tag_name, :status) do
+ 'unknown' | :not_found
+ '' | :not_found
+ '%20' | :bad_request
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
new file mode 100644
index 00000000000..fcc166ac87d
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
@@ -0,0 +1,152 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'PyPi package creation' do |user_type, status, add_member = true|
+ RSpec.shared_examples 'creating pypi package files' do
+ it 'creates package files' do
+ expect { subject }
+ .to change { project.packages.pypi.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+ .and change { Packages::Pypi::Metadatum.count }.by(1)
+ expect(response).to have_gitlab_http_status(status)
+
+ package = project.reload.packages.pypi.last
+
+ expect(package.name).to eq params[:name]
+ expect(package.version).to eq params[:version]
+ expect(package.pypi_metadatum.required_python).to eq params[:requires_python]
+ end
+ end
+
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'creating pypi package files'
+
+ context 'with object storage disabled' do
+ before do
+ stub_package_file_object_storage(enabled: false)
+ end
+
+ context 'without a file from workhorse' do
+ let(:send_rewritten_field) { false }
+
+ it_behaves_like 'returning response status', :bad_request
+ end
+
+ context 'with correct params' do
+ it_behaves_like 'package workhorse uploads'
+ it_behaves_like 'creating pypi package files'
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'push_package'
+ end
+ end
+
+ context 'with object storage enabled' do
+ let(:tmp_object) do
+ fog_connection.directories.new(key: 'packages').files.create(
+ key: "tmp/uploads/#{file_name}",
+ body: 'content'
+ )
+ end
+ let(:fog_file) { fog_to_uploaded_file(tmp_object) }
+ let(:params) { base_params.merge(content: fog_file, 'content.remote_id' => file_name) }
+
+ context 'and direct upload enabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: true)
+ end
+
+ it_behaves_like 'creating pypi package files'
+
+ ['123123', '../../123123'].each do |remote_id|
+ context "with invalid remote_id: #{remote_id}" do
+ let(:params) { base_params.merge(content: fog_file, 'content.remote_id' => remote_id) }
+
+ it_behaves_like 'returning response status', :forbidden
+ end
+ end
+ end
+
+ context 'and direct upload disabled' do
+ context 'and background upload disabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false, background_upload: false)
+ end
+
+ it_behaves_like 'creating pypi package files'
+ end
+
+ context 'and background upload enabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false, background_upload: true)
+ end
+
+ it_behaves_like 'creating pypi package files'
+ end
+ end
+ end
+
+ it_behaves_like 'background upload schedules a file migration'
+ end
+end
+
+RSpec.shared_examples 'PyPi package versions' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it 'returns the package listing' do
+ subject
+
+ expect(response.body).to match(package.package_files.first.file_name)
+ end
+
+ it_behaves_like 'returning response status', status
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'list_package'
+ end
+end
+
+RSpec.shared_examples 'PyPi package download' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it 'returns the package listing' do
+ subject
+
+ expect(response.body).to eq(File.open(package.package_files.first.file.path, "rb").read)
+ end
+
+ it_behaves_like 'returning response status', status
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'pull_package'
+ end
+end
+
+RSpec.shared_examples 'process PyPi api request' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+ end
+end
+
+RSpec.shared_examples 'rejects PyPI access with unknown project id' do
+ context 'with an unknown project' do
+ let(:project) { OpenStruct.new(id: 1234567890) }
+
+ context 'as anonymous' do
+ it_behaves_like 'process PyPi api request', :anonymous, :not_found
+ end
+
+ context 'as authenticated user' do
+ subject { get api(url), headers: build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'process PyPi api request', :anonymous, :not_found
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb b/spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb
index bca51dab353..d21a9f419fd 100644
--- a/spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb
@@ -16,6 +16,29 @@ RSpec.shared_examples 'resource_milestone_events API' do |parent_type, eventable
expect(json_response.first['action']).to eq(event.action)
end
+ context 'when there is an event with a milestone which is not visible for requesting user' do
+ let!(:private_project) { create(:project, :private) }
+ let!(:private_milestone) { create(:milestone, project: private_project) }
+
+ let!(:other_user) { create(:user) }
+
+ it 'returns the expected events' do
+ create_event(private_milestone)
+
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_milestone_events", other_user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(response.headers['X-Total']).to eq('1')
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+
+ expect(json_response.first['id']).to eq(event.id)
+ expect(json_response.first['milestone']['id']).to eq(event.milestone.id)
+ expect(json_response.first['action']).to eq(event.action)
+ end
+ end
+
it "returns a 404 error when eventable id not found" do
get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{non_existing_record_id}/resource_milestone_events", user)
@@ -60,6 +83,20 @@ RSpec.shared_examples 'resource_milestone_events API' do |parent_type, eventable
end
end
+ describe 'pagination' do
+ let!(:event1) { create_event(milestone) }
+ let!(:event2) { create_event(milestone) }
+
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/220192
+ it 'returns the second page' do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_milestone_events?page=2&per_page=1", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.count).to eq(1)
+ expect(json_response.first['id']).to eq(event2.id)
+ end
+ end
+
def create_event(milestone, action: :add)
create(:resource_milestone_event, eventable.class.name.underscore => eventable, milestone: milestone, action: action)
end
diff --git a/spec/support/shared_examples/requests/api/snippets_shared_examples.rb b/spec/support/shared_examples/requests/api/snippets_shared_examples.rb
new file mode 100644
index 00000000000..cfbb84dd099
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/snippets_shared_examples.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'raw snippet files' do
+ let_it_be(:unauthorized_user) { create(:user) }
+ let(:snippet_id) { snippet.id }
+ let(:user) { snippet.author }
+ let(:file_path) { '%2Egitattributes' }
+ let(:ref) { 'master' }
+
+ context 'with no user' do
+ it 'requires authentication' do
+ get api(api_path)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ shared_examples 'not found' do
+ it 'returns 404' do
+ get api(api_path, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('404 Snippet Not Found')
+ end
+ end
+
+ context 'when not authorized' do
+ let(:user) { unauthorized_user }
+
+ it_behaves_like 'not found'
+ end
+
+ context 'with an invalid snippet ID' do
+ let(:snippet_id) { 'invalid' }
+
+ it_behaves_like 'not found'
+ end
+
+ context 'with valid params' do
+ it 'returns the raw file info' do
+ expect(Gitlab::Workhorse).to receive(:send_git_blob).and_call_original
+
+ get api(api_path, user)
+
+ aggregate_failures do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq 'text/plain'
+ expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq 'true'
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
+ expect(response.header['Content-Disposition']).to match 'filename=".gitattributes"'
+ end
+ end
+ end
+
+ context 'with invalid params' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:file_path, :ref, :status, :key, :message) do
+ '%2Egitattributes' | 'invalid-ref' | :not_found | 'message' | '404 Reference Not Found'
+ '%2Egitattributes' | nil | :not_found | 'error' | '404 Not Found'
+ '%2Egitattributes' | '' | :not_found | 'error' | '404 Not Found'
+
+ 'doesnotexist.rb' | 'master' | :not_found | 'message' | '404 File Not Found'
+ '/does/not/exist.rb' | 'master' | :not_found | 'error' | '404 Not Found'
+ '%2E%2E%2Fetc%2Fpasswd' | 'master' | :bad_request | 'error' | 'file_path should be a valid file path'
+ '%2Fetc%2Fpasswd' | 'master' | :bad_request | 'error' | 'file_path should be a valid file path'
+ '../../etc/passwd' | 'master' | :not_found | 'error' | '404 Not Found'
+ end
+
+ with_them do
+ before do
+ get api(api_path, user)
+ end
+
+ it { expect(response).to have_gitlab_http_status(status) }
+ it { expect(json_response[key]).to eq(message) }
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/snippet_shared_examples.rb b/spec/support/shared_examples/requests/snippet_shared_examples.rb
index f830f957174..644abb191a6 100644
--- a/spec/support/shared_examples/requests/snippet_shared_examples.rb
+++ b/spec/support/shared_examples/requests/snippet_shared_examples.rb
@@ -74,18 +74,14 @@ RSpec.shared_examples 'update with repository actions' do
end
end
-RSpec.shared_examples 'snippet response without repository URLs' do
- it 'skip inclusion of repository URLs' do
- expect(json_response).not_to have_key('ssh_url_to_repo')
- expect(json_response).not_to have_key('http_url_to_repo')
- end
-end
-
RSpec.shared_examples 'snippet blob content' do
it 'returns content from repository' do
+ expect(Gitlab::Workhorse).to receive(:send_git_blob).and_call_original
+
subject
- expect(response.body).to eq(snippet.blobs.first.data)
+ expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq 'true'
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
end
context 'when snippet repository is empty' do
@@ -98,3 +94,15 @@ RSpec.shared_examples 'snippet blob content' do
end
end
end
+
+RSpec.shared_examples 'snippet_multiple_files feature disabled' do
+ before do
+ stub_feature_flags(snippet_multiple_files: false)
+
+ subject
+ end
+
+ it 'does not return files attributes' do
+ expect(json_response).not_to have_key('files')
+ end
+end
diff --git a/spec/support/shared_examples/resource_events.rb b/spec/support/shared_examples/resource_events.rb
index 66f5e760c37..c0158f9b24b 100644
--- a/spec/support/shared_examples/resource_events.rb
+++ b/spec/support/shared_examples/resource_events.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-shared_examples 'a resource event' do
+RSpec.shared_examples 'a resource event' do
let_it_be(:user1) { create(:user) }
let_it_be(:user2) { create(:user) }
@@ -54,7 +54,7 @@ shared_examples 'a resource event' do
end
end
-shared_examples 'a resource event for issues' do
+RSpec.shared_examples 'a resource event for issues' do
let_it_be(:user1) { create(:user) }
let_it_be(:user2) { create(:user) }
@@ -101,9 +101,19 @@ shared_examples 'a resource event for issues' do
expect(events).to be_empty
end
end
+
+ if described_class.method_defined?(:issuable)
+ describe '#issuable' do
+ let_it_be(:event1) { create(described_class.name.underscore.to_sym, issue: issue2) }
+
+ it 'returns the expected issuable' do
+ expect(event1.issuable).to eq(issue2)
+ end
+ end
+ end
end
-shared_examples 'a resource event for merge requests' do
+RSpec.shared_examples 'a resource event for merge requests' do
let_it_be(:user1) { create(:user) }
let_it_be(:user2) { create(:user) }
@@ -132,4 +142,14 @@ shared_examples 'a resource event for merge requests' do
expect(events).to be_empty
end
end
+
+ if described_class.method_defined?(:issuable)
+ describe '#issuable' do
+ let_it_be(:event1) { create(described_class.name.underscore.to_sym, merge_request: merge_request2) }
+
+ it 'returns the expected issuable' do
+ expect(event1.issuable).to eq(merge_request2)
+ end
+ end
+ end
end
diff --git a/spec/support/shared_examples/routing/resource_routing_shared_examples.rb b/spec/support/shared_examples/routing/resource_routing_shared_examples.rb
new file mode 100644
index 00000000000..b98901a57ea
--- /dev/null
+++ b/spec/support/shared_examples/routing/resource_routing_shared_examples.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+# Shared examples for resource routes.
+#
+# By default it tests all the default REST actions: index, create, new, edit,
+# show, update, and destroy. You can remove actions by customizing the
+# `actions` variable.
+#
+# The subject is expected to be an instance of the controller under test.
+#
+# It also expects a `base_path` variable to be available which defines the
+# base path of the controller, and a `base_params` variable which
+# defines the route params the base path maps to.
+#
+# Examples
+#
+# # Default behavior
+# describe Projects::CommitsController, 'routing' do
+# it_behaves_like 'resource routing' do
+# let(:base_path) { '/gitlab/gitlabhq/-/commits' }
+# let(:base_params) { { namespace_id: 'gitlab', project_id: 'gitlabhq' } }
+# end
+# end
+#
+# # Customizing actions
+# it_behaves_like 'resource routing' do
+# let(:base_path) { '/gitlab/gitlabhq/-/commits' }
+#
+# # Specify default actions
+# let(:actions) { [:index] }
+#
+# # Add custom actions by passing a hash with action names
+# # as keys, and the HTTP method and path as values.
+# let(:additional_actions) do
+# {
+# preview_markdown: [:post, '/:id/preview_markdown'],
+# }
+# end
+# end
+RSpec.shared_examples 'resource routing' do
+ let(:controller) { described_class.controller_path }
+ let(:id) { '123' }
+
+ let(:default_actions) do
+ {
+ index: [:get, ''],
+ show: [:get, '/:id'],
+ new: [:get, '/new'],
+ create: [:post, ''],
+ edit: [:get, '/:id/edit'],
+ update: [:put, '/:id'],
+ destroy: [:delete, '/:id']
+ }
+ end
+
+ let(:actions) { default_actions.keys }
+ let(:additional_actions) { {} }
+
+ it 'routes resource actions', :aggregate_failures do
+ selected_actions = default_actions.slice(*actions).merge(additional_actions)
+
+ selected_actions.each do |action, (method, action_path)|
+ expected_params = base_params.merge(controller: controller.to_s, action: action.to_s)
+
+ if action_path.include?(':id')
+ action_path = action_path.sub(':id', id)
+ expected_params[:id] = id
+ end
+
+ expect(public_send(method, "#{base_path}#{action_path}")).to route_to(expected_params)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/routing/wiki_routing_shared_examples.rb b/spec/support/shared_examples/routing/wiki_routing_shared_examples.rb
new file mode 100644
index 00000000000..9289934677e
--- /dev/null
+++ b/spec/support/shared_examples/routing/wiki_routing_shared_examples.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'wiki routing' do
+ it_behaves_like 'resource routing' do
+ let(:id) { 'directory/page' }
+ let(:actions) { %i[show new create edit update destroy] }
+ let(:additional_actions) do
+ {
+ pages: [:get, '/pages'],
+ history: [:get, '/:id/history'],
+ git_access: [:get, '/git_access'],
+ preview_markdown: [:post, '/:id/preview_markdown']
+ }
+ end
+ end
+
+ it 'redirects the base path to the home page', type: :request do
+ expect(get(base_path)).to redirect_to("#{base_path}/home")
+ end
+end
diff --git a/spec/support/shared_examples/services/alert_management_shared_examples.rb b/spec/support/shared_examples/services/alert_management_shared_examples.rb
new file mode 100644
index 00000000000..a1354a8099b
--- /dev/null
+++ b/spec/support/shared_examples/services/alert_management_shared_examples.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'creates an alert management alert' do
+ it { is_expected.to be_success }
+
+ it 'creates AlertManagement::Alert' do
+ expect { subject }.to change(AlertManagement::Alert, :count).by(1)
+ end
+
+ it 'executes the alert service hooks' do
+ slack_service = create(:service, type: 'SlackService', project: project, alert_events: true, active: true)
+
+ subject
+
+ expect(ProjectServiceWorker).to have_received(:perform_async).with(slack_service.id, an_instance_of(Hash))
+ end
+end
+
+RSpec.shared_examples 'does not an create alert management alert' do
+ it 'does not create alert' do
+ expect { subject }.not_to change(AlertManagement::Alert, :count)
+ end
+end
+
+RSpec.shared_examples 'adds an alert management alert event' do
+ it { is_expected.to be_success }
+
+ it 'does not create an alert' do
+ expect { subject }.not_to change(AlertManagement::Alert, :count)
+ end
+
+ it 'increases alert events count' do
+ expect { subject }.to change { alert.reload.events }.by(1)
+ end
+
+ it 'does not executes the alert service hooks' do
+ expect(alert).not_to receive(:execute_services)
+
+ subject
+ end
+end
diff --git a/spec/support/shared_examples/services/clusters/parse_cluster_applications_artifact_shared_examples.rb b/spec/support/shared_examples/services/clusters/parse_cluster_applications_artifact_shared_examples.rb
new file mode 100644
index 00000000000..cbe20928f98
--- /dev/null
+++ b/spec/support/shared_examples/services/clusters/parse_cluster_applications_artifact_shared_examples.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'parse cluster applications artifact' do |release_name|
+ let(:application_class) { Clusters::Cluster::APPLICATIONS[release_name] }
+ let(:cluster_application) { cluster.public_send("application_#{release_name}") }
+ let(:file) { fixture_file_upload(Rails.root.join(fixture)) }
+ let(:artifact) { create(:ci_job_artifact, :cluster_applications, job: job, file: file) }
+
+ context 'release is missing' do
+ let(:fixture) { "spec/fixtures/helm/helm_list_v2_#{release_name}_missing.json.gz" }
+
+ context 'application does not exist' do
+ it 'does not create or destroy an application' do
+ expect do
+ described_class.new(job, user).execute(artifact)
+ end.not_to change(application_class, :count)
+ end
+ end
+
+ context 'application exists' do
+ before do
+ create("clusters_applications_#{release_name}".to_sym, :installed, cluster: cluster)
+ end
+
+ it 'marks the application as uninstalled' do
+ described_class.new(job, user).execute(artifact)
+
+ cluster_application.reload
+ expect(cluster_application).to be_uninstalled
+ end
+ end
+ end
+
+ context 'release is deployed' do
+ let(:fixture) { "spec/fixtures/helm/helm_list_v2_#{release_name}_deployed.json.gz" }
+
+ context 'application does not exist' do
+ it 'creates an application and marks it as installed' do
+ expect do
+ described_class.new(job, user).execute(artifact)
+ end.to change(application_class, :count)
+
+ expect(cluster_application).to be_persisted
+ expect(cluster_application).to be_installed
+ end
+ end
+
+ context 'application exists' do
+ before do
+ create("clusters_applications_#{release_name}".to_sym, :errored, cluster: cluster)
+ end
+
+ it 'marks the application as installed' do
+ described_class.new(job, user).execute(artifact)
+
+ expect(cluster_application).to be_installed
+ end
+ end
+ end
+
+ context 'release is failed' do
+ let(:fixture) { "spec/fixtures/helm/helm_list_v2_#{release_name}_failed.json.gz" }
+
+ context 'application does not exist' do
+ it 'creates an application and marks it as errored' do
+ expect do
+ described_class.new(job, user).execute(artifact)
+ end.to change(application_class, :count)
+
+ expect(cluster_application).to be_persisted
+ expect(cluster_application).to be_errored
+ expect(cluster_application.status_reason).to eq('Helm release failed to install')
+ end
+ end
+
+ context 'application exists' do
+ before do
+ create("clusters_applications_#{release_name}".to_sym, :installed, cluster: cluster)
+ end
+
+ it 'marks the application as errored' do
+ described_class.new(job, user).execute(artifact)
+
+ expect(cluster_application).to be_errored
+ expect(cluster_application.status_reason).to eq('Helm release failed to install')
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/common_system_notes_shared_examples.rb b/spec/support/shared_examples/services/common_system_notes_shared_examples.rb
index 4ce3e32d774..20856b05de6 100644
--- a/spec/support/shared_examples/services/common_system_notes_shared_examples.rb
+++ b/spec/support/shared_examples/services/common_system_notes_shared_examples.rb
@@ -17,10 +17,10 @@ RSpec.shared_examples 'system note creation' do |update_params, note_text|
end
end
-RSpec.shared_examples 'WIP notes creation' do |wip_action|
+RSpec.shared_examples 'draft notes creation' do |wip_action|
subject { described_class.new(project, user).execute(issuable, old_labels: []) }
- it 'creates WIP toggle and title change notes' do
+ it 'creates Draft toggle and title change notes' do
expect { subject }.to change { Note.count }.from(0).to(2)
expect(Note.first.note).to match("#{wip_action} as a **Work In Progress**")
diff --git a/spec/support/shared_examples/services/jira_import/start_import_service_shared_examples.rb b/spec/support/shared_examples/services/jira_import/start_import_service_shared_examples.rb
index c5e56ed3539..8fd76f7cb1f 100644
--- a/spec/support/shared_examples/services/jira_import/start_import_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/jira_import/start_import_service_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'responds with error' do |message|
+RSpec.shared_examples 'responds with error' do |message|
it 'returns error' do
expect(subject).to be_a(ServiceResponse)
expect(subject).to be_error
diff --git a/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb b/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb
index 5dd1badbefc..c8fabfe30b9 100644
--- a/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb
+++ b/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb
@@ -29,14 +29,43 @@ RSpec.shared_examples 'valid dashboard service response' do
end
RSpec.shared_examples 'caches the unprocessed dashboard for subsequent calls' do
- it do
- expect(YAML).to receive(:safe_load).once.and_call_original
+ specify do
+ expect_next_instance_of(::Gitlab::Config::Loader::Yaml) do |loader|
+ expect(loader).to receive(:load_raw!).once.and_call_original
+ end
described_class.new(*service_params).get_dashboard
described_class.new(*service_params).get_dashboard
end
end
+# This spec is applicable for predefined/out-of-the-box dashboard services.
+RSpec.shared_examples 'refreshes cache when dashboard_version is changed' do
+ specify do
+ allow_next_instance_of(described_class) do |service|
+ allow(service).to receive(:dashboard_version).and_return('1', '2')
+ end
+
+ expect(File).to receive(:read).twice.and_call_original
+
+ service = described_class.new(*service_params)
+
+ service.get_dashboard
+ service.get_dashboard
+ end
+end
+
+# This spec is applicable for predefined/out-of-the-box dashboard services.
+# This shared_example requires the following variables to be defined:
+# dashboard_path: Relative path to the dashboard, ex: 'config/prometheus/common_metrics.yml'
+# dashboard_version: The version string used in the cache_key.
+RSpec.shared_examples 'dashboard_version contains SHA256 hash of dashboard file content' do
+ specify do
+ dashboard = File.read(Rails.root.join(dashboard_path))
+ expect(Digest::SHA256.hexdigest(dashboard)).to eq(dashboard_version)
+ end
+end
+
RSpec.shared_examples 'valid embedded dashboard service response' do
let(:dashboard_schema) { Gitlab::Json.parse(fixture_file('lib/gitlab/metrics/dashboard/schemas/embedded_dashboard.json')) }
@@ -128,3 +157,50 @@ RSpec.shared_examples 'updates gitlab_metrics_dashboard_processing_time_ms metri
expect(metric.get(labels)).to be > 0
end
end
+
+RSpec.shared_examples '#raw_dashboard raises error if dashboard loading fails' do
+ context 'when yaml is too large' do
+ before do
+ allow_next_instance_of(::Gitlab::Config::Loader::Yaml) do |loader|
+ allow(loader).to receive(:load_raw!)
+ .and_raise(Gitlab::Config::Loader::Yaml::DataTooLargeError, 'The parsed YAML is too big')
+ end
+ end
+
+ it 'raises error' do
+ expect { subject.raw_dashboard }.to raise_error(
+ Gitlab::Metrics::Dashboard::Errors::LayoutError,
+ 'The parsed YAML is too big'
+ )
+ end
+ end
+
+ context 'when yaml loader returns error' do
+ before do
+ allow_next_instance_of(::Gitlab::Config::Loader::Yaml) do |loader|
+ allow(loader).to receive(:load_raw!)
+ .and_raise(Gitlab::Config::Loader::FormatError, 'Invalid configuration format')
+ end
+ end
+
+ it 'raises error' do
+ expect { subject.raw_dashboard }.to raise_error(
+ Gitlab::Metrics::Dashboard::Errors::LayoutError,
+ 'Invalid yaml'
+ )
+ end
+ end
+
+ context 'when yaml is not a hash' do
+ before do
+ allow_next_instance_of(::Gitlab::Config::Loader::Yaml) do |loader|
+ allow(loader).to receive(:load_raw!)
+ .and_raise(Gitlab::Config::Loader::Yaml::NotHashError, 'Invalid configuration format')
+ end
+ end
+
+ it 'returns nil' do
+ expect(subject.raw_dashboard).to eq({})
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/packages_shared_examples.rb b/spec/support/shared_examples/services/packages_shared_examples.rb
new file mode 100644
index 00000000000..45a4c2bb151
--- /dev/null
+++ b/spec/support/shared_examples/services/packages_shared_examples.rb
@@ -0,0 +1,193 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'assigns build to package' do
+ context 'with build info' do
+ let(:job) { create(:ci_build, user: user) }
+ let(:params) { super().merge(build: job) }
+
+ it 'assigns the pipeline to the package' do
+ package = subject
+
+ expect(package.build_info).to be_present
+ expect(package.build_info.pipeline).to eq job.pipeline
+ end
+ end
+end
+
+RSpec.shared_examples 'returns packages' do |container_type, user_type|
+ context "for #{user_type}" do
+ before do
+ send(container_type)&.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ it 'returns success response' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:success)
+ end
+
+ it 'returns a valid response schema' do
+ subject
+
+ expect(response).to match_response_schema(package_schema)
+ end
+
+ it 'returns two packages' do
+ subject
+
+ expect(json_response.length).to eq(2)
+ expect(json_response.map { |package| package['id'] }).to contain_exactly(package1.id, package2.id)
+ end
+ end
+end
+
+RSpec.shared_examples 'returns packages with subgroups' do |container_type, user_type|
+ context "with subgroups for #{user_type}" do
+ before do
+ send(container_type)&.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ it 'returns success response' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:success)
+ end
+
+ it 'returns a valid response schema' do
+ subject
+
+ expect(response).to match_response_schema(package_schema)
+ end
+
+ it 'returns three packages' do
+ subject
+
+ expect(json_response.length).to eq(3)
+ expect(json_response.map { |package| package['id'] }).to contain_exactly(package1.id, package2.id, package3.id)
+ end
+ end
+end
+
+RSpec.shared_examples 'package sorting' do |order_by|
+ subject { get api(url), params: { sort: sort, order_by: order_by } }
+
+ context "sorting by #{order_by}" do
+ context 'ascending order' do
+ let(:sort) { 'asc' }
+
+ it 'returns the sorted packages' do
+ subject
+
+ expect(json_response.map { |package| package['id'] }).to eq(packages.map(&:id))
+ end
+ end
+
+ context 'descending order' do
+ let(:sort) { 'desc' }
+
+ it 'returns the sorted packages' do
+ subject
+
+ expect(json_response.map { |package| package['id'] }).to eq(packages.reverse.map(&:id))
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'rejects packages access' do |container_type, user_type, status|
+ context "for #{user_type}" do
+ before do
+ send(container_type)&.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ it_behaves_like 'returning response status', status
+ end
+end
+
+RSpec.shared_examples 'returns paginated packages' do
+ let(:per_page) { 2 }
+
+ context 'when viewing the first page' do
+ let(:page) { 1 }
+
+ it 'returns first 2 packages' do
+ get api(url, user), params: { page: page, per_page: per_page }
+
+ expect_paginated_array_response([package1.id, package2.id])
+ end
+ end
+
+ context 'when viewing the second page' do
+ let(:page) { 2 }
+
+ it 'returns first 2 packages' do
+ get api(url, user), params: { page: page, per_page: per_page }
+
+ expect_paginated_array_response([package3.id, package4.id])
+ end
+ end
+end
+
+RSpec.shared_examples 'background upload schedules a file migration' do
+ context 'background upload enabled' do
+ before do
+ stub_package_file_object_storage(background_upload: true)
+ end
+
+ it 'schedules migration of file to object storage' do
+ expect(ObjectStorage::BackgroundMoveWorker).to receive(:perform_async).with('Packages::PackageFileUploader', 'Packages::PackageFile', :file, kind_of(Numeric))
+
+ subject
+ end
+ end
+end
+
+RSpec.shared_context 'package filter context' do
+ def package_filter_url(filter, param)
+ "/projects/#{project.id}/packages?package_#{filter}=#{param}"
+ end
+
+ def group_filter_url(filter, param)
+ "/groups/#{group.id}/packages?package_#{filter}=#{param}"
+ end
+end
+
+RSpec.shared_examples 'filters on each package_type' do |is_project: false|
+ include_context 'package filter context'
+
+ let_it_be(:package1) { create(:conan_package, project: project) }
+ let_it_be(:package2) { create(:maven_package, project: project) }
+ let_it_be(:package3) { create(:npm_package, project: project) }
+ let_it_be(:package4) { create(:nuget_package, project: project) }
+ let_it_be(:package5) { create(:pypi_package, project: project) }
+ let_it_be(:package6) { create(:composer_package, project: project) }
+
+ Packages::Package.package_types.keys.each do |package_type|
+ context "for package type #{package_type}" do
+ let(:url) { is_project ? package_filter_url(:type, package_type) : group_filter_url(:type, package_type) }
+
+ subject { get api(url, user) }
+
+ it "returns #{package_type} packages" do
+ subject
+
+ expect(json_response.length).to eq(1)
+ expect(json_response.map { |package| package['package_type'] }).to contain_exactly(package_type)
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'package workhorse uploads' do
+ context 'without a workhorse header' do
+ let(:workhorse_token) { JWT.encode({ 'iss' => 'invalid header' }, Gitlab::Workhorse.secret, 'HS256') }
+
+ it_behaves_like 'returning response status', :forbidden
+
+ it 'logs an error' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception).once
+
+ subject
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb b/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb
index 0e6ecf49cd0..2ddbdebdb97 100644
--- a/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb
@@ -25,19 +25,18 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
- allow(project_repository_double).to receive(:create_repository)
- .and_return(true)
allow(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
allow(project_repository_double).to receive(:checksum)
.and_return(project_repository_checksum)
- allow(repository_double).to receive(:create_repository)
- .and_return(true)
allow(repository_double).to receive(:replicate)
.with(repository.raw)
allow(repository_double).to receive(:checksum)
.and_return(repository_checksum)
+
+ expect(GitlabShellWorker).to receive(:perform_async).with(:mv_repository, 'default', anything, anything)
+ .twice.and_call_original
end
it "moves the project and its #{repository_type} repository to the new storage and unmarks the repository as read only" do
@@ -48,6 +47,7 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
old_repository_path = repository.full_path
result = subject.execute
+ project.reload
expect(result).to be_success
expect(project).not_to be_repository_read_only
@@ -101,15 +101,11 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
it 'unmarks the repository as read-only without updating the repository storage' do
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
- allow(project_repository_double).to receive(:create_repository)
- .and_return(true)
allow(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
allow(project_repository_double).to receive(:checksum)
.and_return(project_repository_checksum)
- allow(repository_double).to receive(:create_repository)
- .and_return(true)
allow(repository_double).to receive(:replicate)
.with(repository.raw)
.and_raise(Gitlab::Git::CommandError)
@@ -128,15 +124,11 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
it 'unmarks the repository as read-only without updating the repository storage' do
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
- allow(project_repository_double).to receive(:create_repository)
- .and_return(true)
allow(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
allow(project_repository_double).to receive(:checksum)
.and_return(project_repository_checksum)
- allow(repository_double).to receive(:create_repository)
- .and_return(true)
allow(repository_double).to receive(:replicate)
.with(repository.raw)
allow(repository_double).to receive(:checksum)
diff --git a/spec/support/shared_examples/services/resource_events/change_milestone_service_shared_examples.rb b/spec/support/shared_examples/services/resource_events/change_milestone_service_shared_examples.rb
index c5f84e205cf..ef41c2fcc13 100644
--- a/spec/support/shared_examples/services/resource_events/change_milestone_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/resource_events/change_milestone_service_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'a milestone events creator' do
+RSpec.shared_examples 'a milestone events creator' do
let_it_be(:user) { create(:user) }
let(:created_at_time) { Time.utc(2019, 12, 30) }
diff --git a/spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb b/spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb
index efcb83a34af..ebe78c299a5 100644
--- a/spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb
@@ -63,16 +63,6 @@ RSpec.shared_examples 'WikiPages::CreateService#execute' do |container_type|
include_examples 'correct event created'
end
- context 'the feature is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it 'does not record the activity' do
- expect { service.execute }.not_to change(Event, :count)
- end
- end
-
context 'when the options are bad' do
let(:page_title) { '' }
diff --git a/spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb b/spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb
index 1231c012c31..db1b50fdf3c 100644
--- a/spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb
@@ -37,14 +37,4 @@ RSpec.shared_examples 'WikiPages::DestroyService#execute' do |container_type|
expect { service.execute(nil) }.not_to change { counter.read(:delete) }
end
-
- context 'the feature is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it 'does not record the activity' do
- expect { service.execute(page) }.not_to change(Event, :count)
- end
- end
end
diff --git a/spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb b/spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb
index 77354fec069..0191a6dfbc9 100644
--- a/spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb
@@ -67,16 +67,6 @@ RSpec.shared_examples 'WikiPages::UpdateService#execute' do |container_type|
include_examples 'adds activity event'
end
- context 'the feature is disabled' do
- before do
- stub_feature_flags(wiki_events: false)
- end
-
- it 'does not record the activity' do
- expect { service.execute(page) }.not_to change(Event, :count)
- end
- end
-
context 'when the options are bad' do
let(:page_title) { '' }
diff --git a/spec/support/shared_examples/snippet_blob_shared_examples.rb b/spec/support/shared_examples/snippet_blob_shared_examples.rb
new file mode 100644
index 00000000000..ba97688d017
--- /dev/null
+++ b/spec/support/shared_examples/snippet_blob_shared_examples.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.shared_examples 'snippet blob raw path' do
+ let(:blob) { snippet.blobs.first }
+ let(:ref) { blob.repository.root_ref }
+
+ context 'for PersonalSnippets' do
+ let(:snippet) { personal_snippet }
+
+ it 'returns the raw personal snippet blob path' do
+ expect(subject).to eq("/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}")
+ end
+ end
+
+ context 'for ProjectSnippets' do
+ let(:snippet) { project_snippet }
+
+ it 'returns the raw project snippet blob path' do
+ expect(subject).to eq("/#{snippet.project.full_path}/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}")
+ end
+ end
+end
diff --git a/spec/support/shared_examples/uploaders/upload_type_shared_examples.rb b/spec/support/shared_examples/uploaders/upload_type_shared_examples.rb
index e58723324d3..9e0104e1410 100644
--- a/spec/support/shared_examples/uploaders/upload_type_shared_examples.rb
+++ b/spec/support/shared_examples/uploaders/upload_type_shared_examples.rb
@@ -2,7 +2,7 @@
# @param path [String] the path to file to upload. E.g. File.join('spec', 'fixtures', 'sanitized.svg')
# @param uploader [CarrierWave::Uploader::Base] uploader to handle the upload.
-shared_examples 'denied carrierwave upload' do
+RSpec.shared_examples 'denied carrierwave upload' do
it 'will deny upload' do
fixture_file = fixture_file_upload(path)
expect { uploader.cache!(fixture_file) }.to raise_exception(CarrierWave::IntegrityError)
@@ -11,7 +11,7 @@ end
# @param path [String] the path to file to upload. E.g. File.join('spec', 'fixtures', 'sanitized.svg')
# @param uploader [CarrierWave::Uploader::Base] uploader to handle the upload.
-shared_examples 'accepted carrierwave upload' do
+RSpec.shared_examples 'accepted carrierwave upload' do
let(:fixture_file) { fixture_file_upload(path) }
before do
@@ -30,7 +30,7 @@ end
# @param path [String] the path to file to upload. E.g. File.join('spec', 'fixtures', 'sanitized.svg')
# @param uploader [CarrierWave::Uploader::Base] uploader to handle the upload.
# @param content_type [String] the upload file content type after cache
-shared_examples 'upload with content type' do |content_type|
+RSpec.shared_examples 'upload with content type' do |content_type|
let(:fixture_file) { fixture_file_upload(path, content_type) }
it 'will not change upload file content type' do
diff --git a/spec/support/shared_examples/views/pipeline_status_changes_email.rb b/spec/support/shared_examples/views/pipeline_status_changes_email.rb
index 15b4ce9c44e..698f11c2216 100644
--- a/spec/support/shared_examples/views/pipeline_status_changes_email.rb
+++ b/spec/support/shared_examples/views/pipeline_status_changes_email.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'pipeline status changes email' do
+RSpec.shared_examples 'pipeline status changes email' do
include Devise::Test::ControllerHelpers
let(:user) { create(:user, developer_projects: [project]) }
diff --git a/spec/support/shared_examples/views/plain_text_email.rb b/spec/support/shared_examples/views/plain_text_email.rb
new file mode 100644
index 00000000000..23f9262b446
--- /dev/null
+++ b/spec/support/shared_examples/views/plain_text_email.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'renders plain text email correctly' do
+ it 'renders the email without HTML links' do
+ render
+
+ expect(rendered).to have_no_selector('a')
+ end
+end
diff --git a/spec/support/shared_examples/workers/gitlab/jira_import/jira_import_workers_shared_examples.rb b/spec/support/shared_examples/workers/gitlab/jira_import/jira_import_workers_shared_examples.rb
index ae8c82cb67c..2cca76c8fe3 100644
--- a/spec/support/shared_examples/workers/gitlab/jira_import/jira_import_workers_shared_examples.rb
+++ b/spec/support/shared_examples/workers/gitlab/jira_import/jira_import_workers_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'include import workers modules' do
+RSpec.shared_examples 'include import workers modules' do
it { expect(described_class).to include_module(ApplicationWorker) }
it { expect(described_class).to include_module(Gitlab::JiraImport::QueueOptions) }
@@ -12,7 +12,7 @@ shared_examples 'include import workers modules' do
end
end
-shared_examples 'does not advance to next stage' do
+RSpec.shared_examples 'does not advance to next stage' do
it 'does not advance to next stage' do
expect(Gitlab::JiraImport::AdvanceStageWorker).not_to receive(:perform_async)
@@ -20,7 +20,7 @@ shared_examples 'does not advance to next stage' do
end
end
-shared_examples 'cannot do Jira import' do
+RSpec.shared_examples 'cannot do Jira import' do
it 'does not advance to next stage' do
worker = described_class.new
expect(worker).not_to receive(:import)
@@ -29,7 +29,7 @@ shared_examples 'cannot do Jira import' do
end
end
-shared_examples 'advance to next stage' do |next_stage|
+RSpec.shared_examples 'advance to next stage' do |next_stage|
let(:job_waiter) { Gitlab::JobWaiter.new(2, 'some-job-key') }
it "advances to #{next_stage} stage" do
diff --git a/spec/support_specs/helpers/active_record/query_recorder_spec.rb b/spec/support_specs/helpers/active_record/query_recorder_spec.rb
index d15fbb5d4c3..f968f511a2a 100644
--- a/spec/support_specs/helpers/active_record/query_recorder_spec.rb
+++ b/spec/support_specs/helpers/active_record/query_recorder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ActiveRecord::QueryRecorder do
+RSpec.describe ActiveRecord::QueryRecorder do
before do
stub_const('TestQueries', Class.new(ActiveRecord::Base))
diff --git a/spec/support_specs/helpers/graphql_helpers_spec.rb b/spec/support_specs/helpers/graphql_helpers_spec.rb
index bfc71f519cf..bc777621674 100644
--- a/spec/support_specs/helpers/graphql_helpers_spec.rb
+++ b/spec/support_specs/helpers/graphql_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GraphqlHelpers do
+RSpec.describe GraphqlHelpers do
include GraphqlHelpers
describe '.graphql_mutation' do
diff --git a/spec/support_specs/helpers/stub_feature_flags_spec.rb b/spec/support_specs/helpers/stub_feature_flags_spec.rb
index e90a7f68a90..8d5f16751ae 100644
--- a/spec/support_specs/helpers/stub_feature_flags_spec.rb
+++ b/spec/support_specs/helpers/stub_feature_flags_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe StubFeatureFlags do
+RSpec.describe StubFeatureFlags do
let(:feature_name) { :test_feature }
describe '#stub_feature_flags' do
diff --git a/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb b/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb
index 4a711b43d9a..15b846f28cb 100644
--- a/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb
+++ b/spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExceedQueryLimitHelpers do
+RSpec.describe ExceedQueryLimitHelpers do
before do
stub_const('TestQueries', Class.new(ActiveRecord::Base))
stub_const('TestMatcher', Class.new)
diff --git a/spec/tasks/cache/clear/redis_spec.rb b/spec/tasks/cache/clear/redis_spec.rb
index 4f597988763..d2de068f254 100644
--- a/spec/tasks/cache/clear/redis_spec.rb
+++ b/spec/tasks/cache/clear/redis_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'clearing redis cache' do
+RSpec.describe 'clearing redis cache' do
before do
Rake.application.rake_require 'tasks/cache'
end
diff --git a/spec/tasks/config_lint_spec.rb b/spec/tasks/config_lint_spec.rb
index c6c11d76388..f5a382989ea 100644
--- a/spec/tasks/config_lint_spec.rb
+++ b/spec/tasks/config_lint_spec.rb
@@ -3,7 +3,7 @@
require 'rake_helper'
Rake.application.rake_require 'tasks/config_lint'
-describe ConfigLint do
+RSpec.describe ConfigLint do
let(:files) { ['lib/support/fake.sh'] }
it 'errors out if any bash scripts have errors' do
@@ -15,7 +15,7 @@ describe ConfigLint do
end
end
-describe 'config_lint rake task' do
+RSpec.describe 'config_lint rake task' do
before do
# Prevent `system` from actually being called
allow(Kernel).to receive(:system).and_return(true)
diff --git a/spec/tasks/gitlab/artifacts/check_rake_spec.rb b/spec/tasks/gitlab/artifacts/check_rake_spec.rb
index 04015f0e21a..d1d02ab9bc5 100644
--- a/spec/tasks/gitlab/artifacts/check_rake_spec.rb
+++ b/spec/tasks/gitlab/artifacts/check_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:artifacts rake tasks' do
+RSpec.describe 'gitlab:artifacts rake tasks' do
describe 'check' do
let!(:artifact) { create(:ci_job_artifact, :archive, :correct_checksum) }
diff --git a/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb b/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb
index 9ee00b4297b..94a8da9478a 100644
--- a/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb
+++ b/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:artifacts namespace rake task' do
+RSpec.describe 'gitlab:artifacts namespace rake task' do
before(:context) do
Rake.application.rake_require 'tasks/gitlab/artifacts/migrate'
end
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index febc18e497a..661ce8e74d2 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rake'
-describe 'gitlab:app namespace rake task', :delete do
+RSpec.describe 'gitlab:app namespace rake task', :delete do
let(:enable_registry) { true }
def tars_glob
diff --git a/spec/tasks/gitlab/check_rake_spec.rb b/spec/tasks/gitlab/check_rake_spec.rb
index e3e2a22add9..7956b5c57e6 100644
--- a/spec/tasks/gitlab/check_rake_spec.rb
+++ b/spec/tasks/gitlab/check_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'check.rake' do
+RSpec.describe 'check.rake' do
before do
Rake.application.rake_require 'tasks/gitlab/check'
diff --git a/spec/tasks/gitlab/cleanup_rake_spec.rb b/spec/tasks/gitlab/cleanup_rake_spec.rb
index 8db18895c24..dc460611169 100644
--- a/spec/tasks/gitlab/cleanup_rake_spec.rb
+++ b/spec/tasks/gitlab/cleanup_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:cleanup rake tasks' do
+RSpec.describe 'gitlab:cleanup rake tasks' do
before do
Rake.application.rake_require 'tasks/gitlab/cleanup'
end
diff --git a/spec/tasks/gitlab/container_registry_rake_spec.rb b/spec/tasks/gitlab/container_registry_rake_spec.rb
index 181d5c8b7c8..b83ff567126 100644
--- a/spec/tasks/gitlab/container_registry_rake_spec.rb
+++ b/spec/tasks/gitlab/container_registry_rake_spec.rb
@@ -2,25 +2,19 @@
require 'rake_helper'
-describe 'gitlab:container_registry namespace rake tasks' do
- let_it_be(:application_settings) { Gitlab::CurrentSettings }
+RSpec.describe 'gitlab:container_registry namespace rake tasks' do
let_it_be(:api_url) { 'http://registry.gitlab' }
before :all do
Rake.application.rake_require 'tasks/gitlab/container_registry'
end
- describe 'configure' do
- before do
- stub_access_token
- stub_container_registry_config(enabled: true, api_url: api_url)
- end
-
+ describe '#configure' do
subject { run_rake_task('gitlab:container_registry:configure') }
shared_examples 'invalid config' do
- it 'does not update the application settings' do
- expect(application_settings).not_to receive(:update!)
+ it 'does not call UpdateContainerRegistryInfoService' do
+ expect_any_instance_of(UpdateContainerRegistryInfoService).not_to receive(:execute)
subject
end
@@ -30,7 +24,7 @@ describe 'gitlab:container_registry namespace rake tasks' do
end
it 'prints a warning message' do
- expect { subject }.to output(/Registry is not enabled or registry api url is not present./).to_stdout
+ expect { subject }.to output("Registry is not enabled or registry api url is not present.\n").to_stdout
end
end
@@ -50,74 +44,18 @@ describe 'gitlab:container_registry namespace rake tasks' do
it_behaves_like 'invalid config'
end
- context 'when creating a registry client instance' do
- let(:token) { 'foo' }
- let(:client) { ContainerRegistry::Client.new(api_url, token: token) }
-
+ context 'when container registry is enabled and api_url is not blank' do
before do
- stub_registry_info({})
- end
-
- it 'uses a token with no access permissions' do
- expect(Auth::ContainerRegistryAuthenticationService)
- .to receive(:access_token).with([], []).and_return(token)
- expect(ContainerRegistry::Client)
- .to receive(:new).with(api_url, token: token).and_return(client)
-
- run_rake_task('gitlab:container_registry:configure')
+ stub_container_registry_config(enabled: true, api_url: api_url)
end
- end
-
- context 'when unabled to detect the container registry type' do
- it 'fails and raises an error message' do
- stub_registry_info({})
-
- run_rake_task('gitlab:container_registry:configure')
-
- application_settings.reload
- expect(application_settings.container_registry_vendor).to be_blank
- expect(application_settings.container_registry_version).to be_blank
- expect(application_settings.container_registry_features).to eq([])
- end
- end
- context 'when able to detect the container registry type' do
- context 'when using the GitLab container registry' do
- it 'updates application settings accordingly' do
- stub_registry_info(vendor: 'gitlab', version: '2.9.1-gitlab', features: %w[a,b,c])
-
- run_rake_task('gitlab:container_registry:configure')
-
- application_settings.reload
- expect(application_settings.container_registry_vendor).to eq('gitlab')
- expect(application_settings.container_registry_version).to eq('2.9.1-gitlab')
- expect(application_settings.container_registry_features).to eq(%w[a,b,c])
+ it 'calls UpdateContainerRegistryInfoService' do
+ expect_next_instance_of(UpdateContainerRegistryInfoService) do |service|
+ expect(service).to receive(:execute)
end
- end
- context 'when using a third-party container registry' do
- it 'updates application settings accordingly' do
- stub_registry_info(vendor: 'other', version: nil, features: nil)
-
- run_rake_task('gitlab:container_registry:configure')
-
- application_settings.reload
- expect(application_settings.container_registry_vendor).to eq('other')
- expect(application_settings.container_registry_version).to be_blank
- expect(application_settings.container_registry_features).to eq([])
- end
+ subject
end
end
end
-
- def stub_access_token
- allow(Auth::ContainerRegistryAuthenticationService)
- .to receive(:access_token).with([], []).and_return('foo')
- end
-
- def stub_registry_info(output)
- allow_next_instance_of(ContainerRegistry::Client) do |client|
- allow(client).to receive(:registry_info).and_return(output)
- end
- end
end
diff --git a/spec/tasks/gitlab/db_rake_spec.rb b/spec/tasks/gitlab/db_rake_spec.rb
index c3da5af5439..a78506803be 100644
--- a/spec/tasks/gitlab/db_rake_spec.rb
+++ b/spec/tasks/gitlab/db_rake_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rake'
-describe 'gitlab:db namespace rake task' do
+RSpec.describe 'gitlab:db namespace rake task' do
before :all do
Rake.application.rake_require 'active_record/railties/databases'
Rake.application.rake_require 'tasks/seed_fu'
diff --git a/spec/tasks/gitlab/external_diffs_rake_spec.rb b/spec/tasks/gitlab/external_diffs_rake_spec.rb
new file mode 100644
index 00000000000..66e555734b3
--- /dev/null
+++ b/spec/tasks/gitlab/external_diffs_rake_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'rake_helper'
+
+RSpec.describe 'gitlab:external_diffs rake tasks' do
+ before do
+ Rake.application.rake_require 'tasks/gitlab/external_diffs'
+ end
+
+ describe 'force_object_storage task' do
+ it 'forces externally stored merge request diffs to object storage' do
+ db = create(:merge_request).merge_request_diff
+ file = create(:merge_request).merge_request_diff.tap { |o| o.update_columns(stored_externally: true, external_diff_store: 1) }
+ object = create(:merge_request).merge_request_diff.tap { |o| o.update_columns(stored_externally: true, external_diff_store: 2) }
+
+ run_rake_task('gitlab:external_diffs:force_object_storage')
+
+ expect(db.reload).not_to be_stored_externally
+ expect(file.reload).to be_stored_externally
+ expect(object.reload).to be_stored_externally
+
+ expect(file.external_diff_store).to eq(2)
+ expect(object.external_diff_store).to eq(2)
+ end
+
+ it 'limits batches according to BATCH_SIZE, START_ID, and END_ID' do
+ stub_env('START_ID' => 'foo', 'END_ID' => 'bar', 'BATCH_SIZE' => 'baz')
+
+ expect(MergeRequestDiff).to receive(:in_batches).with(start: 'foo', finish: 'bar', of: 'baz')
+
+ run_rake_task('gitlab:external_diffs:force_object_storage')
+ end
+ end
+end
diff --git a/spec/tasks/gitlab/generate_sample_prometheus_data_spec.rb b/spec/tasks/gitlab/generate_sample_prometheus_data_spec.rb
index 7620047624a..a8effef2d7b 100644
--- a/spec/tasks/gitlab/generate_sample_prometheus_data_spec.rb
+++ b/spec/tasks/gitlab/generate_sample_prometheus_data_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:generate_sample_prometheus_data rake task' do
+RSpec.describe 'gitlab:generate_sample_prometheus_data rake task' do
let(:cluster) { create(:cluster, :provided_by_user, :project) }
let(:environment) { create(:environment, project: cluster.project) }
let(:sample_query_file) { File.join(Rails.root, Metrics::SampleMetricsService::DIRECTORY, 'test_query_result.yml') }
diff --git a/spec/tasks/gitlab/git_rake_spec.rb b/spec/tasks/gitlab/git_rake_spec.rb
index b8156e55ec7..4bc1b460d9b 100644
--- a/spec/tasks/gitlab/git_rake_spec.rb
+++ b/spec/tasks/gitlab/git_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:git rake tasks' do
+RSpec.describe 'gitlab:git rake tasks' do
let(:base_path) { 'tmp/tests/default_storage' }
let!(:project) { create(:project, :repository) }
diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb
index d9fdc183bfe..c59370a7a32 100644
--- a/spec/tasks/gitlab/gitaly_rake_spec.rb
+++ b/spec/tasks/gitlab/gitaly_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:gitaly namespace rake task' do
+RSpec.describe 'gitlab:gitaly namespace rake task' do
before :all do
Rake.application.rake_require 'tasks/gitlab/gitaly'
end
diff --git a/spec/tasks/gitlab/info_rake_spec.rb b/spec/tasks/gitlab/info_rake_spec.rb
index 8d6b3985380..941f3429017 100644
--- a/spec/tasks/gitlab/info_rake_spec.rb
+++ b/spec/tasks/gitlab/info_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:env:info' do
+RSpec.describe 'gitlab:env:info' do
before do
Rake.application.rake_require 'tasks/gitlab/info'
diff --git a/spec/tasks/gitlab/ldap_rake_spec.rb b/spec/tasks/gitlab/ldap_rake_spec.rb
index bbc3f625088..275fcb98dd0 100644
--- a/spec/tasks/gitlab/ldap_rake_spec.rb
+++ b/spec/tasks/gitlab/ldap_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:ldap:rename_provider rake task' do
+RSpec.describe 'gitlab:ldap:rename_provider rake task' do
it 'completes without error' do
Rake.application.rake_require 'tasks/gitlab/ldap'
stub_warn_user_is_not_gitlab
diff --git a/spec/tasks/gitlab/lfs/check_rake_spec.rb b/spec/tasks/gitlab/lfs/check_rake_spec.rb
index 3d698efdcb6..fd1b6d010e4 100644
--- a/spec/tasks/gitlab/lfs/check_rake_spec.rb
+++ b/spec/tasks/gitlab/lfs/check_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:lfs rake tasks' do
+RSpec.describe 'gitlab:lfs rake tasks' do
describe 'check' do
let!(:lfs_object) { create(:lfs_object, :with_file, :correct_oid) }
diff --git a/spec/tasks/gitlab/lfs/migrate_rake_spec.rb b/spec/tasks/gitlab/lfs/migrate_rake_spec.rb
index fc7be0eebcd..a59da615b94 100644
--- a/spec/tasks/gitlab/lfs/migrate_rake_spec.rb
+++ b/spec/tasks/gitlab/lfs/migrate_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:lfs namespace rake task' do
+RSpec.describe 'gitlab:lfs namespace rake task' do
before :all do
Rake.application.rake_require 'tasks/gitlab/lfs/migrate'
end
diff --git a/spec/tasks/gitlab/packages/migrate_rake_spec.rb b/spec/tasks/gitlab/packages/migrate_rake_spec.rb
new file mode 100644
index 00000000000..0a296eb0808
--- /dev/null
+++ b/spec/tasks/gitlab/packages/migrate_rake_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'rake_helper'
+
+RSpec.describe 'gitlab:packages namespace rake task' do
+ before :all do
+ Rake.application.rake_require 'tasks/gitlab/packages/migrate'
+ end
+
+ describe 'migrate' do
+ let(:local) { ObjectStorage::Store::LOCAL }
+ let(:remote) { ObjectStorage::Store::REMOTE }
+ let!(:package_file) { create(:package_file, :pom, file_store: local) }
+
+ def packages_migrate
+ run_rake_task('gitlab:packages:migrate')
+ end
+
+ context 'object storage disabled' do
+ before do
+ stub_packages_object_storage(enabled: false)
+ end
+
+ it "doesn't migrate files" do
+ expect { packages_migrate }.to raise_error('Object store is disabled for packages feature')
+ end
+ end
+
+ context 'object storage enabled' do
+ before do
+ stub_packages_object_storage
+ end
+
+ it 'migrates local file to object storage' do
+ expect { packages_migrate }.to change { package_file.reload.file_store }.from(local).to(remote)
+ end
+ end
+ end
+end
diff --git a/spec/tasks/gitlab/praefect_rake_spec.rb b/spec/tasks/gitlab/praefect_rake_spec.rb
index d986a778c8c..c67dba110c7 100644
--- a/spec/tasks/gitlab/praefect_rake_spec.rb
+++ b/spec/tasks/gitlab/praefect_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:praefect:replicas' do
+RSpec.describe 'gitlab:praefect:replicas' do
before do
Rake.application.rake_require 'tasks/gitlab/praefect'
end
diff --git a/spec/tasks/gitlab/seed/group_seed_rake_spec.rb b/spec/tasks/gitlab/seed/group_seed_rake_spec.rb
index ecf4e9575ab..0b69615eebc 100644
--- a/spec/tasks/gitlab/seed/group_seed_rake_spec.rb
+++ b/spec/tasks/gitlab/seed/group_seed_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:seed:group_seed rake task' do
+RSpec.describe 'gitlab:seed:group_seed rake task' do
let(:username) { 'group_seed' }
let!(:user) { create(:user, username: username) }
let(:task_params) { [2, username] }
diff --git a/spec/tasks/gitlab/shell_rake_spec.rb b/spec/tasks/gitlab/shell_rake_spec.rb
index 08b3fea0c80..a929daddb67 100644
--- a/spec/tasks/gitlab/shell_rake_spec.rb
+++ b/spec/tasks/gitlab/shell_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:shell rake tasks' do
+RSpec.describe 'gitlab:shell rake tasks' do
before do
Rake.application.rake_require 'tasks/gitlab/shell'
diff --git a/spec/tasks/gitlab/snippets_rake_spec.rb b/spec/tasks/gitlab/snippets_rake_spec.rb
index c4bb8d7897c..f21922e14b8 100644
--- a/spec/tasks/gitlab/snippets_rake_spec.rb
+++ b/spec/tasks/gitlab/snippets_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:snippets namespace rake task' do
+RSpec.describe 'gitlab:snippets namespace rake task' do
let_it_be(:user) { create(:user)}
let_it_be(:migrated) { create(:personal_snippet, :repository, author: user) }
let(:non_migrated) { create_list(:personal_snippet, 3, author: user) }
diff --git a/spec/tasks/gitlab/storage_rake_spec.rb b/spec/tasks/gitlab/storage_rake_spec.rb
index 226d3aaab4e..abd44adbdcc 100644
--- a/spec/tasks/gitlab/storage_rake_spec.rb
+++ b/spec/tasks/gitlab/storage_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'rake gitlab:storage:*' do
+RSpec.describe 'rake gitlab:storage:*' do
before do
Rake.application.rake_require 'tasks/gitlab/storage'
diff --git a/spec/tasks/gitlab/task_helpers_spec.rb b/spec/tasks/gitlab/task_helpers_spec.rb
index 8e6872f4d6f..2921913319b 100644
--- a/spec/tasks/gitlab/task_helpers_spec.rb
+++ b/spec/tasks/gitlab/task_helpers_spec.rb
@@ -6,7 +6,7 @@ class TestHelpersTest
include Gitlab::TaskHelpers
end
-describe Gitlab::TaskHelpers do
+RSpec.describe Gitlab::TaskHelpers do
subject { TestHelpersTest.new }
let(:repo) { 'https://gitlab.com/gitlab-org/gitlab-test.git' }
diff --git a/spec/tasks/gitlab/update_templates_rake_spec.rb b/spec/tasks/gitlab/update_templates_rake_spec.rb
index 14b4ad5e3d8..25151a2f3ae 100644
--- a/spec/tasks/gitlab/update_templates_rake_spec.rb
+++ b/spec/tasks/gitlab/update_templates_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:update_project_templates rake task' do
+RSpec.describe 'gitlab:update_project_templates rake task' do
let!(:tmpdir) { Dir.mktmpdir }
before do
diff --git a/spec/tasks/gitlab/uploads/check_rake_spec.rb b/spec/tasks/gitlab/uploads/check_rake_spec.rb
index 91f0cedb246..4c6f16dbcf9 100644
--- a/spec/tasks/gitlab/uploads/check_rake_spec.rb
+++ b/spec/tasks/gitlab/uploads/check_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:uploads rake tasks' do
+RSpec.describe 'gitlab:uploads rake tasks' do
describe 'check' do
let!(:upload) { create(:upload, path: Rails.root.join('spec/fixtures/banana_sample.gif')) }
diff --git a/spec/tasks/gitlab/uploads/migrate_rake_spec.rb b/spec/tasks/gitlab/uploads/migrate_rake_spec.rb
index 49026cd74f9..7f0f5c6767d 100644
--- a/spec/tasks/gitlab/uploads/migrate_rake_spec.rb
+++ b/spec/tasks/gitlab/uploads/migrate_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:uploads:migrate and migrate_to_local rake tasks' do
+RSpec.describe 'gitlab:uploads:migrate and migrate_to_local rake tasks' do
let(:model_class) { nil }
let(:uploader_class) { nil }
let(:mounted_as) { nil }
diff --git a/spec/tasks/gitlab/web_hook_rake_spec.rb b/spec/tasks/gitlab/web_hook_rake_spec.rb
index ee3c7e034f3..3e18ce5ab29 100644
--- a/spec/tasks/gitlab/web_hook_rake_spec.rb
+++ b/spec/tasks/gitlab/web_hook_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:web_hook namespace rake tasks' do
+RSpec.describe 'gitlab:web_hook namespace rake tasks' do
let_it_be(:group, refind: true) { create(:group) }
let_it_be(:project1, reload: true) { create(:project, namespace: group) }
let_it_be(:project2, reload: true) { create(:project, namespace: group) }
diff --git a/spec/tasks/gitlab/workhorse_rake_spec.rb b/spec/tasks/gitlab/workhorse_rake_spec.rb
index 139652ac258..2efbf0febac 100644
--- a/spec/tasks/gitlab/workhorse_rake_spec.rb
+++ b/spec/tasks/gitlab/workhorse_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:workhorse namespace rake task' do
+RSpec.describe 'gitlab:workhorse namespace rake task' do
before :all do
Rake.application.rake_require 'tasks/gitlab/workhorse'
end
diff --git a/spec/tasks/gitlab/x509/update_rake_spec.rb b/spec/tasks/gitlab/x509/update_rake_spec.rb
index 8c62c6c1728..93e97ab38ad 100644
--- a/spec/tasks/gitlab/x509/update_rake_spec.rb
+++ b/spec/tasks/gitlab/x509/update_rake_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'gitlab:x509 namespace rake task' do
+RSpec.describe 'gitlab:x509 namespace rake task' do
before :all do
Rake.application.rake_require 'tasks/gitlab/x509/update'
end
diff --git a/spec/tasks/migrate/schema_check_rake_spec.rb b/spec/tasks/migrate/schema_check_rake_spec.rb
index 1097a43cd8a..aebb3a6551f 100644
--- a/spec/tasks/migrate/schema_check_rake_spec.rb
+++ b/spec/tasks/migrate/schema_check_rake_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'rake'
-describe 'schema_version_check rake task' do
+RSpec.describe 'schema_version_check rake task' do
include StubENV
before :all do
diff --git a/spec/tasks/tokens_spec.rb b/spec/tasks/tokens_spec.rb
index 9c69155056a..13fcd37b426 100644
--- a/spec/tasks/tokens_spec.rb
+++ b/spec/tasks/tokens_spec.rb
@@ -2,7 +2,7 @@
require 'rake_helper'
-describe 'tokens rake tasks' do
+RSpec.describe 'tokens rake tasks' do
let!(:user) { create(:user) }
before do
diff --git a/spec/lib/quality/helm3_client_spec.rb b/spec/tooling/lib/tooling/helm3_client_spec.rb
index 1144ee9369d..f12bae051f0 100644
--- a/spec/lib/quality/helm3_client_spec.rb
+++ b/spec/tooling/lib/tooling/helm3_client_spec.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
+require_relative '../../../../tooling/lib/tooling/helm3_client'
-RSpec.describe Quality::Helm3Client do
- let(:namespace) { 'review-apps-ee' }
+RSpec.describe Tooling::Helm3Client do
+ let(:namespace) { 'review-apps' }
let(:release_name) { 'my-release' }
let(:raw_helm_list_page1) do
<<~OUTPUT
diff --git a/spec/lib/quality/kubernetes_client_spec.rb b/spec/tooling/lib/tooling/kubernetes_client_spec.rb
index 1cfee5200f3..fdd56aa0189 100644
--- a/spec/lib/quality/kubernetes_client_spec.rb
+++ b/spec/tooling/lib/tooling/kubernetes_client_spec.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
+require_relative '../../../../tooling/lib/tooling/kubernetes_client'
-RSpec.describe Quality::KubernetesClient do
- let(:namespace) { 'review-apps-ee' }
+RSpec.describe Tooling::KubernetesClient do
+ let(:namespace) { 'review-apps' }
let(:release_name) { 'my-release' }
let(:pod_for_release) { "pod-my-release-abcd" }
let(:raw_resource_names_str) { "NAME\nfoo\n#{pod_for_release}\nbar" }
diff --git a/spec/tooling/lib/tooling/test_file_finder_spec.rb b/spec/tooling/lib/tooling/test_file_finder_spec.rb
index 9c33f20877b..64b55b9b1d6 100644
--- a/spec/tooling/lib/tooling/test_file_finder_spec.rb
+++ b/spec/tooling/lib/tooling/test_file_finder_spec.rb
@@ -3,7 +3,7 @@
require_relative '../../../../tooling/lib/tooling/test_file_finder'
RSpec.describe Tooling::TestFileFinder do
- subject { Tooling::TestFileFinder.new(file) }
+ subject { described_class.new(file) }
describe '#test_files' do
context 'when given non .rb files' do
@@ -88,6 +88,78 @@ RSpec.describe Tooling::TestFileFinder do
end
end
+ context 'when given a factory file' do
+ let(:file) { 'spec/factories/users.rb' }
+
+ it 'returns spec/factories_spec.rb file' do
+ expect(subject.test_files).to contain_exactly('spec/factories_spec.rb')
+ end
+ end
+
+ context 'when given an ee factory file' do
+ let(:file) { 'ee/spec/factories/users.rb' }
+
+ it 'returns spec/factories_spec.rb file' do
+ expect(subject.test_files).to contain_exactly('spec/factories_spec.rb')
+ end
+ end
+
+ context 'when given db/structure.sql' do
+ let(:file) { 'db/structure.sql' }
+
+ it 'returns spec/db/schema_spec.rb' do
+ expect(subject.test_files).to contain_exactly('spec/db/schema_spec.rb')
+ end
+ end
+
+ context 'when given an initializer' do
+ let(:file) { 'config/initializers/action_mailer_hooks.rb' }
+
+ it 'returns the matching initializer spec' do
+ expect(subject.test_files).to contain_exactly('spec/initializers/action_mailer_hooks_spec.rb')
+ end
+ end
+
+ context 'when given a haml view' do
+ let(:file) { 'app/views/admin/users/_user.html.haml' }
+
+ it 'returns the matching view spec' do
+ expect(subject.test_files).to contain_exactly('spec/views/admin/users/_user.html.haml_spec.rb')
+ end
+ end
+
+ context 'when given a haml view in ee/' do
+ let(:file) { 'ee/app/views/admin/users/_user.html.haml' }
+
+ it 'returns the matching view spec' do
+ expect(subject.test_files).to contain_exactly('ee/spec/views/admin/users/_user.html.haml_spec.rb')
+ end
+ end
+
+ context 'when given a migration file' do
+ let(:file) { 'db/migrate/20191023152913_add_default_and_free_plans.rb' }
+
+ it 'returns the matching migration spec' do
+ test_files = %w[
+ spec/migrations/add_default_and_free_plans_spec.rb
+ spec/migrations/20191023152913_add_default_and_free_plans_spec.rb
+ ]
+ expect(subject.test_files).to contain_exactly(*test_files)
+ end
+ end
+
+ context 'when given a post-migration file' do
+ let(:file) { 'db/post_migrate/20200608072931_backfill_imported_snippet_repositories.rb' }
+
+ it 'returns the matching migration spec' do
+ test_files = %w[
+ spec/migrations/backfill_imported_snippet_repositories_spec.rb
+ spec/migrations/20200608072931_backfill_imported_snippet_repositories_spec.rb
+ ]
+ expect(subject.test_files).to contain_exactly(*test_files)
+ end
+ end
+
context 'with foss_test_only: true' do
subject { Tooling::TestFileFinder.new(file, foss_test_only: true) }
diff --git a/spec/uploaders/attachment_uploader_spec.rb b/spec/uploaders/attachment_uploader_spec.rb
index d5a92b9b317..05cffff1f1a 100644
--- a/spec/uploaders/attachment_uploader_spec.rb
+++ b/spec/uploaders/attachment_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AttachmentUploader do
+RSpec.describe AttachmentUploader do
let(:note) { create(:note, :with_attachment) }
let(:uploader) { note.attachment }
let(:upload) { create(:upload, :attachment_upload, model: uploader.model) }
diff --git a/spec/uploaders/avatar_uploader_spec.rb b/spec/uploaders/avatar_uploader_spec.rb
index 142ee557afa..1fadd9425ef 100644
--- a/spec/uploaders/avatar_uploader_spec.rb
+++ b/spec/uploaders/avatar_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AvatarUploader do
+RSpec.describe AvatarUploader do
let(:model) { build_stubbed(:user) }
let(:uploader) { described_class.new(model, :avatar) }
let(:upload) { create(:upload, model: model) }
diff --git a/spec/uploaders/content_type_whitelist_spec.rb b/spec/uploaders/content_type_whitelist_spec.rb
index 32d030cdfee..cf7463369ab 100644
--- a/spec/uploaders/content_type_whitelist_spec.rb
+++ b/spec/uploaders/content_type_whitelist_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContentTypeWhitelist do
+RSpec.describe ContentTypeWhitelist do
let_it_be(:model) { build_stubbed(:user) }
let!(:uploader) do
stub_const('DummyUploader', Class.new(CarrierWave::Uploader::Base))
diff --git a/spec/uploaders/design_management/design_v432x230_uploader_spec.rb b/spec/uploaders/design_management/design_v432x230_uploader_spec.rb
index 8c62b6ad6a8..b3a106ef94b 100644
--- a/spec/uploaders/design_management/design_v432x230_uploader_spec.rb
+++ b/spec/uploaders/design_management/design_v432x230_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DesignManagement::DesignV432x230Uploader do
+RSpec.describe DesignManagement::DesignV432x230Uploader do
include CarrierWave::Test::Matchers
let(:model) { create(:design_action, :with_image_v432x230) }
diff --git a/spec/uploaders/external_diff_uploader_spec.rb b/spec/uploaders/external_diff_uploader_spec.rb
index ee11085d54e..ee23c1e36b7 100644
--- a/spec/uploaders/external_diff_uploader_spec.rb
+++ b/spec/uploaders/external_diff_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExternalDiffUploader do
+RSpec.describe ExternalDiffUploader do
let(:diff) { create(:merge_request).merge_request_diff }
let(:path) { Gitlab.config.external_diffs.storage_path }
diff --git a/spec/uploaders/favicon_uploader_spec.rb b/spec/uploaders/favicon_uploader_spec.rb
index 0f5941b3f0a..6bff3ff8a14 100644
--- a/spec/uploaders/favicon_uploader_spec.rb
+++ b/spec/uploaders/favicon_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe FaviconUploader do
+RSpec.describe FaviconUploader do
let_it_be(:model) { build_stubbed(:user) }
let_it_be(:uploader) { described_class.new(model, :favicon) }
diff --git a/spec/uploaders/file_mover_spec.rb b/spec/uploaders/file_mover_spec.rb
index 474515b537c..0f7496f17d5 100644
--- a/spec/uploaders/file_mover_spec.rb
+++ b/spec/uploaders/file_mover_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe FileMover do
+RSpec.describe FileMover do
include FileMoverHelpers
let(:user) { create(:user) }
diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb
index 629c84778b9..9f1d276d092 100644
--- a/spec/uploaders/file_uploader_spec.rb
+++ b/spec/uploaders/file_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe FileUploader do
+RSpec.describe FileUploader do
let(:group) { create(:group, name: 'awesome') }
let(:project) { create(:project, :legacy_storage, namespace: group, name: 'project') }
let(:uploader) { described_class.new(project, :avatar) }
diff --git a/spec/uploaders/gitlab_uploader_spec.rb b/spec/uploaders/gitlab_uploader_spec.rb
index 80efdb88585..72845b47a53 100644
--- a/spec/uploaders/gitlab_uploader_spec.rb
+++ b/spec/uploaders/gitlab_uploader_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'carrierwave/storage/fog'
-describe GitlabUploader do
+RSpec.describe GitlabUploader do
let(:uploader_class) { Class.new(described_class) }
subject { uploader_class.new(double) }
diff --git a/spec/uploaders/import_export_uploader_spec.rb b/spec/uploaders/import_export_uploader_spec.rb
index 33cab911f86..b1fdcf067c6 100644
--- a/spec/uploaders/import_export_uploader_spec.rb
+++ b/spec/uploaders/import_export_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ImportExportUploader do
+RSpec.describe ImportExportUploader do
let(:model) { build_stubbed(:import_export_upload) }
let(:upload) { create(:upload, model: model) }
let(:import_export_upload) { ImportExportUpload.new }
diff --git a/spec/uploaders/job_artifact_uploader_spec.rb b/spec/uploaders/job_artifact_uploader_spec.rb
index a03cf3b9dea..9ccf216d1fc 100644
--- a/spec/uploaders/job_artifact_uploader_spec.rb
+++ b/spec/uploaders/job_artifact_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe JobArtifactUploader do
+RSpec.describe JobArtifactUploader do
let(:store) { described_class::Store::LOCAL }
let(:job_artifact) { create(:ci_job_artifact, file_store: store) }
let(:uploader) { described_class.new(job_artifact, :file) }
diff --git a/spec/uploaders/lfs_object_uploader_spec.rb b/spec/uploaders/lfs_object_uploader_spec.rb
index 1041e13d34f..d1a3fb243ac 100644
--- a/spec/uploaders/lfs_object_uploader_spec.rb
+++ b/spec/uploaders/lfs_object_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe LfsObjectUploader do
+RSpec.describe LfsObjectUploader do
let(:lfs_object) { create(:lfs_object, :with_file) }
let(:uploader) { described_class.new(lfs_object, :file) }
let(:path) { Gitlab.config.lfs.storage_path }
diff --git a/spec/uploaders/namespace_file_uploader_spec.rb b/spec/uploaders/namespace_file_uploader_spec.rb
index bc8d6a33e85..99bf4e130f2 100644
--- a/spec/uploaders/namespace_file_uploader_spec.rb
+++ b/spec/uploaders/namespace_file_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NamespaceFileUploader do
+RSpec.describe NamespaceFileUploader do
let(:group) { build_stubbed(:group) }
let(:uploader) { described_class.new(group) }
let(:upload) { create(:upload, :namespace_upload, model: group) }
diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb
index f42d581ece4..694aafe5ed5 100644
--- a/spec/uploaders/object_storage_spec.rb
+++ b/spec/uploaders/object_storage_spec.rb
@@ -18,7 +18,7 @@ class Implementation < GitlabUploader
end
end
-describe ObjectStorage do
+RSpec.describe ObjectStorage do
let(:uploader_class) { Implementation }
let(:object) { build_stubbed(:user) }
let(:uploader) { uploader_class.new(object, :file) }
diff --git a/spec/uploaders/packages/package_file_uploader_spec.rb b/spec/uploaders/packages/package_file_uploader_spec.rb
new file mode 100644
index 00000000000..1fe65649d7a
--- /dev/null
+++ b/spec/uploaders/packages/package_file_uploader_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::PackageFileUploader do
+ let(:package_file) { create(:package_file, :xml) }
+ let(:uploader) { described_class.new(package_file, :file) }
+ let(:path) { Gitlab.config.packages.storage_path }
+
+ subject { uploader }
+
+ it_behaves_like "builds correct paths",
+ store_dir: %r[\h{2}/\h{2}],
+ cache_dir: %r[/packages/tmp/cache],
+ work_dir: %r[/packages/tmp/work]
+
+ context 'object store is remote' do
+ before do
+ stub_packages_object_storage
+ end
+
+ include_context 'with storage', described_class::Store::REMOTE
+
+ it_behaves_like "builds correct paths",
+ store_dir: %r[\h{2}/\h{2}]
+ end
+
+ describe 'remote file' do
+ let(:package_file) { create(:package_file, :object_storage, :xml) }
+
+ context 'with object storage enabled' do
+ before do
+ stub_packages_object_storage
+ end
+
+ it 'can store file remotely' do
+ allow(ObjectStorage::BackgroundMoveWorker).to receive(:perform_async)
+
+ package_file
+
+ expect(package_file.file_store).to eq(described_class::Store::REMOTE)
+ expect(package_file.file.path).not_to be_blank
+ end
+ end
+ end
+end
diff --git a/spec/uploaders/personal_file_uploader_spec.rb b/spec/uploaders/personal_file_uploader_spec.rb
index c211ec3607c..d2eae5d7a54 100644
--- a/spec/uploaders/personal_file_uploader_spec.rb
+++ b/spec/uploaders/personal_file_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PersonalFileUploader do
+RSpec.describe PersonalFileUploader do
let(:model) { create(:personal_snippet) }
let(:uploader) { described_class.new(model) }
let(:upload) { create(:upload, :personal_snippet_upload) }
diff --git a/spec/uploaders/records_uploads_spec.rb b/spec/uploaders/records_uploads_spec.rb
index 1a3c416c74a..c1f5f962d77 100644
--- a/spec/uploaders/records_uploads_spec.rb
+++ b/spec/uploaders/records_uploads_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RecordsUploads do
+RSpec.describe RecordsUploads do
let!(:uploader) do
stub_const('RecordsUploadsExampleUploader', Class.new(GitlabUploader))
diff --git a/spec/uploaders/terraform/state_uploader_spec.rb b/spec/uploaders/terraform/state_uploader_spec.rb
index cbcb6298eca..dadfdf6e93f 100644
--- a/spec/uploaders/terraform/state_uploader_spec.rb
+++ b/spec/uploaders/terraform/state_uploader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Terraform::StateUploader do
+RSpec.describe Terraform::StateUploader do
subject { terraform_state.file }
let(:terraform_state) { create(:terraform_state, :with_file) }
diff --git a/spec/uploaders/uploader_helper_spec.rb b/spec/uploaders/uploader_helper_spec.rb
index 7bc6caf8224..e62e9b3c14b 100644
--- a/spec/uploaders/uploader_helper_spec.rb
+++ b/spec/uploaders/uploader_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UploaderHelper do
+RSpec.describe UploaderHelper do
let(:uploader) do
example_uploader = Class.new(CarrierWave::Uploader::Base) do
include UploaderHelper
diff --git a/spec/uploaders/workers/object_storage/background_move_worker_spec.rb b/spec/uploaders/workers/object_storage/background_move_worker_spec.rb
index 38b70d33993..a481939ed7a 100644
--- a/spec/uploaders/workers/object_storage/background_move_worker_spec.rb
+++ b/spec/uploaders/workers/object_storage/background_move_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ObjectStorage::BackgroundMoveWorker do
+RSpec.describe ObjectStorage::BackgroundMoveWorker do
let(:local) { ObjectStorage::Store::LOCAL }
let(:remote) { ObjectStorage::Store::REMOTE }
diff --git a/spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb b/spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb
index 7bf8512a6fd..ef5459ce788 100644
--- a/spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb
+++ b/spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ObjectStorage::MigrateUploadsWorker do
+RSpec.describe ObjectStorage::MigrateUploadsWorker do
let(:model_class) { Project }
let(:uploads) { Upload.all }
let(:to_store) { ObjectStorage::Store::REMOTE }
diff --git a/spec/validators/addressable_url_validator_spec.rb b/spec/validators/addressable_url_validator_spec.rb
index 46b1bebb074..394ffc7bbea 100644
--- a/spec/validators/addressable_url_validator_spec.rb
+++ b/spec/validators/addressable_url_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AddressableUrlValidator do
+RSpec.describe AddressableUrlValidator do
let!(:badge) { build(:badge, link_url: 'http://www.example.com') }
let(:validator) { described_class.new(validator_options.reverse_merge(attributes: [:link_url])) }
diff --git a/spec/validators/array_members_validator_spec.rb b/spec/validators/array_members_validator_spec.rb
new file mode 100644
index 00000000000..ff8f0da7651
--- /dev/null
+++ b/spec/validators/array_members_validator_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ArrayMembersValidator do
+ using RSpec::Parameterized::TableSyntax
+
+ child_class = Class.new
+
+ subject(:test_class) do
+ Class.new do
+ include ActiveModel::Model
+ include ActiveModel::Validations
+ attr_accessor :children
+ validates :children, array_members: { member_class: child_class }
+ end
+ end
+
+ where(:children, :is_valid) do
+ [child_class.new] | true
+ [Class.new.new] | false
+ [child_class.new, Class.new.new] | false
+ [] | false
+ child_class.new | false
+ [Class.new(child_class).new] | false
+ end
+
+ with_them do
+ it 'only accepts valid children nodes' do
+ expect(test_class.new(children: children).valid?).to eq(is_valid)
+ end
+ end
+
+ context 'validation message' do
+ subject(:test_class) do
+ Class.new do
+ include ActiveModel::Model
+ include ActiveModel::Validations
+ attr_accessor :children
+ end
+ end
+
+ context 'with default object name' do
+ it 'uses attribute name', :aggregate_failures do
+ test_class.class_eval do
+ validates :children, array_members: { member_class: child_class }
+ end
+
+ object = test_class.new(children: [])
+
+ expect(object.valid?).to be_falsey
+ expect(object.errors.messages).to eql(children: ['should be an array of children objects'])
+ end
+ end
+
+ context 'with custom object name' do
+ it 'uses that name', :aggregate_failures do
+ test_class.class_eval do
+ validates :children, array_members: { member_class: child_class, object_name: 'test' }
+ end
+
+ object = test_class.new(children: [])
+
+ expect(object.valid?).to be_falsey
+ expect(object.errors.messages).to eql(children: ['should be an array of test objects'])
+ end
+ end
+ end
+end
diff --git a/spec/validators/branch_filter_validator_spec.rb b/spec/validators/branch_filter_validator_spec.rb
index 957d1dd99bb..2d869fa674d 100644
--- a/spec/validators/branch_filter_validator_spec.rb
+++ b/spec/validators/branch_filter_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BranchFilterValidator do
+RSpec.describe BranchFilterValidator do
let(:validator) { described_class.new(attributes: [:push_events_branch_filter]) }
let(:hook) { build(:project_hook) }
diff --git a/spec/validators/color_validator_spec.rb b/spec/validators/color_validator_spec.rb
index e5a38ac9372..bd77b3df182 100644
--- a/spec/validators/color_validator_spec.rb
+++ b/spec/validators/color_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ColorValidator do
+RSpec.describe ColorValidator do
using RSpec::Parameterized::TableSyntax
subject do
diff --git a/spec/validators/cron_freeze_period_timezone_validator_spec.rb b/spec/validators/cron_freeze_period_timezone_validator_spec.rb
index d283b89fa54..ee2541eb858 100644
--- a/spec/validators/cron_freeze_period_timezone_validator_spec.rb
+++ b/spec/validators/cron_freeze_period_timezone_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CronFreezePeriodTimezoneValidator do
+RSpec.describe CronFreezePeriodTimezoneValidator do
using RSpec::Parameterized::TableSyntax
subject { create :ci_freeze_period }
diff --git a/spec/validators/cron_validator_spec.rb b/spec/validators/cron_validator_spec.rb
index d6605610402..dff3b506b89 100644
--- a/spec/validators/cron_validator_spec.rb
+++ b/spec/validators/cron_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CronValidator do
+RSpec.describe CronValidator do
subject do
Class.new do
include ActiveModel::Model
diff --git a/spec/validators/devise_email_validator_spec.rb b/spec/validators/devise_email_validator_spec.rb
index 1dbf3f66cfd..29a008f858a 100644
--- a/spec/validators/devise_email_validator_spec.rb
+++ b/spec/validators/devise_email_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeviseEmailValidator do
+RSpec.describe DeviseEmailValidator do
let!(:user) { build(:user, public_email: 'test@example.com') }
subject { validator.validate(user) }
diff --git a/spec/validators/js_regex_validator_spec.rb b/spec/validators/js_regex_validator_spec.rb
index dcc5e7ed4e8..d34ab78b44f 100644
--- a/spec/validators/js_regex_validator_spec.rb
+++ b/spec/validators/js_regex_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe JsRegexValidator do
+RSpec.describe JsRegexValidator do
describe '#validates_each' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/validators/json_schema_validator_spec.rb b/spec/validators/json_schema_validator_spec.rb
index 3e3a9c0e6a8..83eb0e2f3dd 100644
--- a/spec/validators/json_schema_validator_spec.rb
+++ b/spec/validators/json_schema_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe JsonSchemaValidator do
+RSpec.describe JsonSchemaValidator do
describe '#validates_each' do
let(:build_report_result) { build(:ci_build_report_result, :with_junit_success) }
diff --git a/spec/validators/named_ecdsa_key_validator_spec.rb b/spec/validators/named_ecdsa_key_validator_spec.rb
index 044c5b84a56..07326b7c55a 100644
--- a/spec/validators/named_ecdsa_key_validator_spec.rb
+++ b/spec/validators/named_ecdsa_key_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NamedEcdsaKeyValidator do
+RSpec.describe NamedEcdsaKeyValidator do
let(:validator) { described_class.new(attributes: [:key]) }
let!(:domain) { build(:pages_domain) }
diff --git a/spec/validators/namespace_path_validator_spec.rb b/spec/validators/namespace_path_validator_spec.rb
index bc9834dbd2a..25940bba49a 100644
--- a/spec/validators/namespace_path_validator_spec.rb
+++ b/spec/validators/namespace_path_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NamespacePathValidator do
+RSpec.describe NamespacePathValidator do
let(:validator) { described_class.new(attributes: [:path]) }
describe '.valid_path?' do
diff --git a/spec/validators/project_path_validator_spec.rb b/spec/validators/project_path_validator_spec.rb
index d6046f7214b..b92eb9106f6 100644
--- a/spec/validators/project_path_validator_spec.rb
+++ b/spec/validators/project_path_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectPathValidator do
+RSpec.describe ProjectPathValidator do
let(:validator) { described_class.new(attributes: [:path]) }
describe '.valid_path?' do
diff --git a/spec/validators/public_url_validator_spec.rb b/spec/validators/public_url_validator_spec.rb
index c81232d9b78..4fa6ad0cd6f 100644
--- a/spec/validators/public_url_validator_spec.rb
+++ b/spec/validators/public_url_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PublicUrlValidator do
+RSpec.describe PublicUrlValidator do
include_examples 'url validator examples', AddressableUrlValidator::DEFAULT_OPTIONS[:schemes]
include_examples 'public url validator examples', allow_local_requests_from_web_hooks_and_services: true
end
diff --git a/spec/validators/qualified_domain_array_validator_spec.rb b/spec/validators/qualified_domain_array_validator_spec.rb
index 664048c7544..788e007970a 100644
--- a/spec/validators/qualified_domain_array_validator_spec.rb
+++ b/spec/validators/qualified_domain_array_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe QualifiedDomainArrayValidator do
+RSpec.describe QualifiedDomainArrayValidator do
let(:qualified_domain_array_validator_test_class) do
Class.new do
include ActiveModel::Validations
diff --git a/spec/validators/sha_validator_spec.rb b/spec/validators/sha_validator_spec.rb
index 0a76570f65e..cf2c7a72883 100644
--- a/spec/validators/sha_validator_spec.rb
+++ b/spec/validators/sha_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ShaValidator do
+RSpec.describe ShaValidator do
let(:validator) { described_class.new(attributes: [:base_commit_sha]) }
let!(:merge_diff) { build(:merge_request_diff) }
diff --git a/spec/validators/system_hook_url_validator_spec.rb b/spec/validators/system_hook_url_validator_spec.rb
index 02384bbd1ce..445c0c7f67a 100644
--- a/spec/validators/system_hook_url_validator_spec.rb
+++ b/spec/validators/system_hook_url_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SystemHookUrlValidator do
+RSpec.describe SystemHookUrlValidator do
include_examples 'url validator examples', AddressableUrlValidator::DEFAULT_OPTIONS[:schemes]
include_examples 'public url validator examples', allow_local_requests_from_system_hooks: true
end
diff --git a/spec/validators/variable_duplicates_validator_spec.rb b/spec/validators/variable_duplicates_validator_spec.rb
index f48ebee7e0e..acc47ff225f 100644
--- a/spec/validators/variable_duplicates_validator_spec.rb
+++ b/spec/validators/variable_duplicates_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe VariableDuplicatesValidator do
+RSpec.describe VariableDuplicatesValidator do
let(:validator) { described_class.new(attributes: [:variables], **options) }
describe '#validate_each' do
diff --git a/spec/validators/x509_certificate_credentials_validator_spec.rb b/spec/validators/x509_certificate_credentials_validator_spec.rb
index 2a5a322622f..9baa31c7257 100644
--- a/spec/validators/x509_certificate_credentials_validator_spec.rb
+++ b/spec/validators/x509_certificate_credentials_validator_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe X509CertificateCredentialsValidator do
+RSpec.describe X509CertificateCredentialsValidator do
let(:certificate_data) { File.read('spec/fixtures/x509_certificate.crt') }
let(:pkey_data) { File.read('spec/fixtures/x509_certificate_pk.key') }
diff --git a/spec/views/admin/application_settings/_eks.html.haml_spec.rb b/spec/views/admin/application_settings/_eks.html.haml_spec.rb
index 52434557d3a..2f45eaadc9f 100644
--- a/spec/views/admin/application_settings/_eks.html.haml_spec.rb
+++ b/spec/views/admin/application_settings/_eks.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'admin/application_settings/_eks' do
+RSpec.describe 'admin/application_settings/_eks' do
let_it_be(:admin) { create(:admin) }
let(:page) { Capybara::Node::Simple.new(rendered) }
diff --git a/spec/views/admin/application_settings/_repository_storage.html.haml_spec.rb b/spec/views/admin/application_settings/_repository_storage.html.haml_spec.rb
index 63236dbb0c4..2915fe1964f 100644
--- a/spec/views/admin/application_settings/_repository_storage.html.haml_spec.rb
+++ b/spec/views/admin/application_settings/_repository_storage.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'admin/application_settings/_repository_storage.html.haml' do
+RSpec.describe 'admin/application_settings/_repository_storage.html.haml' do
let(:app_settings) { create(:application_setting) }
let(:repository_storages_weighted_attributes) { [:repository_storages_weighted_default, :repository_storages_weighted_mepmep, :repository_storages_weighted_foobar]}
let(:repository_storages_weighted) do
diff --git a/spec/views/admin/application_settings/general.html.haml_spec.rb b/spec/views/admin/application_settings/general.html.haml_spec.rb
index d8ca5dd1b49..5343847d755 100644
--- a/spec/views/admin/application_settings/general.html.haml_spec.rb
+++ b/spec/views/admin/application_settings/general.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'admin/application_settings/general.html.haml' do
+RSpec.describe 'admin/application_settings/general.html.haml' do
let(:app_settings) { build(:application_setting) }
let(:user) { create(:admin) }
diff --git a/spec/views/admin/application_settings/repository.html.haml_spec.rb b/spec/views/admin/application_settings/repository.html.haml_spec.rb
new file mode 100644
index 00000000000..b110bc277ac
--- /dev/null
+++ b/spec/views/admin/application_settings/repository.html.haml_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'admin/application_settings/repository.html.haml' do
+ let(:app_settings) { build(:application_setting) }
+ let(:user) { create(:admin) }
+
+ before do
+ assign(:application_setting, app_settings)
+ allow(view).to receive(:current_user).and_return(user)
+ end
+
+ describe 'default initial branch name' do
+ context 'when the feature flag is disabled' do
+ before do
+ stub_feature_flags(global_default_branch_name: false)
+ end
+
+ it 'does not show the setting section' do
+ render
+
+ expect(rendered).not_to have_css("#js-default-branch-name")
+ end
+ end
+
+ context 'when the feature flag is enabled' do
+ before do
+ stub_feature_flags(global_default_branch_name: true)
+ end
+
+ it 'has the setting section' do
+ render
+
+ expect(rendered).to have_css("#js-default-branch-name")
+ end
+
+ it 'renders the correct setting section content' do
+ render
+
+ expect(rendered).to have_content("Default initial branch name")
+ expect(rendered).to have_content("Set the default name of the initial branch when creating new repositories through the user interface.")
+ end
+ end
+ end
+end
diff --git a/spec/views/admin/dashboard/index.html.haml_spec.rb b/spec/views/admin/dashboard/index.html.haml_spec.rb
index 93fedde6e96..569a20e8f08 100644
--- a/spec/views/admin/dashboard/index.html.haml_spec.rb
+++ b/spec/views/admin/dashboard/index.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'admin/dashboard/index.html.haml' do
+RSpec.describe 'admin/dashboard/index.html.haml' do
include Devise::Test::ControllerHelpers
before do
diff --git a/spec/views/admin/sessions/new.html.haml_spec.rb b/spec/views/admin/sessions/new.html.haml_spec.rb
index b52ad0f9505..94870f0bdba 100644
--- a/spec/views/admin/sessions/new.html.haml_spec.rb
+++ b/spec/views/admin/sessions/new.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'admin/sessions/new.html.haml' do
+RSpec.describe 'admin/sessions/new.html.haml' do
let(:user) { create(:admin) }
before do
diff --git a/spec/views/admin/sessions/two_factor.html.haml_spec.rb b/spec/views/admin/sessions/two_factor.html.haml_spec.rb
index 2c061c7707b..9c5ff9925c1 100644
--- a/spec/views/admin/sessions/two_factor.html.haml_spec.rb
+++ b/spec/views/admin/sessions/two_factor.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'admin/sessions/two_factor.html.haml' do
+RSpec.describe 'admin/sessions/two_factor.html.haml' do
before do
allow(view).to receive(:current_user).and_return(user)
end
diff --git a/spec/views/admin/users/_user.html.haml_spec.rb b/spec/views/admin/users/_user.html.haml_spec.rb
index de5a291a6f8..aed05e4ea9b 100644
--- a/spec/views/admin/users/_user.html.haml_spec.rb
+++ b/spec/views/admin/users/_user.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'admin/users/_user.html.haml' do
+RSpec.describe 'admin/users/_user.html.haml' do
before do
allow(view).to receive(:user).and_return(user)
end
diff --git a/spec/views/ci/status/_badge.html.haml_spec.rb b/spec/views/ci/status/_badge.html.haml_spec.rb
index 59db828a0c7..6cbd9a61e98 100644
--- a/spec/views/ci/status/_badge.html.haml_spec.rb
+++ b/spec/views/ci/status/_badge.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'ci/status/_badge' do
+RSpec.describe 'ci/status/_badge' do
let(:user) { create(:user) }
let(:project) { create(:project, :private) }
let(:pipeline) { create(:ci_pipeline, project: project) }
diff --git a/spec/views/ci/status/_icon.html.haml_spec.rb b/spec/views/ci/status/_icon.html.haml_spec.rb
index 626159fc512..d0579734451 100644
--- a/spec/views/ci/status/_icon.html.haml_spec.rb
+++ b/spec/views/ci/status/_icon.html.haml_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'ci/status/_icon' do
+RSpec.describe 'ci/status/_icon' do
let(:user) { create(:user) }
let(:project) { create(:project, :private) }
let(:pipeline) { create(:ci_pipeline, project: project) }
diff --git a/spec/views/dashboard/projects/_blank_state_admin_welcome.haml_spec.rb b/spec/views/dashboard/projects/_blank_state_admin_welcome.haml_spec.rb
index bc92278bb22..9c064596f09 100644
--- a/spec/views/dashboard/projects/_blank_state_admin_welcome.haml_spec.rb
+++ b/spec/views/dashboard/projects/_blank_state_admin_welcome.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'dashboard/projects/_blank_state_admin_welcome.html.haml' do
+RSpec.describe 'dashboard/projects/_blank_state_admin_welcome.html.haml' do
let(:user) { create(:admin) }
before do
diff --git a/spec/views/dashboard/projects/_nav.html.haml_spec.rb b/spec/views/dashboard/projects/_nav.html.haml_spec.rb
index 61b6bfef120..a592c4e44d2 100644
--- a/spec/views/dashboard/projects/_nav.html.haml_spec.rb
+++ b/spec/views/dashboard/projects/_nav.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'dashboard/projects/_nav.html.haml' do
+RSpec.describe 'dashboard/projects/_nav.html.haml' do
it 'highlights All tab by default' do
render
diff --git a/spec/views/devise/sessions/new.html.haml_spec.rb b/spec/views/devise/sessions/new.html.haml_spec.rb
index 27bd683bbf0..b5c69f4f04d 100644
--- a/spec/views/devise/sessions/new.html.haml_spec.rb
+++ b/spec/views/devise/sessions/new.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'devise/sessions/new' do
+RSpec.describe 'devise/sessions/new' do
describe 'ldap' do
include LdapHelpers
diff --git a/spec/views/devise/shared/_signin_box.html.haml_spec.rb b/spec/views/devise/shared/_signin_box.html.haml_spec.rb
index dfd8c315e50..8b1af1866dc 100644
--- a/spec/views/devise/shared/_signin_box.html.haml_spec.rb
+++ b/spec/views/devise/shared/_signin_box.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'devise/shared/_signin_box' do
+RSpec.describe 'devise/shared/_signin_box' do
describe 'Crowd form' do
before do
stub_devise
diff --git a/spec/views/errors/access_denied.html.haml_spec.rb b/spec/views/errors/access_denied.html.haml_spec.rb
index b2e82847ab9..276d94111c6 100644
--- a/spec/views/errors/access_denied.html.haml_spec.rb
+++ b/spec/views/errors/access_denied.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'errors/access_denied' do
+RSpec.describe 'errors/access_denied' do
it 'does not fail to render when there is no message provided' do
expect { render }.not_to raise_error
end
diff --git a/spec/views/events/event/_push.html.haml_spec.rb b/spec/views/events/event/_push.html.haml_spec.rb
index d33a8aa86fc..f4d3258ff67 100644
--- a/spec/views/events/event/_push.html.haml_spec.rb
+++ b/spec/views/events/event/_push.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'events/event/_push.html.haml' do
+RSpec.describe 'events/event/_push.html.haml' do
let(:event) { build_stubbed(:push_event) }
context 'with a branch' do
diff --git a/spec/views/groups/_home_panel.html.haml_spec.rb b/spec/views/groups/_home_panel.html.haml_spec.rb
index 8960dfa67db..b8168b20450 100644
--- a/spec/views/groups/_home_panel.html.haml_spec.rb
+++ b/spec/views/groups/_home_panel.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'groups/_home_panel' do
+RSpec.describe 'groups/_home_panel' do
let(:group) { create(:group) }
before do
diff --git a/spec/views/groups/edit.html.haml_spec.rb b/spec/views/groups/edit.html.haml_spec.rb
index 9fc850841ee..83623ea7bb4 100644
--- a/spec/views/groups/edit.html.haml_spec.rb
+++ b/spec/views/groups/edit.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'groups/edit.html.haml' do
+RSpec.describe 'groups/edit.html.haml' do
include Devise::Test::ControllerHelpers
describe '"Share with group lock" setting' do
diff --git a/spec/views/help/index.html.haml_spec.rb b/spec/views/help/index.html.haml_spec.rb
index 3831ddacb72..c59790a346e 100644
--- a/spec/views/help/index.html.haml_spec.rb
+++ b/spec/views/help/index.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'help/index' do
+RSpec.describe 'help/index' do
include StubVersion
describe 'version information' do
diff --git a/spec/views/help/instance_configuration.html.haml_spec.rb b/spec/views/help/instance_configuration.html.haml_spec.rb
index 81d569b5cf4..7b431bb4180 100644
--- a/spec/views/help/instance_configuration.html.haml_spec.rb
+++ b/spec/views/help/instance_configuration.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'help/instance_configuration' do
+RSpec.describe 'help/instance_configuration' do
describe 'General Sections:' do
let(:instance_configuration) { build(:instance_configuration)}
let(:settings) { instance_configuration.settings }
diff --git a/spec/views/help/show.html.haml_spec.rb b/spec/views/help/show.html.haml_spec.rb
index 539c647c1d3..ab303919673 100644
--- a/spec/views/help/show.html.haml_spec.rb
+++ b/spec/views/help/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'help/show' do
+RSpec.describe 'help/show' do
describe 'Markdown rendering' do
before do
assign(:path, 'ssh/README')
diff --git a/spec/views/import/gitlab_projects/new.html.haml_spec.rb b/spec/views/import/gitlab_projects/new.html.haml_spec.rb
index 17636c99cbc..c09c798f487 100644
--- a/spec/views/import/gitlab_projects/new.html.haml_spec.rb
+++ b/spec/views/import/gitlab_projects/new.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'import/gitlab_projects/new.html.haml' do
+RSpec.describe 'import/gitlab_projects/new.html.haml' do
include Devise::Test::ControllerHelpers
let(:namespace) { build_stubbed(:namespace) }
diff --git a/spec/views/layouts/_head.html.haml_spec.rb b/spec/views/layouts/_head.html.haml_spec.rb
index 7011fa23327..25fcbeb61df 100644
--- a/spec/views/layouts/_head.html.haml_spec.rb
+++ b/spec/views/layouts/_head.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'layouts/_head' do
+RSpec.describe 'layouts/_head' do
include StubConfiguration
before do
diff --git a/spec/views/layouts/application.html.haml_spec.rb b/spec/views/layouts/application.html.haml_spec.rb
index 4270bbf1924..679d0b1ff60 100644
--- a/spec/views/layouts/application.html.haml_spec.rb
+++ b/spec/views/layouts/application.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'layouts/application' do
+RSpec.describe 'layouts/application' do
let(:user) { create(:user) }
before do
diff --git a/spec/views/layouts/header/_new_dropdown.haml_spec.rb b/spec/views/layouts/header/_new_dropdown.haml_spec.rb
index 1a04ffed103..cf33ec9884b 100644
--- a/spec/views/layouts/header/_new_dropdown.haml_spec.rb
+++ b/spec/views/layouts/header/_new_dropdown.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'layouts/header/_new_dropdown' do
+RSpec.describe 'layouts/header/_new_dropdown' do
let(:user) { create(:user) }
context 'group-specific links' do
diff --git a/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb
index 2f8a75a81c8..d1e756422d5 100644
--- a/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_admin.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'layouts/nav/sidebar/_admin' do
+RSpec.describe 'layouts/nav/sidebar/_admin' do
shared_examples 'page has active tab' do |title|
it "activates #{title} tab" do
render
diff --git a/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb
index 24b66a0e767..b3c8450fb48 100644
--- a/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'layouts/nav/sidebar/_group' do
+RSpec.describe 'layouts/nav/sidebar/_group' do
let(:group) { create(:group) }
before do
diff --git a/spec/views/layouts/nav/sidebar/_instance_statistics.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_instance_statistics.html.haml_spec.rb
index 7f7f5637035..d3b57f6dfcf 100644
--- a/spec/views/layouts/nav/sidebar/_instance_statistics.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_instance_statistics.html.haml_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe 'layouts/nav/sidebar/_instance_statistics' do
+RSpec.describe 'layouts/nav/sidebar/_instance_statistics' do
it_behaves_like 'has nav sidebar'
end
diff --git a/spec/views/layouts/nav/sidebar/_profile.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_profile.html.haml_spec.rb
index 6b820ab0b4c..0f6dcf8e57f 100644
--- a/spec/views/layouts/nav/sidebar/_profile.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_profile.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'layouts/nav/sidebar/_profile' do
+RSpec.describe 'layouts/nav/sidebar/_profile' do
let(:user) { create(:user) }
before do
diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
index 881ea818cb0..bf0bf63e164 100644
--- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'layouts/nav/sidebar/_project' do
+RSpec.describe 'layouts/nav/sidebar/_project' do
let(:project) { create(:project, :repository) }
before do
@@ -76,7 +76,7 @@ describe 'layouts/nav/sidebar/_project' do
it 'does not show the wiki tab' do
render
- expect(rendered).not_to have_link('Wiki', href: wiki_path(project.wiki))
+ expect(rendered).not_to have_link('Wiki')
end
end
end
@@ -109,6 +109,38 @@ describe 'layouts/nav/sidebar/_project' do
end
end
+ describe 'confluence tab' do
+ let!(:service) { create(:confluence_service, project: project, active: active) }
+
+ before do
+ render
+ end
+
+ context 'when the Confluence integration is active' do
+ let(:active) { true }
+
+ it 'shows the Confluence tab' do
+ expect(rendered).to have_link('Confluence', href: project_wikis_confluence_path(project))
+ end
+
+ it 'does not show the GitLab wiki tab' do
+ expect(rendered).not_to have_link('Wiki')
+ end
+ end
+
+ context 'when it is disabled' do
+ let(:active) { false }
+
+ it 'does not show the Confluence tab' do
+ expect(rendered).not_to have_link('Confluence')
+ end
+
+ it 'shows the GitLab wiki tab' do
+ expect(rendered).to have_link('Wiki', href: wiki_path(project.wiki))
+ end
+ end
+ end
+
describe 'ci/cd settings tab' do
before do
project.update!(archived: project_archived)
diff --git a/spec/views/notify/changed_milestone_email.html.haml_spec.rb b/spec/views/notify/changed_milestone_email.html.haml_spec.rb
index 194b58840a3..50a06683409 100644
--- a/spec/views/notify/changed_milestone_email.html.haml_spec.rb
+++ b/spec/views/notify/changed_milestone_email.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'notify/changed_milestone_email.html.haml' do
+RSpec.describe 'notify/changed_milestone_email.html.haml' do
let(:milestone) { create(:milestone, title: 'some-milestone') }
let(:milestone_link) { milestone_url(milestone) }
diff --git a/spec/views/notify/pipeline_failed_email.html.haml_spec.rb b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb
index 80dc14b523d..b41933f9c36 100644
--- a/spec/views/notify/pipeline_failed_email.html.haml_spec.rb
+++ b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'notify/pipeline_failed_email.html.haml' do
+RSpec.describe 'notify/pipeline_failed_email.html.haml' do
it_behaves_like 'pipeline status changes email' do
let(:title) { 'Your pipeline has failed' }
let(:status) { :failed }
diff --git a/spec/views/notify/pipeline_failed_email.text.erb_spec.rb b/spec/views/notify/pipeline_failed_email.text.erb_spec.rb
index 9a4cea408a6..a5c31632b0d 100644
--- a/spec/views/notify/pipeline_failed_email.text.erb_spec.rb
+++ b/spec/views/notify/pipeline_failed_email.text.erb_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'notify/pipeline_failed_email.text.erb' do
+RSpec.describe 'notify/pipeline_failed_email.text.erb' do
include Devise::Test::ControllerHelpers
let(:user) { create(:user, developer_projects: [project]) }
diff --git a/spec/views/notify/pipeline_fixed_email.html.haml_spec.rb b/spec/views/notify/pipeline_fixed_email.html.haml_spec.rb
index 382fc5ecdd3..2b3b08f8e8c 100644
--- a/spec/views/notify/pipeline_fixed_email.html.haml_spec.rb
+++ b/spec/views/notify/pipeline_fixed_email.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'notify/pipeline_fixed_email.html.haml' do
+RSpec.describe 'notify/pipeline_fixed_email.html.haml' do
it_behaves_like 'pipeline status changes email' do
let(:title) { 'Your pipeline has been fixed!' }
let(:status) { :success }
diff --git a/spec/views/notify/pipeline_fixed_email.text.erb_spec.rb b/spec/views/notify/pipeline_fixed_email.text.erb_spec.rb
index ec540dc3f77..8640998acaa 100644
--- a/spec/views/notify/pipeline_fixed_email.text.erb_spec.rb
+++ b/spec/views/notify/pipeline_fixed_email.text.erb_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'notify/pipeline_fixed_email.text.erb' do
+RSpec.describe 'notify/pipeline_fixed_email.text.erb' do
it_behaves_like 'pipeline status changes email' do
let(:title) { 'Your pipeline has been fixed!' }
let(:status) { :success }
diff --git a/spec/views/notify/pipeline_success_email.html.haml_spec.rb b/spec/views/notify/pipeline_success_email.html.haml_spec.rb
index 417909fd67b..51ea7ef5066 100644
--- a/spec/views/notify/pipeline_success_email.html.haml_spec.rb
+++ b/spec/views/notify/pipeline_success_email.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'notify/pipeline_success_email.html.haml' do
+RSpec.describe 'notify/pipeline_success_email.html.haml' do
it_behaves_like 'pipeline status changes email' do
let(:title) { 'Your pipeline has passed' }
let(:status) { :success }
diff --git a/spec/views/notify/pipeline_success_email.text.erb_spec.rb b/spec/views/notify/pipeline_success_email.text.erb_spec.rb
index 4a914cab85e..3acf4dd3e26 100644
--- a/spec/views/notify/pipeline_success_email.text.erb_spec.rb
+++ b/spec/views/notify/pipeline_success_email.text.erb_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'notify/pipeline_success_email.text.erb' do
+RSpec.describe 'notify/pipeline_success_email.text.erb' do
it_behaves_like 'pipeline status changes email' do
let(:title) { 'Your pipeline has passed' }
let(:status) { :success }
diff --git a/spec/views/notify/push_to_merge_request_email.text.haml_spec.rb b/spec/views/notify/push_to_merge_request_email.text.haml_spec.rb
new file mode 100644
index 00000000000..ce402533496
--- /dev/null
+++ b/spec/views/notify/push_to_merge_request_email.text.haml_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe 'notify/push_to_merge_request_email.text.haml' do
+ let(:user) { create(:user, developer_projects: [project]) }
+ let(:project) { create(:project, :repository) }
+ let(:merge_request) { create(:merge_request, :simple, source_project: project) }
+ let(:new_commits) { project.repository.commits_between('be93687618e4b132087f430a4d8fc3a609c9b77c', '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51') }
+
+ before do
+ assign(:updated_by_user, user)
+ assign(:project, project)
+ assign(:merge_request, merge_request)
+ assign(:existing_commits, [])
+ assign(:new_commits, new_commits)
+ end
+
+ it_behaves_like 'renders plain text email correctly'
+end
diff --git a/spec/views/profiles/preferences/show.html.haml_spec.rb b/spec/views/profiles/preferences/show.html.haml_spec.rb
index 2e50e329cfd..5acfbfb9db1 100644
--- a/spec/views/profiles/preferences/show.html.haml_spec.rb
+++ b/spec/views/profiles/preferences/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'profiles/preferences/show' do
+RSpec.describe 'profiles/preferences/show' do
using RSpec::Parameterized::TableSyntax
let_it_be(:user) { build(:user) }
diff --git a/spec/views/profiles/show.html.haml_spec.rb b/spec/views/profiles/show.html.haml_spec.rb
index 14e6feed3ab..daa1d20e6b1 100644
--- a/spec/views/profiles/show.html.haml_spec.rb
+++ b/spec/views/profiles/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'profiles/show' do
+RSpec.describe 'profiles/show' do
let(:user) { create(:user) }
before do
diff --git a/spec/views/projects/_home_panel.html.haml_spec.rb b/spec/views/projects/_home_panel.html.haml_spec.rb
index 9956144b601..548dba7874a 100644
--- a/spec/views/projects/_home_panel.html.haml_spec.rb
+++ b/spec/views/projects/_home_panel.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/_home_panel' do
+RSpec.describe 'projects/_home_panel' do
include ProjectForksHelper
context 'notifications' do
diff --git a/spec/views/projects/blob/_viewer.html.haml_spec.rb b/spec/views/projects/blob/_viewer.html.haml_spec.rb
index a798a72fa76..893cfec1491 100644
--- a/spec/views/projects/blob/_viewer.html.haml_spec.rb
+++ b/spec/views/projects/blob/_viewer.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/blob/_viewer.html.haml' do
+RSpec.describe 'projects/blob/_viewer.html.haml' do
include FakeBlobHelpers
let(:project) { build(:project) }
diff --git a/spec/views/projects/buttons/_dropdown.html.haml_spec.rb b/spec/views/projects/buttons/_dropdown.html.haml_spec.rb
index 2bc1de040d5..fc9d7c3ea91 100644
--- a/spec/views/projects/buttons/_dropdown.html.haml_spec.rb
+++ b/spec/views/projects/buttons/_dropdown.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/buttons/_dropdown' do
+RSpec.describe 'projects/buttons/_dropdown' do
let(:user) { create(:user) }
context 'user with all abilities' do
diff --git a/spec/views/projects/ci/lints/show.html.haml_spec.rb b/spec/views/projects/ci/lints/show.html.haml_spec.rb
index 8c3cf04bae6..bcfb952ca66 100644
--- a/spec/views/projects/ci/lints/show.html.haml_spec.rb
+++ b/spec/views/projects/ci/lints/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/ci/lints/show' do
+RSpec.describe 'projects/ci/lints/show' do
include Devise::Test::ControllerHelpers
let(:project) { create(:project, :repository) }
let(:config_processor) { Gitlab::Ci::YamlProcessor.new(YAML.dump(content)) }
diff --git a/spec/views/projects/clusters/clusters/gcp/_form.html.haml_spec.rb b/spec/views/projects/clusters/clusters/gcp/_form.html.haml_spec.rb
index 1cb2f9a4301..bf5cb6fb25d 100644
--- a/spec/views/projects/clusters/clusters/gcp/_form.html.haml_spec.rb
+++ b/spec/views/projects/clusters/clusters/gcp/_form.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'clusters/clusters/gcp/_form' do
+RSpec.describe 'clusters/clusters/gcp/_form' do
let(:admin) { create(:admin) }
let(:environment) { create(:environment) }
let(:gcp_cluster) { create(:cluster, :provided_by_gcp) }
diff --git a/spec/views/projects/commit/_commit_box.html.haml_spec.rb b/spec/views/projects/commit/_commit_box.html.haml_spec.rb
index 07c530670d2..9c97696493e 100644
--- a/spec/views/projects/commit/_commit_box.html.haml_spec.rb
+++ b/spec/views/projects/commit/_commit_box.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/commit/_commit_box.html.haml' do
+RSpec.describe 'projects/commit/_commit_box.html.haml' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
diff --git a/spec/views/projects/commit/branches.html.haml_spec.rb b/spec/views/projects/commit/branches.html.haml_spec.rb
index 0fe7165a790..f1064be3047 100644
--- a/spec/views/projects/commit/branches.html.haml_spec.rb
+++ b/spec/views/projects/commit/branches.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/commit/branches.html.haml' do
+RSpec.describe 'projects/commit/branches.html.haml' do
let(:project) { create(:project, :repository) }
before do
diff --git a/spec/views/projects/commit/show.html.haml_spec.rb b/spec/views/projects/commit/show.html.haml_spec.rb
index af28a8f9193..c2970abbe9f 100644
--- a/spec/views/projects/commit/show.html.haml_spec.rb
+++ b/spec/views/projects/commit/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/commit/show.html.haml' do
+RSpec.describe 'projects/commit/show.html.haml' do
let(:project) { create(:project, :repository) }
let(:commit) { project.commit }
diff --git a/spec/views/projects/commits/_commit.html.haml_spec.rb b/spec/views/projects/commits/_commit.html.haml_spec.rb
index 1c01a7f9a9c..898d3baae19 100644
--- a/spec/views/projects/commits/_commit.html.haml_spec.rb
+++ b/spec/views/projects/commits/_commit.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/commits/_commit.html.haml' do
+RSpec.describe 'projects/commits/_commit.html.haml' do
let(:template) { 'projects/commits/commit.html.haml' }
let(:project) { create(:project, :repository) }
let(:commit) { project.repository.commit(ref) }
diff --git a/spec/views/projects/deployments/_confirm_rollback_modal_spec.html_spec.rb b/spec/views/projects/deployments/_confirm_rollback_modal_spec.html_spec.rb
index 9168bc8e833..2fb7b6187eb 100644
--- a/spec/views/projects/deployments/_confirm_rollback_modal_spec.html_spec.rb
+++ b/spec/views/projects/deployments/_confirm_rollback_modal_spec.html_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/deployments/_confirm_rollback_modal' do
+RSpec.describe 'projects/deployments/_confirm_rollback_modal' do
let(:environment) { create(:environment, :with_review_app) }
let(:deployments) { environment.deployments }
let(:project) { environment.project }
diff --git a/spec/views/projects/diffs/_stats.html.haml_spec.rb b/spec/views/projects/diffs/_stats.html.haml_spec.rb
index eb853596f98..f0580b50349 100644
--- a/spec/views/projects/diffs/_stats.html.haml_spec.rb
+++ b/spec/views/projects/diffs/_stats.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/diffs/_stats.html.haml' do
+RSpec.describe 'projects/diffs/_stats.html.haml' do
let(:project) { create(:project, :repository) }
let(:commit) { project.commit('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') }
diff --git a/spec/views/projects/diffs/_viewer.html.haml_spec.rb b/spec/views/projects/diffs/_viewer.html.haml_spec.rb
index 27f271bb178..305c44190b4 100644
--- a/spec/views/projects/diffs/_viewer.html.haml_spec.rb
+++ b/spec/views/projects/diffs/_viewer.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/diffs/_viewer.html.haml' do
+RSpec.describe 'projects/diffs/_viewer.html.haml' do
include FakeBlobHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/views/projects/edit.html.haml_spec.rb b/spec/views/projects/edit.html.haml_spec.rb
index 0c0f74a41f0..b44d07d2ee4 100644
--- a/spec/views/projects/edit.html.haml_spec.rb
+++ b/spec/views/projects/edit.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/edit' do
+RSpec.describe 'projects/edit' do
include Devise::Test::ControllerHelpers
include ProjectForksHelper
diff --git a/spec/views/projects/environments/terminal.html.haml_spec.rb b/spec/views/projects/environments/terminal.html.haml_spec.rb
index b0b08a84f8d..bfb88f0f72b 100644
--- a/spec/views/projects/environments/terminal.html.haml_spec.rb
+++ b/spec/views/projects/environments/terminal.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/environments/terminal' do
+RSpec.describe 'projects/environments/terminal' do
let!(:environment) { create(:environment, :with_review_app) }
before do
diff --git a/spec/views/projects/imports/new.html.haml_spec.rb b/spec/views/projects/imports/new.html.haml_spec.rb
index 6cca369b9f6..edf9eadf924 100644
--- a/spec/views/projects/imports/new.html.haml_spec.rb
+++ b/spec/views/projects/imports/new.html.haml_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe "projects/imports/new.html.haml" do
+RSpec.describe "projects/imports/new.html.haml" do
let(:user) { create(:user) }
context 'when import fails' do
diff --git a/spec/views/projects/issues/_related_branches.html.haml_spec.rb b/spec/views/projects/issues/_related_branches.html.haml_spec.rb
index 6c9bbaea38c..ba6f7068024 100644
--- a/spec/views/projects/issues/_related_branches.html.haml_spec.rb
+++ b/spec/views/projects/issues/_related_branches.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/issues/_related_branches' do
+RSpec.describe 'projects/issues/_related_branches' do
include Devise::Test::ControllerHelpers
let(:pipeline) { build(:ci_pipeline, :success) }
diff --git a/spec/views/projects/issues/import_csv/_button.html.haml_spec.rb b/spec/views/projects/issues/import_csv/_button.html.haml_spec.rb
index 440edd376e0..8bc0a00d71c 100644
--- a/spec/views/projects/issues/import_csv/_button.html.haml_spec.rb
+++ b/spec/views/projects/issues/import_csv/_button.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/issues/import_csv/_button' do
+RSpec.describe 'projects/issues/import_csv/_button' do
include Devise::Test::ControllerHelpers
context 'when the user does not have edit permissions' do
diff --git a/spec/views/projects/issues/show.html.haml_spec.rb b/spec/views/projects/issues/show.html.haml_spec.rb
index 60a541916e9..b2d208f038a 100644
--- a/spec/views/projects/issues/show.html.haml_spec.rb
+++ b/spec/views/projects/issues/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/issues/show' do
+RSpec.describe 'projects/issues/show' do
include_context 'project show action'
context 'when the issue is closed' do
@@ -23,7 +23,7 @@ describe 'projects/issues/show' do
project.add_developer(user)
end
- it 'shows "Closed (moved)" if an issue has been moved' do
+ it 'shows "Closed (moved)" if an issue has been moved and closed' do
render
expect(rendered).to have_selector('.status-box-issue-closed:not(.hidden)', text: 'Closed (moved)')
@@ -41,6 +41,14 @@ describe 'projects/issues/show' do
expect(rendered).to have_selector("a[href=\"#{issue_path(new_issue)}\"]", text: 'moved')
end
+
+ it 'does not show "closed (moved)" if an issue has been moved and reopened (not closed)' do
+ allow(issue).to receive(:closed?).and_return(false)
+
+ render
+
+ expect(rendered).not_to have_selector('.status-box-issue-closed:not(.hidden)', text: 'Closed (moved)')
+ end
end
context 'when user cannot see moved issue' do
diff --git a/spec/views/projects/jobs/_build.html.haml_spec.rb b/spec/views/projects/jobs/_build.html.haml_spec.rb
index 681df77ea99..4256df9ce9a 100644
--- a/spec/views/projects/jobs/_build.html.haml_spec.rb
+++ b/spec/views/projects/jobs/_build.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/ci/jobs/_build' do
+RSpec.describe 'projects/ci/jobs/_build' do
include Devise::Test::ControllerHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/views/projects/jobs/_generic_commit_status.html.haml_spec.rb b/spec/views/projects/jobs/_generic_commit_status.html.haml_spec.rb
index f193ce0a552..9be2a6f0ce4 100644
--- a/spec/views/projects/jobs/_generic_commit_status.html.haml_spec.rb
+++ b/spec/views/projects/jobs/_generic_commit_status.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/generic_commit_statuses/_generic_commit_status.html.haml' do
+RSpec.describe 'projects/generic_commit_statuses/_generic_commit_status.html.haml' do
include Devise::Test::ControllerHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/views/projects/jobs/show.html.haml_spec.rb b/spec/views/projects/jobs/show.html.haml_spec.rb
index 903a16d9e53..83a00135629 100644
--- a/spec/views/projects/jobs/show.html.haml_spec.rb
+++ b/spec/views/projects/jobs/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/jobs/show' do
+RSpec.describe 'projects/jobs/show' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:build) { create(:ci_build, pipeline: pipeline) }
diff --git a/spec/views/projects/merge_requests/_commits.html.haml_spec.rb b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
index 755a40a7e4c..fd77c4eb372 100644
--- a/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/merge_requests/_commits.html.haml', :sidekiq_might_not_need_inline do
+RSpec.describe 'projects/merge_requests/_commits.html.haml', :sidekiq_might_not_need_inline do
include Devise::Test::ControllerHelpers
include ProjectForksHelper
diff --git a/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb b/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
index f7db4a4d614..038a94fe7c3 100644
--- a/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/merge_requests/creations/_new_submit.html.haml' do
+RSpec.describe 'projects/merge_requests/creations/_new_submit.html.haml' do
let(:merge_request) { create(:merge_request) }
let!(:pipeline) { create(:ci_empty_pipeline) }
diff --git a/spec/views/projects/merge_requests/diffs/_diffs.html.haml_spec.rb b/spec/views/projects/merge_requests/diffs/_diffs.html.haml_spec.rb
index a82ef3c04b5..7cdc817d784 100644
--- a/spec/views/projects/merge_requests/diffs/_diffs.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/diffs/_diffs.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/merge_requests/diffs/_diffs.html.haml' do
+RSpec.describe 'projects/merge_requests/diffs/_diffs.html.haml' do
include Devise::Test::ControllerHelpers
let(:user) { create(:user) }
diff --git a/spec/views/projects/merge_requests/edit.html.haml_spec.rb b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
index 74d9067076c..55a74dc8229 100644
--- a/spec/views/projects/merge_requests/edit.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/merge_requests/edit.html.haml' do
+RSpec.describe 'projects/merge_requests/edit.html.haml' do
include Devise::Test::ControllerHelpers
include ProjectForksHelper
diff --git a/spec/views/projects/merge_requests/show.html.haml_spec.rb b/spec/views/projects/merge_requests/show.html.haml_spec.rb
index e0acf5d1507..32819fc2cb0 100644
--- a/spec/views/projects/merge_requests/show.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/merge_requests/show.html.haml' do
+RSpec.describe 'projects/merge_requests/show.html.haml' do
before do
allow(view).to receive(:experiment_enabled?).and_return(false)
end
diff --git a/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb b/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb
index a07523a4423..3776af9e757 100644
--- a/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb
+++ b/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/notes/_more_actions_dropdown' do
+RSpec.describe 'projects/notes/_more_actions_dropdown' do
let(:author_user) { create(:user) }
let(:not_author_user) { create(:user) }
diff --git a/spec/views/projects/pages/show.html.haml_spec.rb b/spec/views/projects/pages/show.html.haml_spec.rb
index 63b66616f31..fac46d08f0c 100644
--- a/spec/views/projects/pages/show.html.haml_spec.rb
+++ b/spec/views/projects/pages/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/pages/show' do
+RSpec.describe 'projects/pages/show' do
include LetsEncryptHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/views/projects/pages_domains/show.html.haml_spec.rb b/spec/views/projects/pages_domains/show.html.haml_spec.rb
index 2de82a63560..d2abe3dfa56 100644
--- a/spec/views/projects/pages_domains/show.html.haml_spec.rb
+++ b/spec/views/projects/pages_domains/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/pages_domains/show' do
+RSpec.describe 'projects/pages_domains/show' do
let(:project) { create(:project, :repository) }
before do
diff --git a/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb b/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb
index daf799ec13f..e650e183bc8 100644
--- a/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb
+++ b/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/pipeline_schedules/_pipeline_schedule' do
+RSpec.describe 'projects/pipeline_schedules/_pipeline_schedule' do
let(:owner) { create(:user) }
let(:maintainer) { create(:user) }
let(:project) { create(:project) }
diff --git a/spec/views/projects/pipelines/_stage.html.haml_spec.rb b/spec/views/projects/pipelines/_stage.html.haml_spec.rb
index 591602859c3..c8f6784a0f6 100644
--- a/spec/views/projects/pipelines/_stage.html.haml_spec.rb
+++ b/spec/views/projects/pipelines/_stage.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/pipelines/_stage' do
+RSpec.describe 'projects/pipelines/_stage' do
let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:stage) { build(:ci_stage, pipeline: pipeline) }
@@ -54,7 +54,7 @@ describe 'projects/pipelines/_stage' do
context 'when there are multiple builds' do
before do
- HasStatus::AVAILABLE_STATUSES.each do |status|
+ Ci::HasStatus::AVAILABLE_STATUSES.each do |status|
create_build(status)
end
end
@@ -62,7 +62,7 @@ describe 'projects/pipelines/_stage' do
it 'shows them in order' do
render
- expect(rendered).to have_text(HasStatus::ORDERED_STATUSES.join(" "))
+ expect(rendered).to have_text(Ci::HasStatus::ORDERED_STATUSES.join(" "))
end
def create_build(status)
diff --git a/spec/views/projects/services/_form.haml_spec.rb b/spec/views/projects/services/_form.haml_spec.rb
index 720e0aaf450..f5c4e79a082 100644
--- a/spec/views/projects/services/_form.haml_spec.rb
+++ b/spec/views/projects/services/_form.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/services/_form' do
+RSpec.describe 'projects/services/_form' do
let(:project) { create(:redmine_project) }
let(:user) { create(:admin) }
diff --git a/spec/views/projects/services/edit.html.haml_spec.rb b/spec/views/projects/services/edit.html.haml_spec.rb
index 12e1cda2c00..785af6a5344 100644
--- a/spec/views/projects/services/edit.html.haml_spec.rb
+++ b/spec/views/projects/services/edit.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/services/edit' do
+RSpec.describe 'projects/services/edit' do
let(:service) { create(:drone_ci_service, project: project) }
let(:project) { create(:project) }
diff --git a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb
index d25860ab301..b3f59a59ac8 100644
--- a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb
+++ b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/settings/ci_cd/_autodevops_form' do
+RSpec.describe 'projects/settings/ci_cd/_autodevops_form' do
let(:project) { create(:project, :repository) }
before do
diff --git a/spec/views/projects/settings/operations/show.html.haml_spec.rb b/spec/views/projects/settings/operations/show.html.haml_spec.rb
index 7d6faae0f5a..1f0b0ea76bf 100644
--- a/spec/views/projects/settings/operations/show.html.haml_spec.rb
+++ b/spec/views/projects/settings/operations/show.html.haml_spec.rb
@@ -2,9 +2,16 @@
require 'spec_helper'
-describe 'projects/settings/operations/show' do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
+RSpec.describe 'projects/settings/operations/show' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+
+ let(:operations_show_locals) do
+ {
+ prometheus_service: project.find_or_initialize_service('prometheus'),
+ alerts_service: project.find_or_initialize_service('alerts')
+ }
+ end
before do
assign :project, project
@@ -20,13 +27,13 @@ describe 'projects/settings/operations/show' do
allow(view).to receive(:incident_management_available?) { false }
end
- let!(:error_tracking_setting) do
+ let_it_be(:error_tracking_setting) do
create(:project_error_tracking_setting, project: project)
end
context 'Settings page ' do
it 'renders the Operations Settings page' do
- render template: "projects/settings/operations/show", locals: { prometheus_service: project.find_or_initialize_service('prometheus') }
+ render template: 'projects/settings/operations/show', locals: operations_show_locals
expect(rendered).to have_content _('Error Tracking')
expect(rendered).to have_content _('To link Sentry to GitLab, enter your Sentry URL and Auth Token')
diff --git a/spec/views/projects/show.html.haml_spec.rb b/spec/views/projects/show.html.haml_spec.rb
deleted file mode 100644
index c28260f422c..00000000000
--- a/spec/views/projects/show.html.haml_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe 'projects/show.html.haml' do
- include Devise::Test::ControllerHelpers
-
- let(:user) { create(:admin) }
- let(:project) { create(:project, :repository) }
-
- before do
- presented_project = project.present(current_user: user)
-
- allow(presented_project).to receive(:default_view).and_return('customize_workflow')
- allow(controller).to receive(:current_user).and_return(user)
-
- assign(:project, presented_project)
- end
-
- context 'commit signatures' do
- context 'with vue tree view enabled' do
- it 'are not rendered via js-signature-container' do
- render
-
- expect(rendered).not_to have_css('.js-signature-container')
- end
- end
-
- context 'with vue tree view disabled' do
- before do
- stub_feature_flags(vue_file_list: false)
- end
-
- it 'rendered via js-signature-container' do
- render
-
- expect(rendered).to have_css('.js-signature-container')
- end
- end
- end
-end
diff --git a/spec/views/projects/tags/index.html.haml_spec.rb b/spec/views/projects/tags/index.html.haml_spec.rb
index 08fbada9c9c..4d501b82238 100644
--- a/spec/views/projects/tags/index.html.haml_spec.rb
+++ b/spec/views/projects/tags/index.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/tags/index.html.haml' do
+RSpec.describe 'projects/tags/index.html.haml' do
let(:project) { create(:project, :repository) }
let(:tags) { TagsFinder.new(project.repository, {}).execute }
let(:git_tag) { project.repository.tags.last }
diff --git a/spec/views/projects/tree/_tree_header.html.haml_spec.rb b/spec/views/projects/tree/_tree_header.html.haml_spec.rb
deleted file mode 100644
index 69ad331f880..00000000000
--- a/spec/views/projects/tree/_tree_header.html.haml_spec.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe 'projects/tree/_tree_header' do
- let(:project) { create(:project, :repository) }
- let(:current_user) { create(:user) }
- let(:repository) { project.repository }
-
- before do
- stub_feature_flags(vue_file_list: false)
-
- assign(:project, project)
- assign(:repository, repository)
- assign(:id, File.join('master', ''))
- assign(:ref, 'master')
-
- allow(view).to receive(:current_user).and_return(current_user)
- allow(view).to receive(:can_collaborate_with_project?) { true }
- end
-
- it 'renders the WebIDE button when user can collaborate but not create fork or MR' do
- allow(view).to receive(:can?) { false }
-
- render
-
- expect(rendered).to have_link('Web IDE')
- end
-
- it 'renders the WebIDE button when user can create fork and can open MR in project' do
- allow(view).to receive(:can?) { true }
-
- render
-
- expect(rendered).to have_link('Web IDE')
- end
-
- it 'opens a popup confirming a fork if the user can create fork/MR but cannot collaborate with the project' do
- allow(view).to receive(:can?) { true }
- allow(view).to receive(:can_collaborate_with_project?) { false }
-
- render
-
- expect(rendered).to have_link('Web IDE', href: '#modal-confirm-fork')
- end
-
- it 'does not render the WebIDE button when user cannot collaborate or create mr' do
- allow(view).to receive(:can?) { false }
- allow(view).to receive(:can_collaborate_with_project?) { false }
-
- render
-
- expect(rendered).not_to have_link('Web IDE')
- end
-end
diff --git a/spec/views/projects/tree/_tree_row.html.haml_spec.rb b/spec/views/projects/tree/_tree_row.html.haml_spec.rb
index 864272fc146..43a37934afd 100644
--- a/spec/views/projects/tree/_tree_row.html.haml_spec.rb
+++ b/spec/views/projects/tree/_tree_row.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/tree/_tree_row' do
+RSpec.describe 'projects/tree/_tree_row' do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
diff --git a/spec/views/projects/tree/show.html.haml_spec.rb b/spec/views/projects/tree/show.html.haml_spec.rb
index 8c6b229247d..bdf9b08d8f5 100644
--- a/spec/views/projects/tree/show.html.haml_spec.rb
+++ b/spec/views/projects/tree/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'projects/tree/show' do
+RSpec.describe 'projects/tree/show' do
include Devise::Test::ControllerHelpers
let(:project) { create(:project, :repository) }
@@ -39,26 +39,4 @@ describe 'projects/tree/show' do
expect(rendered).to have_css('.js-project-refs-dropdown .dropdown-toggle-text', text: ref)
end
end
-
- context 'commit signatures' do
- context 'with vue tree view disabled' do
- before do
- stub_feature_flags(vue_file_list: false)
- end
-
- it 'rendered via js-signature-container' do
- render
-
- expect(rendered).to have_css('.js-signature-container')
- end
- end
-
- context 'with vue tree view enabled' do
- it 'are not rendered via js-signature-container' do
- render
-
- expect(rendered).not_to have_css('.js-signature-container')
- end
- end
- end
end
diff --git a/spec/views/search/_filter.html.haml_spec.rb b/spec/views/search/_filter.html.haml_spec.rb
index d2cd636f8c6..eb32528e3c7 100644
--- a/spec/views/search/_filter.html.haml_spec.rb
+++ b/spec/views/search/_filter.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'search/_filter' do
+RSpec.describe 'search/_filter' do
context 'when the search page is opened' do
it 'displays the correct elements' do
render
diff --git a/spec/views/search/_form.html.haml_spec.rb b/spec/views/search/_form.html.haml_spec.rb
index 69f40895d86..073a39e4ed6 100644
--- a/spec/views/search/_form.html.haml_spec.rb
+++ b/spec/views/search/_form.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'search/_form' do
+RSpec.describe 'search/_form' do
context 'when the search page is opened' do
it 'displays the correct elements' do
render
diff --git a/spec/views/search/_results.html.haml_spec.rb b/spec/views/search/_results.html.haml_spec.rb
index 3243758c650..cd7a3559538 100644
--- a/spec/views/search/_results.html.haml_spec.rb
+++ b/spec/views/search/_results.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'search/_results' do
+RSpec.describe 'search/_results' do
before do
controller.params[:action] = 'show'
diff --git a/spec/views/search/show.html.haml_spec.rb b/spec/views/search/show.html.haml_spec.rb
index 483b913f2cc..9ddfe08c8f3 100644
--- a/spec/views/search/show.html.haml_spec.rb
+++ b/spec/views/search/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'search/show' do
+RSpec.describe 'search/show' do
let(:search_term) { nil }
before do
diff --git a/spec/views/shared/_label_row.html.haml_spec.rb b/spec/views/shared/_label_row.html.haml_spec.rb
index 0764f8480c8..1e2ed41bafc 100644
--- a/spec/views/shared/_label_row.html.haml_spec.rb
+++ b/spec/views/shared/_label_row.html.haml_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'shared/_label_row.html.haml' do
+RSpec.describe 'shared/_label_row.html.haml' do
label_types = {
'project label': :label,
'group label': :group_label
diff --git a/spec/views/shared/milestones/_issuable.html.haml_spec.rb b/spec/views/shared/milestones/_issuable.html.haml_spec.rb
index 6e81fec79d4..46f79f60bbe 100644
--- a/spec/views/shared/milestones/_issuable.html.haml_spec.rb
+++ b/spec/views/shared/milestones/_issuable.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'shared/milestones/_issuable.html.haml' do
+RSpec.describe 'shared/milestones/_issuable.html.haml' do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:milestone) { create(:milestone, project: project) }
diff --git a/spec/views/shared/milestones/_issuables.html.haml_spec.rb b/spec/views/shared/milestones/_issuables.html.haml_spec.rb
index f77c14a687b..70ab6914580 100644
--- a/spec/views/shared/milestones/_issuables.html.haml_spec.rb
+++ b/spec/views/shared/milestones/_issuables.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'shared/milestones/_issuables.html.haml' do
+RSpec.describe 'shared/milestones/_issuables.html.haml' do
let(:issuables_size) { 100 }
before do
diff --git a/spec/views/shared/milestones/_top.html.haml_spec.rb b/spec/views/shared/milestones/_top.html.haml_spec.rb
index 2d72e278706..1aa971709f1 100644
--- a/spec/views/shared/milestones/_top.html.haml_spec.rb
+++ b/spec/views/shared/milestones/_top.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'shared/milestones/_top.html.haml' do
+RSpec.describe 'shared/milestones/_top.html.haml' do
let_it_be(:group) { create(:group) }
let(:project) { create(:project, group: group) }
let(:milestone) { create(:milestone, project: project) }
diff --git a/spec/views/shared/notes/_form.html.haml_spec.rb b/spec/views/shared/notes/_form.html.haml_spec.rb
index d354c2f0100..b7bad4c5d78 100644
--- a/spec/views/shared/notes/_form.html.haml_spec.rb
+++ b/spec/views/shared/notes/_form.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'shared/notes/_form' do
+RSpec.describe 'shared/notes/_form' do
include Devise::Test::ControllerHelpers
let(:user) { create(:user) }
diff --git a/spec/views/shared/projects/_list.html.haml_spec.rb b/spec/views/shared/projects/_list.html.haml_spec.rb
index d6043921fc8..037f988257b 100644
--- a/spec/views/shared/projects/_list.html.haml_spec.rb
+++ b/spec/views/shared/projects/_list.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'shared/projects/_list' do
+RSpec.describe 'shared/projects/_list' do
let(:group) { create(:group) }
before do
diff --git a/spec/views/shared/projects/_project.html.haml_spec.rb b/spec/views/shared/projects/_project.html.haml_spec.rb
index 8c3b8768469..62f23338c48 100644
--- a/spec/views/shared/projects/_project.html.haml_spec.rb
+++ b/spec/views/shared/projects/_project.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'shared/projects/_project.html.haml' do
+RSpec.describe 'shared/projects/_project.html.haml' do
let_it_be(:project) { create(:project) }
before do
diff --git a/spec/views/shared/runners/show.html.haml_spec.rb b/spec/views/shared/runners/show.html.haml_spec.rb
index 5e92928b143..5e2812eb48a 100644
--- a/spec/views/shared/runners/show.html.haml_spec.rb
+++ b/spec/views/shared/runners/show.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'shared/runners/show.html.haml' do
+RSpec.describe 'shared/runners/show.html.haml' do
include PageLayoutHelper
let(:runner) do
diff --git a/spec/workers/admin_email_worker_spec.rb b/spec/workers/admin_email_worker_spec.rb
index f72b932423f..1a5cb90bc17 100644
--- a/spec/workers/admin_email_worker_spec.rb
+++ b/spec/workers/admin_email_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AdminEmailWorker do
+RSpec.describe AdminEmailWorker do
subject(:worker) { described_class.new }
describe '.perform' do
diff --git a/spec/workers/archive_trace_worker_spec.rb b/spec/workers/archive_trace_worker_spec.rb
index 44f7be15201..a9f256b1b3b 100644
--- a/spec/workers/archive_trace_worker_spec.rb
+++ b/spec/workers/archive_trace_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ArchiveTraceWorker do
+RSpec.describe ArchiveTraceWorker do
describe '#perform' do
subject { described_class.new.perform(job&.id) }
diff --git a/spec/workers/authorized_keys_worker_spec.rb b/spec/workers/authorized_keys_worker_spec.rb
index 4f1dde0bfc0..50236f9ea7b 100644
--- a/spec/workers/authorized_keys_worker_spec.rb
+++ b/spec/workers/authorized_keys_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AuthorizedKeysWorker do
+RSpec.describe AuthorizedKeysWorker do
let(:worker) { described_class.new }
describe '#perform' do
diff --git a/spec/workers/authorized_project_update/periodic_recalculate_worker_spec.rb b/spec/workers/authorized_project_update/periodic_recalculate_worker_spec.rb
index fcd073953b6..2d633828ae3 100644
--- a/spec/workers/authorized_project_update/periodic_recalculate_worker_spec.rb
+++ b/spec/workers/authorized_project_update/periodic_recalculate_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AuthorizedProjectUpdate::PeriodicRecalculateWorker do
+RSpec.describe AuthorizedProjectUpdate::PeriodicRecalculateWorker do
describe '#perform' do
it 'calls AuthorizedProjectUpdate::PeriodicRecalculateService' do
expect_next_instance_of(AuthorizedProjectUpdate::PeriodicRecalculateService) do |service|
diff --git a/spec/workers/authorized_project_update/project_create_worker_spec.rb b/spec/workers/authorized_project_update/project_create_worker_spec.rb
index 5ebfb60bc79..5226ab30de7 100644
--- a/spec/workers/authorized_project_update/project_create_worker_spec.rb
+++ b/spec/workers/authorized_project_update/project_create_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AuthorizedProjectUpdate::ProjectCreateWorker do
+RSpec.describe AuthorizedProjectUpdate::ProjectCreateWorker do
let_it_be(:group) { create(:group, :private) }
let_it_be(:group_project) { create(:project, group: group) }
let_it_be(:group_user) { create(:user) }
@@ -27,7 +27,7 @@ describe AuthorizedProjectUpdate::ProjectCreateWorker do
context 'idempotence' do
before do
- create(:group_member, access_level: Gitlab::Access::MAINTAINER, group: group, user: group_user)
+ create(:group_member, access_level: access_level, group: group, user: group_user)
ProjectAuthorization.delete_all
end
diff --git a/spec/workers/authorized_project_update/project_group_link_create_worker_spec.rb b/spec/workers/authorized_project_update/project_group_link_create_worker_spec.rb
new file mode 100644
index 00000000000..7c4ad4ce641
--- /dev/null
+++ b/spec/workers/authorized_project_update/project_group_link_create_worker_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe AuthorizedProjectUpdate::ProjectGroupLinkCreateWorker do
+ let_it_be(:group) { create(:group, :private) }
+ let_it_be(:group_project) { create(:project, group: group) }
+ let_it_be(:shared_with_group) { create(:group, :private) }
+ let_it_be(:user) { create(:user) }
+
+ let(:access_level) { Gitlab::Access::MAINTAINER }
+
+ subject(:worker) { described_class.new }
+
+ it 'calls AuthorizedProjectUpdate::ProjectCreateService' do
+ expect_next_instance_of(AuthorizedProjectUpdate::ProjectGroupLinkCreateService) do |service|
+ expect(service).to(receive(:execute))
+ end
+
+ worker.perform(group_project.id, shared_with_group.id)
+ end
+
+ it 'returns ServiceResponse.success' do
+ result = worker.perform(group_project.id, shared_with_group.id)
+
+ expect(result.success?).to be_truthy
+ end
+
+ context 'idempotence' do
+ before do
+ create(:group_member, group: shared_with_group, user: user, access_level: access_level)
+ create(:project_group_link, project: group_project, group: shared_with_group)
+ ProjectAuthorization.delete_all
+ end
+
+ include_examples 'an idempotent worker' do
+ let(:job_args) { [group_project.id, shared_with_group.id] }
+
+ it 'creates project authorization' do
+ subject
+
+ project_authorization = ProjectAuthorization.where(
+ project_id: group_project.id,
+ user_id: user.id,
+ access_level: access_level)
+
+ expect(project_authorization).to exist
+ expect(ProjectAuthorization.count).to eq(1)
+ end
+ end
+ end
+end
diff --git a/spec/workers/authorized_project_update/user_refresh_over_user_range_worker_spec.rb b/spec/workers/authorized_project_update/user_refresh_over_user_range_worker_spec.rb
index 5d1c405dfd0..c49e4c453bf 100644
--- a/spec/workers/authorized_project_update/user_refresh_over_user_range_worker_spec.rb
+++ b/spec/workers/authorized_project_update/user_refresh_over_user_range_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AuthorizedProjectUpdate::UserRefreshOverUserRangeWorker do
+RSpec.describe AuthorizedProjectUpdate::UserRefreshOverUserRangeWorker do
let(:start_user_id) { 42 }
let(:end_user_id) { 4242 }
diff --git a/spec/workers/authorized_project_update/user_refresh_with_low_urgency_worker_spec.rb b/spec/workers/authorized_project_update/user_refresh_with_low_urgency_worker_spec.rb
index fa029dae0fa..bd16eeb4712 100644
--- a/spec/workers/authorized_project_update/user_refresh_with_low_urgency_worker_spec.rb
+++ b/spec/workers/authorized_project_update/user_refresh_with_low_urgency_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AuthorizedProjectUpdate::UserRefreshWithLowUrgencyWorker do
+RSpec.describe AuthorizedProjectUpdate::UserRefreshWithLowUrgencyWorker do
it 'is labeled as low urgency' do
expect(described_class.get_urgency).to eq(:low)
end
diff --git a/spec/workers/authorized_projects_worker_spec.rb b/spec/workers/authorized_projects_worker_spec.rb
index 93f22471c56..fbfde77be97 100644
--- a/spec/workers/authorized_projects_worker_spec.rb
+++ b/spec/workers/authorized_projects_worker_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe AuthorizedProjectsWorker do
+RSpec.describe AuthorizedProjectsWorker do
it_behaves_like "refreshes user's project authorizations"
end
diff --git a/spec/workers/auto_devops/disable_worker_spec.rb b/spec/workers/auto_devops/disable_worker_spec.rb
index 53113f286ba..239f4b09f5c 100644
--- a/spec/workers/auto_devops/disable_worker_spec.rb
+++ b/spec/workers/auto_devops/disable_worker_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe AutoDevops::DisableWorker, '#perform' do
+RSpec.describe AutoDevops::DisableWorker, '#perform' do
let(:user) { create(:user, developer_projects: [project]) }
let(:project) { create(:project, :repository, :auto_devops) }
let(:auto_devops) { project.auto_devops }
diff --git a/spec/workers/auto_merge_process_worker_spec.rb b/spec/workers/auto_merge_process_worker_spec.rb
index 616727ce5ca..00d27d9c2b5 100644
--- a/spec/workers/auto_merge_process_worker_spec.rb
+++ b/spec/workers/auto_merge_process_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AutoMergeProcessWorker do
+RSpec.describe AutoMergeProcessWorker do
describe '#perform' do
subject { described_class.new.perform(merge_request&.id) }
diff --git a/spec/workers/background_migration_worker_spec.rb b/spec/workers/background_migration_worker_spec.rb
index 2b2ffc9f5b9..15e93d62c7d 100644
--- a/spec/workers/background_migration_worker_spec.rb
+++ b/spec/workers/background_migration_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BackgroundMigrationWorker, :clean_gitlab_redis_shared_state do
+RSpec.describe BackgroundMigrationWorker, :clean_gitlab_redis_shared_state do
let(:worker) { described_class.new }
describe '.minimum_interval' do
diff --git a/spec/workers/build_coverage_worker_spec.rb b/spec/workers/build_coverage_worker_spec.rb
index 25686ae68ca..4beb6e5972f 100644
--- a/spec/workers/build_coverage_worker_spec.rb
+++ b/spec/workers/build_coverage_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BuildCoverageWorker do
+RSpec.describe BuildCoverageWorker do
describe '#perform' do
context 'when build exists' do
let!(:build) { create(:ci_build) }
diff --git a/spec/workers/build_finished_worker_spec.rb b/spec/workers/build_finished_worker_spec.rb
index 849563d9608..e7f7ae84621 100644
--- a/spec/workers/build_finished_worker_spec.rb
+++ b/spec/workers/build_finished_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BuildFinishedWorker do
+RSpec.describe BuildFinishedWorker do
subject { described_class.new.perform(build.id) }
describe '#perform' do
diff --git a/spec/workers/build_hooks_worker_spec.rb b/spec/workers/build_hooks_worker_spec.rb
index 59b252a8be3..aefbd7e590e 100644
--- a/spec/workers/build_hooks_worker_spec.rb
+++ b/spec/workers/build_hooks_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BuildHooksWorker do
+RSpec.describe BuildHooksWorker do
describe '#perform' do
context 'when build exists' do
let!(:build) { create(:ci_build) }
diff --git a/spec/workers/build_success_worker_spec.rb b/spec/workers/build_success_worker_spec.rb
index f4ff8cb15f8..0583d79ed46 100644
--- a/spec/workers/build_success_worker_spec.rb
+++ b/spec/workers/build_success_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BuildSuccessWorker do
+RSpec.describe BuildSuccessWorker do
describe '#perform' do
subject { described_class.new.perform(build.id) }
diff --git a/spec/workers/build_trace_sections_worker_spec.rb b/spec/workers/build_trace_sections_worker_spec.rb
index 97fc0a2da0c..1c84b0083fb 100644
--- a/spec/workers/build_trace_sections_worker_spec.rb
+++ b/spec/workers/build_trace_sections_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe BuildTraceSectionsWorker do
+RSpec.describe BuildTraceSectionsWorker do
describe '#perform' do
context 'when build exists' do
let!(:build) { create(:ci_build) }
diff --git a/spec/workers/chat_notification_worker_spec.rb b/spec/workers/chat_notification_worker_spec.rb
index e4dccf2bf6b..a20a136d197 100644
--- a/spec/workers/chat_notification_worker_spec.rb
+++ b/spec/workers/chat_notification_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ChatNotificationWorker do
+RSpec.describe ChatNotificationWorker do
let(:worker) { described_class.new }
let(:chat_build) do
create(:ci_build, pipeline: create(:ci_pipeline, source: :chat))
diff --git a/spec/workers/ci/archive_traces_cron_worker_spec.rb b/spec/workers/ci/archive_traces_cron_worker_spec.rb
index 789e83783bb..14abe819587 100644
--- a/spec/workers/ci/archive_traces_cron_worker_spec.rb
+++ b/spec/workers/ci/archive_traces_cron_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::ArchiveTracesCronWorker do
+RSpec.describe Ci::ArchiveTracesCronWorker do
subject { described_class.new.perform }
let(:finished_at) { 1.day.ago }
diff --git a/spec/workers/ci/build_prepare_worker_spec.rb b/spec/workers/ci/build_prepare_worker_spec.rb
index 9f76696ee66..b2c74a920ea 100644
--- a/spec/workers/ci/build_prepare_worker_spec.rb
+++ b/spec/workers/ci/build_prepare_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildPrepareWorker do
+RSpec.describe Ci::BuildPrepareWorker do
subject { described_class.new.perform(build_id) }
context 'build exists' do
diff --git a/spec/workers/ci/build_report_result_worker_spec.rb b/spec/workers/ci/build_report_result_worker_spec.rb
index 290a98366b4..aedfa70c8b8 100644
--- a/spec/workers/ci/build_report_result_worker_spec.rb
+++ b/spec/workers/ci/build_report_result_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildReportResultWorker do
+RSpec.describe Ci::BuildReportResultWorker do
subject { described_class.new.perform(build_id) }
context 'when build exists' do
diff --git a/spec/workers/ci/build_schedule_worker_spec.rb b/spec/workers/ci/build_schedule_worker_spec.rb
index 647f9763fed..f8b4efc562b 100644
--- a/spec/workers/ci/build_schedule_worker_spec.rb
+++ b/spec/workers/ci/build_schedule_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::BuildScheduleWorker do
+RSpec.describe Ci::BuildScheduleWorker do
subject { described_class.new.perform(build.id) }
context 'when build is found' do
diff --git a/spec/workers/ci/create_cross_project_pipeline_worker_spec.rb b/spec/workers/ci/create_cross_project_pipeline_worker_spec.rb
index 492f5e812ee..95dcf5624cc 100644
--- a/spec/workers/ci/create_cross_project_pipeline_worker_spec.rb
+++ b/spec/workers/ci/create_cross_project_pipeline_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::CreateCrossProjectPipelineWorker do
+RSpec.describe Ci::CreateCrossProjectPipelineWorker do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
diff --git a/spec/workers/ci/daily_build_group_report_results_worker_spec.rb b/spec/workers/ci/daily_build_group_report_results_worker_spec.rb
index d9706982a62..e13c6311e46 100644
--- a/spec/workers/ci/daily_build_group_report_results_worker_spec.rb
+++ b/spec/workers/ci/daily_build_group_report_results_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::DailyBuildGroupReportResultsWorker do
+RSpec.describe Ci::DailyBuildGroupReportResultsWorker do
describe '#perform' do
let!(:pipeline) { create(:ci_pipeline) }
diff --git a/spec/workers/ci/pipeline_bridge_status_worker_spec.rb b/spec/workers/ci/pipeline_bridge_status_worker_spec.rb
index a6129b2cf93..6ec5eb0e639 100644
--- a/spec/workers/ci/pipeline_bridge_status_worker_spec.rb
+++ b/spec/workers/ci/pipeline_bridge_status_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::PipelineBridgeStatusWorker do
+RSpec.describe Ci::PipelineBridgeStatusWorker do
describe '#perform' do
subject { described_class.new.perform(pipeline_id) }
diff --git a/spec/workers/ci/pipeline_success_unlock_artifacts_worker_spec.rb b/spec/workers/ci/pipeline_success_unlock_artifacts_worker_spec.rb
new file mode 100644
index 00000000000..cb2cf58d50b
--- /dev/null
+++ b/spec/workers/ci/pipeline_success_unlock_artifacts_worker_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::PipelineSuccessUnlockArtifactsWorker do
+ describe '#perform' do
+ subject(:perform) { described_class.new.perform(pipeline_id) }
+
+ include_examples 'an idempotent worker' do
+ subject(:idempotent_perform) { perform_multiple(pipeline.id, exec_times: 2) }
+
+ let!(:older_pipeline) do
+ create(:ci_pipeline, :success, :with_job, locked: :artifacts_locked).tap do |pipeline|
+ create(:ci_job_artifact, job: pipeline.builds.first)
+ end
+ end
+
+ let!(:pipeline) do
+ create(:ci_pipeline, :success, :with_job, ref: older_pipeline.ref, tag: older_pipeline.tag, project: older_pipeline.project, locked: :unlocked).tap do |pipeline|
+ create(:ci_job_artifact, job: pipeline.builds.first)
+ end
+ end
+
+ it 'unlocks the artifacts from older pipelines' do
+ expect { idempotent_perform }.to change { older_pipeline.reload.locked }.from('artifacts_locked').to('unlocked')
+ end
+ end
+
+ context 'when pipeline exists' do
+ let(:pipeline) { create(:ci_pipeline, :success, :with_job) }
+ let(:pipeline_id) { pipeline.id }
+
+ context 'when pipeline has artifacts' do
+ before do
+ create(:ci_job_artifact, job: pipeline.builds.first)
+ end
+
+ it 'calls the service' do
+ service = spy(Ci::UnlockArtifactsService)
+ expect(Ci::UnlockArtifactsService).to receive(:new).and_return(service)
+
+ perform
+
+ expect(service).to have_received(:execute)
+ end
+ end
+
+ context 'when pipeline does not have artifacts' do
+ it 'does not call service' do
+ expect(Ci::UnlockArtifactsService).not_to receive(:new)
+
+ perform
+ end
+ end
+ end
+
+ context 'when pipeline does not exist' do
+ let(:pipeline_id) { non_existing_record_id }
+
+ it 'does not call service' do
+ expect(Ci::UnlockArtifactsService).not_to receive(:new)
+
+ perform
+ end
+ end
+ end
+end
diff --git a/spec/workers/ci/ref_delete_unlock_artifacts_worker_spec.rb b/spec/workers/ci/ref_delete_unlock_artifacts_worker_spec.rb
new file mode 100644
index 00000000000..d9f2dd326dd
--- /dev/null
+++ b/spec/workers/ci/ref_delete_unlock_artifacts_worker_spec.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::RefDeleteUnlockArtifactsWorker do
+ describe '#perform' do
+ subject(:perform) { described_class.new.perform(project_id, user_id, ref) }
+
+ let(:ref) { 'refs/heads/master' }
+
+ let(:project) { create(:project) }
+
+ include_examples 'an idempotent worker' do
+ subject(:idempotent_perform) { perform_multiple([project_id, user_id, ref], exec_times: 2) }
+
+ let(:project_id) { project.id }
+ let(:user_id) { project.creator.id }
+
+ let(:pipeline) { create(:ci_pipeline, ref: 'master', project: project, locked: :artifacts_locked) }
+
+ it 'unlocks the artifacts from older pipelines' do
+ expect { idempotent_perform }.to change { pipeline.reload.locked }.from('artifacts_locked').to('unlocked')
+ end
+ end
+
+ context 'when project exists' do
+ let(:project_id) { project.id }
+
+ context 'when user exists' do
+ let(:user_id) { project.creator.id }
+
+ context 'when ci ref exists' do
+ before do
+ create(:ci_ref, ref_path: ref)
+ end
+
+ it 'calls the service' do
+ service = spy(Ci::UnlockArtifactsService)
+ expect(Ci::UnlockArtifactsService).to receive(:new).and_return(service)
+
+ perform
+
+ expect(service).to have_received(:execute)
+ end
+ end
+
+ context 'when ci ref does not exist' do
+ it 'does not call the service' do
+ expect(Ci::UnlockArtifactsService).not_to receive(:new)
+
+ perform
+ end
+ end
+ end
+
+ context 'when user does not exist' do
+ let(:user_id) { non_existing_record_id }
+
+ it 'does not call service' do
+ expect(Ci::UnlockArtifactsService).not_to receive(:new)
+
+ perform
+ end
+ end
+ end
+
+ context 'when project does not exist' do
+ let(:project_id) { non_existing_record_id }
+ let(:user_id) { project.creator.id }
+
+ it 'does not call service' do
+ expect(Ci::UnlockArtifactsService).not_to receive(:new)
+
+ perform
+ end
+ end
+ end
+end
diff --git a/spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb b/spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb
index 634d932121e..f9914a7cecb 100644
--- a/spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb
+++ b/spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Ci::ResourceGroups::AssignResourceFromResourceGroupWorker do
+RSpec.describe Ci::ResourceGroups::AssignResourceFromResourceGroupWorker do
let(:worker) { described_class.new }
describe '#perform' do
diff --git a/spec/workers/cleanup_container_repository_worker_spec.rb b/spec/workers/cleanup_container_repository_worker_spec.rb
index 1228c2c2d9c..0545f7a35e4 100644
--- a/spec/workers/cleanup_container_repository_worker_spec.rb
+++ b/spec/workers/cleanup_container_repository_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CleanupContainerRepositoryWorker, :clean_gitlab_redis_shared_state do
+RSpec.describe CleanupContainerRepositoryWorker, :clean_gitlab_redis_shared_state do
let(:repository) { create(:container_repository) }
let(:project) { repository.project }
let(:user) { project.owner }
diff --git a/spec/workers/cluster_configure_istio_worker_spec.rb b/spec/workers/cluster_configure_istio_worker_spec.rb
index 0f02d428ced..5d949fde973 100644
--- a/spec/workers/cluster_configure_istio_worker_spec.rb
+++ b/spec/workers/cluster_configure_istio_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ClusterConfigureIstioWorker do
+RSpec.describe ClusterConfigureIstioWorker do
describe '#perform' do
shared_examples 'configure istio service' do
it 'configures istio' do
diff --git a/spec/workers/cluster_provision_worker_spec.rb b/spec/workers/cluster_provision_worker_spec.rb
index 608639331fd..2d6ca4ab7e3 100644
--- a/spec/workers/cluster_provision_worker_spec.rb
+++ b/spec/workers/cluster_provision_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ClusterProvisionWorker do
+RSpec.describe ClusterProvisionWorker do
describe '#perform' do
context 'when provider type is gcp' do
let(:cluster) { create(:cluster, provider_type: :gcp, provider_gcp: provider) }
diff --git a/spec/workers/cluster_update_app_worker_spec.rb b/spec/workers/cluster_update_app_worker_spec.rb
index 5391c194679..c24f40024fd 100644
--- a/spec/workers/cluster_update_app_worker_spec.rb
+++ b/spec/workers/cluster_update_app_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ClusterUpdateAppWorker do
+RSpec.describe ClusterUpdateAppWorker do
include ExclusiveLeaseHelpers
let_it_be(:project) { create(:project) }
@@ -12,7 +12,7 @@ describe ClusterUpdateAppWorker do
subject { described_class.new }
around do |example|
- Timecop.freeze(Time.now) { example.run }
+ Timecop.freeze(Time.current) { example.run }
end
before do
@@ -22,11 +22,11 @@ describe ClusterUpdateAppWorker do
describe '#perform' do
context 'when the application last_update_started_at is higher than the time the job was scheduled in' do
it 'does nothing' do
- application = create(:clusters_applications_prometheus, :updated, last_update_started_at: Time.now)
+ application = create(:clusters_applications_prometheus, :updated, last_update_started_at: Time.current)
expect(prometheus_update_service).not_to receive(:execute)
- expect(subject.perform(application.name, application.id, project.id, Time.now - 5.minutes)).to be_nil
+ expect(subject.perform(application.name, application.id, project.id, Time.current - 5.minutes)).to be_nil
end
end
@@ -34,7 +34,7 @@ describe ClusterUpdateAppWorker do
it 'returns nil' do
application = create(:clusters_applications_prometheus, :updating)
- expect(subject.perform(application.name, application.id, project.id, Time.now)).to be_nil
+ expect(subject.perform(application.name, application.id, project.id, Time.current)).to be_nil
end
end
@@ -43,7 +43,7 @@ describe ClusterUpdateAppWorker do
expect(prometheus_update_service).to receive(:execute)
- subject.perform(application.name, application.id, project.id, Time.now)
+ subject.perform(application.name, application.id, project.id, Time.current)
end
context 'with exclusive lease' do
@@ -60,7 +60,7 @@ describe ClusterUpdateAppWorker do
it 'does not allow same app to be updated concurrently by same project' do
expect(Clusters::Applications::PrometheusUpdateService).not_to receive(:new)
- subject.perform(application.name, application.id, project.id, Time.now)
+ subject.perform(application.name, application.id, project.id, Time.current)
end
it 'does not allow same app to be updated concurrently by different project', :aggregate_failures do
@@ -68,7 +68,7 @@ describe ClusterUpdateAppWorker do
expect(Clusters::Applications::PrometheusUpdateService).not_to receive(:new)
- subject.perform(application.name, application.id, project1.id, Time.now)
+ subject.perform(application.name, application.id, project1.id, Time.current)
end
it 'allows different app to be updated concurrently by same project' do
@@ -80,7 +80,7 @@ describe ClusterUpdateAppWorker do
expect(Clusters::Applications::PrometheusUpdateService).to receive(:new)
.with(application2, project)
- subject.perform(application2.name, application2.id, project.id, Time.now)
+ subject.perform(application2.name, application2.id, project.id, Time.current)
end
it 'allows different app to be updated by different project', :aggregate_failures do
@@ -94,7 +94,7 @@ describe ClusterUpdateAppWorker do
expect(Clusters::Applications::PrometheusUpdateService).to receive(:new)
.with(application2, project2)
- subject.perform(application2.name, application2.id, project2.id, Time.now)
+ subject.perform(application2.name, application2.id, project2.id, Time.current)
end
end
end
diff --git a/spec/workers/cluster_wait_for_app_update_worker_spec.rb b/spec/workers/cluster_wait_for_app_update_worker_spec.rb
index f1206bd85cb..b7f7622a0e6 100644
--- a/spec/workers/cluster_wait_for_app_update_worker_spec.rb
+++ b/spec/workers/cluster_wait_for_app_update_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ClusterWaitForAppUpdateWorker do
+RSpec.describe ClusterWaitForAppUpdateWorker do
let(:check_upgrade_progress_service) { spy }
before do
diff --git a/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb b/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb
index a9ffdfb085e..7a42c988a92 100644
--- a/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb
+++ b/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ClusterWaitForIngressIpAddressWorker do
+RSpec.describe ClusterWaitForIngressIpAddressWorker do
describe '#perform' do
let(:service) { instance_double(Clusters::Applications::CheckIngressIpAddressService, execute: true) }
let(:application) { instance_double(Clusters::Applications::Ingress) }
diff --git a/spec/workers/clusters/applications/activate_service_worker_spec.rb b/spec/workers/clusters/applications/activate_service_worker_spec.rb
index 09f4c536edf..c157c57888e 100644
--- a/spec/workers/clusters/applications/activate_service_worker_spec.rb
+++ b/spec/workers/clusters/applications/activate_service_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::ActivateServiceWorker, '#perform' do
+RSpec.describe Clusters::Applications::ActivateServiceWorker, '#perform' do
context 'cluster exists' do
describe 'prometheus service' do
let(:service_name) { 'prometheus' }
diff --git a/spec/workers/clusters/applications/check_prometheus_health_worker_spec.rb b/spec/workers/clusters/applications/check_prometheus_health_worker_spec.rb
index a09b9ec4165..5a37031a55a 100644
--- a/spec/workers/clusters/applications/check_prometheus_health_worker_spec.rb
+++ b/spec/workers/clusters/applications/check_prometheus_health_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::CheckPrometheusHealthWorker, '#perform' do
+RSpec.describe Clusters::Applications::CheckPrometheusHealthWorker, '#perform' do
subject { described_class.new.perform }
it 'triggers health service' do
diff --git a/spec/workers/clusters/applications/deactivate_service_worker_spec.rb b/spec/workers/clusters/applications/deactivate_service_worker_spec.rb
index 809843a1bc8..18cceaaf3b1 100644
--- a/spec/workers/clusters/applications/deactivate_service_worker_spec.rb
+++ b/spec/workers/clusters/applications/deactivate_service_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::DeactivateServiceWorker, '#perform' do
+RSpec.describe Clusters::Applications::DeactivateServiceWorker, '#perform' do
context 'cluster exists' do
describe 'prometheus service' do
let(:service_name) { 'prometheus' }
diff --git a/spec/workers/clusters/applications/wait_for_uninstall_app_worker_spec.rb b/spec/workers/clusters/applications/wait_for_uninstall_app_worker_spec.rb
index aaf5c9defc4..0191a2898b2 100644
--- a/spec/workers/clusters/applications/wait_for_uninstall_app_worker_spec.rb
+++ b/spec/workers/clusters/applications/wait_for_uninstall_app_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Applications::WaitForUninstallAppWorker, '#perform' do
+RSpec.describe Clusters::Applications::WaitForUninstallAppWorker, '#perform' do
let(:app) { create(:clusters_applications_helm) }
let(:app_name) { app.name }
let(:app_id) { app.id }
diff --git a/spec/workers/clusters/cleanup/app_worker_spec.rb b/spec/workers/clusters/cleanup/app_worker_spec.rb
index 29c00db8079..661468f037f 100644
--- a/spec/workers/clusters/cleanup/app_worker_spec.rb
+++ b/spec/workers/clusters/cleanup/app_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Cleanup::AppWorker do
+RSpec.describe Clusters::Cleanup::AppWorker do
describe '#perform' do
subject { worker_instance.perform(cluster.id) }
diff --git a/spec/workers/clusters/cleanup/project_namespace_worker_spec.rb b/spec/workers/clusters/cleanup/project_namespace_worker_spec.rb
index 8b6f22e9a61..b9219586a0b 100644
--- a/spec/workers/clusters/cleanup/project_namespace_worker_spec.rb
+++ b/spec/workers/clusters/cleanup/project_namespace_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Cleanup::ProjectNamespaceWorker do
+RSpec.describe Clusters::Cleanup::ProjectNamespaceWorker do
describe '#perform' do
context 'when cluster.cleanup_status is cleanup_removing_project_namespaces' do
let!(:cluster) { create(:cluster, :with_environments, :cleanup_removing_project_namespaces) }
diff --git a/spec/workers/clusters/cleanup/service_account_worker_spec.rb b/spec/workers/clusters/cleanup/service_account_worker_spec.rb
index 9af53dd63c1..dabc32a0ccd 100644
--- a/spec/workers/clusters/cleanup/service_account_worker_spec.rb
+++ b/spec/workers/clusters/cleanup/service_account_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Cleanup::ServiceAccountWorker do
+RSpec.describe Clusters::Cleanup::ServiceAccountWorker do
describe '#perform' do
let!(:cluster) { create(:cluster, :cleanup_removing_service_account) }
diff --git a/spec/workers/concerns/application_worker_spec.rb b/spec/workers/concerns/application_worker_spec.rb
index 087a36d2bd0..a18b83f199b 100644
--- a/spec/workers/concerns/application_worker_spec.rb
+++ b/spec/workers/concerns/application_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ApplicationWorker do
+RSpec.describe ApplicationWorker do
let_it_be(:worker) do
Class.new do
def self.name
diff --git a/spec/workers/concerns/cluster_queue_spec.rb b/spec/workers/concerns/cluster_queue_spec.rb
index 732d55dfbde..c03ca9cea48 100644
--- a/spec/workers/concerns/cluster_queue_spec.rb
+++ b/spec/workers/concerns/cluster_queue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ClusterQueue do
+RSpec.describe ClusterQueue do
let(:worker) do
Class.new do
def self.name
diff --git a/spec/workers/concerns/cronjob_queue_spec.rb b/spec/workers/concerns/cronjob_queue_spec.rb
index 0cea67bf116..d1ad5c65ea3 100644
--- a/spec/workers/concerns/cronjob_queue_spec.rb
+++ b/spec/workers/concerns/cronjob_queue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CronjobQueue do
+RSpec.describe CronjobQueue do
let(:worker) do
Class.new do
def self.name
diff --git a/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb b/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
index 51b685b5792..d0cbc6b35e2 100644
--- a/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::ObjectImporter do
+RSpec.describe Gitlab::GithubImport::ObjectImporter do
let(:worker) do
Class.new do
def self.name
diff --git a/spec/workers/concerns/gitlab/github_import/queue_spec.rb b/spec/workers/concerns/gitlab/github_import/queue_spec.rb
index d262bc2e05c..beca221b593 100644
--- a/spec/workers/concerns/gitlab/github_import/queue_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/queue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Queue do
+RSpec.describe Gitlab::GithubImport::Queue do
it 'sets the Sidekiq options for the worker' do
worker = Class.new do
def self.name
diff --git a/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb b/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb
index c4f6ddf9aca..09d64fe50bd 100644
--- a/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::ReschedulingMethods do
+RSpec.describe Gitlab::GithubImport::ReschedulingMethods do
let(:worker) do
Class.new { include(Gitlab::GithubImport::ReschedulingMethods) }.new
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 f9081a875b5..b7635748498 100644
--- a/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::StageMethods do
+RSpec.describe Gitlab::GithubImport::StageMethods do
let(:project) { create(:project) }
let(:worker) do
Class.new { include(Gitlab::GithubImport::StageMethods) }.new
diff --git a/spec/workers/concerns/gitlab/notify_upon_death_spec.rb b/spec/workers/concerns/gitlab/notify_upon_death_spec.rb
index 1c75ac99227..dd0a1cadc9c 100644
--- a/spec/workers/concerns/gitlab/notify_upon_death_spec.rb
+++ b/spec/workers/concerns/gitlab/notify_upon_death_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::NotifyUponDeath do
+RSpec.describe Gitlab::NotifyUponDeath do
let(:worker_class) do
Class.new do
include Sidekiq::Worker
diff --git a/spec/workers/concerns/pipeline_background_queue_spec.rb b/spec/workers/concerns/pipeline_background_queue_spec.rb
index 78ceafb359f..77c7e7440c5 100644
--- a/spec/workers/concerns/pipeline_background_queue_spec.rb
+++ b/spec/workers/concerns/pipeline_background_queue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelineBackgroundQueue do
+RSpec.describe PipelineBackgroundQueue do
let(:worker) do
Class.new do
def self.name
diff --git a/spec/workers/concerns/pipeline_queue_spec.rb b/spec/workers/concerns/pipeline_queue_spec.rb
index eedfceb8bf0..6c1ac2052e4 100644
--- a/spec/workers/concerns/pipeline_queue_spec.rb
+++ b/spec/workers/concerns/pipeline_queue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelineQueue do
+RSpec.describe PipelineQueue do
let(:worker) do
Class.new do
def self.name
diff --git a/spec/workers/concerns/project_export_options_spec.rb b/spec/workers/concerns/project_export_options_spec.rb
deleted file mode 100644
index 985afaaf11e..00000000000
--- a/spec/workers/concerns/project_export_options_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe ProjectExportOptions do
- let(:project) { create(:project) }
- let(:project_export_job) { create(:project_export_job, project: project, jid: '123', status: 1) }
- let(:job) { { 'args' => [project.owner.id, project.id, nil, nil], 'jid' => '123' } }
- let(:worker_class) do
- Class.new do
- include Sidekiq::Worker
- include ProjectExportOptions
- end
- end
-
- it 'sets default retry limit' do
- expect(worker_class.sidekiq_options['retry']).to eq(ProjectExportOptions::EXPORT_RETRY_COUNT)
- end
-
- it 'sets default status expiration' do
- expect(worker_class.sidekiq_options['status_expiration']).to eq(StuckExportJobsWorker::EXPORT_JOBS_EXPIRATION)
- end
-
- describe '.sidekiq_retries_exhausted' do
- it 'marks status as failed' do
- expect { worker_class.sidekiq_retries_exhausted_block.call(job) }.to change { project_export_job.reload.status }.from(1).to(3)
- end
-
- context 'when status update fails' do
- before do
- project_export_job.update(status: 2)
- end
-
- it 'logs an error' do
- expect(Sidekiq.logger).to receive(:error).with("Failed to set Job #{job['jid']} for project #{project.id} to failed state")
-
- worker_class.sidekiq_retries_exhausted_block.call(job)
- end
- end
- end
-end
diff --git a/spec/workers/concerns/project_import_options_spec.rb b/spec/workers/concerns/project_import_options_spec.rb
index c56dcc5ed82..85a26ddb0cb 100644
--- a/spec/workers/concerns/project_import_options_spec.rb
+++ b/spec/workers/concerns/project_import_options_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectImportOptions do
+RSpec.describe ProjectImportOptions do
let(:project) { create(:project, :import_started) }
let(:job) { { 'args' => [project.id, nil, nil], 'jid' => '123' } }
let(:worker_class) do
diff --git a/spec/workers/concerns/reenqueuer_spec.rb b/spec/workers/concerns/reenqueuer_spec.rb
index b28f83d211b..df0724045c1 100644
--- a/spec/workers/concerns/reenqueuer_spec.rb
+++ b/spec/workers/concerns/reenqueuer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Reenqueuer do
+RSpec.describe Reenqueuer do
include ExclusiveLeaseHelpers
let_it_be(:worker_class) do
@@ -93,7 +93,7 @@ describe Reenqueuer do
end
end
-describe Reenqueuer::ReenqueuerSleeper do
+RSpec.describe Reenqueuer::ReenqueuerSleeper do
let_it_be(:dummy_class) do
Class.new do
include Reenqueuer::ReenqueuerSleeper
diff --git a/spec/workers/concerns/repository_check_queue_spec.rb b/spec/workers/concerns/repository_check_queue_spec.rb
index 55ed71f124c..ae377c09b37 100644
--- a/spec/workers/concerns/repository_check_queue_spec.rb
+++ b/spec/workers/concerns/repository_check_queue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RepositoryCheckQueue do
+RSpec.describe RepositoryCheckQueue do
let(:worker) do
Class.new do
def self.name
diff --git a/spec/workers/concerns/waitable_worker_spec.rb b/spec/workers/concerns/waitable_worker_spec.rb
index e7ef96c685d..5d08d38380a 100644
--- a/spec/workers/concerns/waitable_worker_spec.rb
+++ b/spec/workers/concerns/waitable_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WaitableWorker do
+RSpec.describe WaitableWorker do
let(:worker) do
Class.new do
def self.name
diff --git a/spec/workers/concerns/worker_context_spec.rb b/spec/workers/concerns/worker_context_spec.rb
index 4e8c81c57dc..3de37b99aba 100644
--- a/spec/workers/concerns/worker_context_spec.rb
+++ b/spec/workers/concerns/worker_context_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WorkerContext do
+RSpec.describe WorkerContext do
let(:worker) do
Class.new do
def self.name
diff --git a/spec/workers/container_expiration_policy_worker_spec.rb b/spec/workers/container_expiration_policy_worker_spec.rb
index b15a28dcdca..868eb6b192e 100644
--- a/spec/workers/container_expiration_policy_worker_spec.rb
+++ b/spec/workers/container_expiration_policy_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ContainerExpirationPolicyWorker do
+RSpec.describe ContainerExpirationPolicyWorker do
include ExclusiveLeaseHelpers
subject { described_class.new.perform }
diff --git a/spec/workers/create_commit_signature_worker_spec.rb b/spec/workers/create_commit_signature_worker_spec.rb
index fd5d99b3265..d283ff5b732 100644
--- a/spec/workers/create_commit_signature_worker_spec.rb
+++ b/spec/workers/create_commit_signature_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CreateCommitSignatureWorker do
+RSpec.describe CreateCommitSignatureWorker do
let(:project) { create(:project, :repository) }
let(:commits) { project.repository.commits('HEAD', limit: 3).commits }
let(:commit_shas) { commits.map(&:id) }
diff --git a/spec/workers/create_evidence_worker_spec.rb b/spec/workers/create_evidence_worker_spec.rb
index b8c622f7d1d..c700c086163 100644
--- a/spec/workers/create_evidence_worker_spec.rb
+++ b/spec/workers/create_evidence_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CreateEvidenceWorker do
+RSpec.describe CreateEvidenceWorker do
let(:project) { create(:project, :repository) }
let(:release) { create(:release, project: project) }
let(:pipeline) { create(:ci_empty_pipeline, sha: release.sha, project: project) }
diff --git a/spec/workers/create_note_diff_file_worker_spec.rb b/spec/workers/create_note_diff_file_worker_spec.rb
index e35aaa7d593..4c1df8ade06 100644
--- a/spec/workers/create_note_diff_file_worker_spec.rb
+++ b/spec/workers/create_note_diff_file_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CreateNoteDiffFileWorker do
+RSpec.describe CreateNoteDiffFileWorker do
describe '#perform' do
let(:diff_note) { create(:diff_note_on_merge_request) }
diff --git a/spec/workers/create_pipeline_worker_spec.rb b/spec/workers/create_pipeline_worker_spec.rb
index 62a17da80c0..6a3729fa28a 100644
--- a/spec/workers/create_pipeline_worker_spec.rb
+++ b/spec/workers/create_pipeline_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CreatePipelineWorker do
+RSpec.describe CreatePipelineWorker do
describe '#perform' do
let(:worker) { described_class.new }
diff --git a/spec/workers/delete_container_repository_worker_spec.rb b/spec/workers/delete_container_repository_worker_spec.rb
index 8c40611a959..b8363a2f81a 100644
--- a/spec/workers/delete_container_repository_worker_spec.rb
+++ b/spec/workers/delete_container_repository_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeleteContainerRepositoryWorker do
+RSpec.describe DeleteContainerRepositoryWorker do
let(:registry) { create(:container_repository) }
let(:project) { registry.project }
let(:user) { project.owner }
diff --git a/spec/workers/delete_diff_files_worker_spec.rb b/spec/workers/delete_diff_files_worker_spec.rb
index 9f8b20df48e..b3b01588e0b 100644
--- a/spec/workers/delete_diff_files_worker_spec.rb
+++ b/spec/workers/delete_diff_files_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeleteDiffFilesWorker do
+RSpec.describe DeleteDiffFilesWorker do
describe '#perform' do
let(:merge_request) { create(:merge_request) }
let(:merge_request_diff) { merge_request.merge_request_diff }
diff --git a/spec/workers/delete_merged_branches_worker_spec.rb b/spec/workers/delete_merged_branches_worker_spec.rb
index 3eaeb7e0797..861ca111b92 100644
--- a/spec/workers/delete_merged_branches_worker_spec.rb
+++ b/spec/workers/delete_merged_branches_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeleteMergedBranchesWorker do
+RSpec.describe DeleteMergedBranchesWorker do
subject(:worker) { described_class.new }
let(:project) { create(:project, :repository) }
diff --git a/spec/workers/delete_user_worker_spec.rb b/spec/workers/delete_user_worker_spec.rb
index c963b886e62..52f2c692b8c 100644
--- a/spec/workers/delete_user_worker_spec.rb
+++ b/spec/workers/delete_user_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DeleteUserWorker do
+RSpec.describe DeleteUserWorker do
let!(:user) { create(:user) }
let!(:current_user) { create(:user) }
diff --git a/spec/workers/deployments/finished_worker_spec.rb b/spec/workers/deployments/finished_worker_spec.rb
index 2961ff599c3..9b4bd78c03a 100644
--- a/spec/workers/deployments/finished_worker_spec.rb
+++ b/spec/workers/deployments/finished_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Deployments::FinishedWorker do
+RSpec.describe Deployments::FinishedWorker do
let(:worker) { described_class.new }
describe '#perform' do
diff --git a/spec/workers/deployments/success_worker_spec.rb b/spec/workers/deployments/success_worker_spec.rb
index 7f2816d7535..7c21a3147a7 100644
--- a/spec/workers/deployments/success_worker_spec.rb
+++ b/spec/workers/deployments/success_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Deployments::SuccessWorker do
+RSpec.describe Deployments::SuccessWorker do
subject { described_class.new.perform(deployment&.id) }
context 'when successful deployment' do
diff --git a/spec/workers/design_management/new_version_worker_spec.rb b/spec/workers/design_management/new_version_worker_spec.rb
index ef7cd8de108..4d57c46487e 100644
--- a/spec/workers/design_management/new_version_worker_spec.rb
+++ b/spec/workers/design_management/new_version_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DesignManagement::NewVersionWorker do
+RSpec.describe DesignManagement::NewVersionWorker do
describe '#perform' do
let(:worker) { described_class.new }
diff --git a/spec/workers/detect_repository_languages_worker_spec.rb b/spec/workers/detect_repository_languages_worker_spec.rb
index 84af49050d4..217e16bd155 100644
--- a/spec/workers/detect_repository_languages_worker_spec.rb
+++ b/spec/workers/detect_repository_languages_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe DetectRepositoryLanguagesWorker do
+RSpec.describe DetectRepositoryLanguagesWorker do
let_it_be(:project) { create(:project) }
subject { described_class.new }
diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb
index f8a31fcdee6..8bf7f3f552d 100644
--- a/spec/workers/email_receiver_worker_spec.rb
+++ b/spec/workers/email_receiver_worker_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe EmailReceiverWorker, :mailer do
+RSpec.describe EmailReceiverWorker, :mailer do
let(:raw_message) { fixture_file('emails/valid_reply.eml') }
context "when reply by email is enabled" do
diff --git a/spec/workers/emails_on_push_worker_spec.rb b/spec/workers/emails_on_push_worker_spec.rb
index 0f87df89c29..fac463b4dd4 100644
--- a/spec/workers/emails_on_push_worker_spec.rb
+++ b/spec/workers/emails_on_push_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe EmailsOnPushWorker, :mailer do
+RSpec.describe EmailsOnPushWorker, :mailer do
include RepoHelpers
include EmailSpec::Matchers
diff --git a/spec/workers/environments/auto_stop_cron_worker_spec.rb b/spec/workers/environments/auto_stop_cron_worker_spec.rb
index 6773637d4a7..1e86597d288 100644
--- a/spec/workers/environments/auto_stop_cron_worker_spec.rb
+++ b/spec/workers/environments/auto_stop_cron_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Environments::AutoStopCronWorker do
+RSpec.describe Environments::AutoStopCronWorker do
subject { worker.perform }
let(:worker) { described_class.new }
diff --git a/spec/workers/error_tracking_issue_link_worker_spec.rb b/spec/workers/error_tracking_issue_link_worker_spec.rb
index 701d54b72f0..5be568c2dad 100644
--- a/spec/workers/error_tracking_issue_link_worker_spec.rb
+++ b/spec/workers/error_tracking_issue_link_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ErrorTrackingIssueLinkWorker do
+RSpec.describe ErrorTrackingIssueLinkWorker do
let_it_be(:error_tracking) { create(:project_error_tracking_setting) }
let_it_be(:project) { error_tracking.project }
let_it_be(:issue) { create(:issue, project: project) }
diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb
index 195783c74df..3bb9db07ff3 100644
--- a/spec/workers/every_sidekiq_worker_spec.rb
+++ b/spec/workers/every_sidekiq_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Every Sidekiq worker' do
+RSpec.describe 'Every Sidekiq worker' do
let(:workers_without_defaults) do
Gitlab::SidekiqConfig.workers - Gitlab::SidekiqConfig::DEFAULT_WORKERS
end
@@ -19,7 +19,7 @@ describe 'Every Sidekiq worker' do
file_worker_queues = Gitlab::SidekiqConfig.worker_queues.to_set
worker_queues = Gitlab::SidekiqConfig.workers.map(&:queue).to_set
- worker_queues << ActionMailer::DeliveryJob.new.queue_name
+ worker_queues << ActionMailer::MailDeliveryJob.new.queue_name
worker_queues << 'default'
missing_from_file = worker_queues - file_worker_queues
@@ -53,7 +53,7 @@ describe 'Every Sidekiq worker' do
# All Sidekiq worker classes should declare a valid `feature_category`
# or explicitly be excluded with the `feature_category_not_owned!` annotation.
- # Please see doc/development/sidekiq_style_guide.md#Feature-Categorization for more details.
+ # Please see doc/development/sidekiq_style_guide.md#feature-categorization for more details.
it 'has a feature_category or feature_category_not_owned! attribute', :aggregate_failures do
workers_without_defaults.each do |worker|
expect(worker.get_feature_category).to be_a(Symbol), "expected #{worker.inspect} to declare a feature_category or feature_category_not_owned!"
@@ -62,7 +62,7 @@ describe 'Every Sidekiq worker' do
# All Sidekiq worker classes should declare a valid `feature_category`.
# The category should match a value in `config/feature_categories.yml`.
- # Please see doc/development/sidekiq_style_guide.md#Feature-Categorization for more details.
+ # Please see doc/development/sidekiq_style_guide.md#feature-categorization for more details.
it 'has a feature_category that maps to a value in feature_categories.yml', :aggregate_failures do
workers_with_feature_categories = workers_without_defaults
.select(&:get_feature_category)
diff --git a/spec/workers/expire_build_artifacts_worker_spec.rb b/spec/workers/expire_build_artifacts_worker_spec.rb
index 06561e94fb7..995f37daf17 100644
--- a/spec/workers/expire_build_artifacts_worker_spec.rb
+++ b/spec/workers/expire_build_artifacts_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExpireBuildArtifactsWorker do
+RSpec.describe ExpireBuildArtifactsWorker do
let(:worker) { described_class.new }
describe '#perform' do
diff --git a/spec/workers/expire_build_instance_artifacts_worker_spec.rb b/spec/workers/expire_build_instance_artifacts_worker_spec.rb
index 335fa5e6c98..38318447b5f 100644
--- a/spec/workers/expire_build_instance_artifacts_worker_spec.rb
+++ b/spec/workers/expire_build_instance_artifacts_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExpireBuildInstanceArtifactsWorker do
+RSpec.describe ExpireBuildInstanceArtifactsWorker do
include RepoHelpers
let(:worker) { described_class.new }
@@ -32,7 +32,7 @@ describe ExpireBuildInstanceArtifactsWorker do
context 'with not yet expired artifacts' do
let_it_be(:build) do
- create(:ci_build, :artifacts, artifacts_expire_at: Time.now + 7.days)
+ create(:ci_build, :artifacts, artifacts_expire_at: Time.current + 7.days)
end
it 'does not expire' do
diff --git a/spec/workers/expire_job_cache_worker_spec.rb b/spec/workers/expire_job_cache_worker_spec.rb
index 062926cf7aa..b4f8f56563b 100644
--- a/spec/workers/expire_job_cache_worker_spec.rb
+++ b/spec/workers/expire_job_cache_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExpireJobCacheWorker do
+RSpec.describe ExpireJobCacheWorker do
let_it_be(:pipeline) { create(:ci_empty_pipeline) }
let(:project) { pipeline.project }
diff --git a/spec/workers/expire_pipeline_cache_worker_spec.rb b/spec/workers/expire_pipeline_cache_worker_spec.rb
index 61ea22fbd32..fb6ee67311c 100644
--- a/spec/workers/expire_pipeline_cache_worker_spec.rb
+++ b/spec/workers/expire_pipeline_cache_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExpirePipelineCacheWorker do
+RSpec.describe ExpirePipelineCacheWorker do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
diff --git a/spec/workers/export_csv_worker_spec.rb b/spec/workers/export_csv_worker_spec.rb
index 87285b6264a..1a5b17ee35b 100644
--- a/spec/workers/export_csv_worker_spec.rb
+++ b/spec/workers/export_csv_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ExportCsvWorker do
+RSpec.describe ExportCsvWorker do
let(:user) { create(:user) }
let(:project) { create(:project, creator: user) }
diff --git a/spec/workers/external_service_reactive_caching_worker_spec.rb b/spec/workers/external_service_reactive_caching_worker_spec.rb
index 45cce71b75b..907894d9b9a 100644
--- a/spec/workers/external_service_reactive_caching_worker_spec.rb
+++ b/spec/workers/external_service_reactive_caching_worker_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe ExternalServiceReactiveCachingWorker do
+RSpec.describe ExternalServiceReactiveCachingWorker do
it_behaves_like 'reactive cacheable worker'
end
diff --git a/spec/workers/file_hook_worker_spec.rb b/spec/workers/file_hook_worker_spec.rb
index 1a7e753fc4a..c171dc37e5f 100644
--- a/spec/workers/file_hook_worker_spec.rb
+++ b/spec/workers/file_hook_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe FileHookWorker do
+RSpec.describe FileHookWorker do
include RepoHelpers
let(:filename) { 'my_file_hook.rb' }
diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb
index 64ad4ba7eb6..cb6396e2859 100644
--- a/spec/workers/git_garbage_collect_worker_spec.rb
+++ b/spec/workers/git_garbage_collect_worker_spec.rb
@@ -4,7 +4,7 @@ require 'fileutils'
require 'spec_helper'
-describe GitGarbageCollectWorker do
+RSpec.describe GitGarbageCollectWorker do
include GitHelpers
let(:project) { create(:project, :repository) }
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 b1647d8c7df..4e8261f61c4 100644
--- a/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::AdvanceStageWorker, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::GithubImport::AdvanceStageWorker, :clean_gitlab_redis_shared_state do
let(:project) { create(:project) }
let(:import_state) { create(:import_state, project: project, jid: '123') }
let(:worker) { described_class.new }
diff --git a/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb b/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
index 42d69ff6166..211eba993f7 100644
--- a/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::ImportDiffNoteWorker do
+RSpec.describe Gitlab::GithubImport::ImportDiffNoteWorker do
let(:worker) { described_class.new }
describe '#import' do
diff --git a/spec/workers/gitlab/github_import/import_issue_worker_spec.rb b/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
index 06a573e16b7..1d285790ee2 100644
--- a/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::ImportIssueWorker do
+RSpec.describe Gitlab::GithubImport::ImportIssueWorker do
let(:worker) { described_class.new }
describe '#import' do
diff --git a/spec/workers/gitlab/github_import/import_note_worker_spec.rb b/spec/workers/gitlab/github_import/import_note_worker_spec.rb
index 5110c3ff11b..618aca4ff0a 100644
--- a/spec/workers/gitlab/github_import/import_note_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_note_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::ImportNoteWorker do
+RSpec.describe Gitlab::GithubImport::ImportNoteWorker do
let(:worker) { described_class.new }
describe '#import' do
diff --git a/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb b/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
index d46e381fc51..0f5df302d56 100644
--- a/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::ImportPullRequestWorker do
+RSpec.describe Gitlab::GithubImport::ImportPullRequestWorker do
let(:worker) { described_class.new }
describe '#import' do
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 fa4ded8e42f..3a8b585fa77 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
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::RefreshImportJidWorker do
+RSpec.describe Gitlab::GithubImport::RefreshImportJidWorker do
let(:worker) { described_class.new }
describe '.perform_in_the_future' do
diff --git a/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb b/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb
index 35a856802c2..c821e0aaa09 100644
--- a/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Stage::FinishImportWorker do
+RSpec.describe Gitlab::GithubImport::Stage::FinishImportWorker do
let(:project) { create(:project) }
let(:worker) { described_class.new }
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 0c7fc2a164e..f68d0838501 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,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Stage::ImportBaseDataWorker do
+RSpec.describe Gitlab::GithubImport::Stage::ImportBaseDataWorker do
let(:project) { create(:project) }
let(:import_state) { create(:import_state, project: project) }
let(:worker) { described_class.new }
diff --git a/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb
index 5d96f562c30..f2a28ec40b8 100644
--- a/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Stage::ImportIssuesAndDiffNotesWorker do
+RSpec.describe Gitlab::GithubImport::Stage::ImportIssuesAndDiffNotesWorker do
let(:project) { create(:project) }
let(:worker) { described_class.new }
diff --git a/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb
index e7c9dabb292..103d55890c4 100644
--- a/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Stage::ImportLfsObjectsWorker do
+RSpec.describe Gitlab::GithubImport::Stage::ImportLfsObjectsWorker do
let(:project) { create(:project) }
let(:worker) { described_class.new }
diff --git a/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb
index 90590a45900..73b19239f4a 100644
--- a/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Stage::ImportNotesWorker do
+RSpec.describe Gitlab::GithubImport::Stage::ImportNotesWorker do
let(:project) { create(:project) }
let(:worker) { described_class.new }
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 15d485f1018..0acbca7032c 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,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Stage::ImportPullRequestsWorker do
+RSpec.describe Gitlab::GithubImport::Stage::ImportPullRequestsWorker do
let(:project) { create(:project) }
let(:import_state) { create(:import_state, project: project) }
let(:worker) { described_class.new }
diff --git a/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb
index 3a8fe73622a..bc51a44e057 100644
--- a/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Stage::ImportRepositoryWorker do
+RSpec.describe Gitlab::GithubImport::Stage::ImportRepositoryWorker do
let(:project) { double(:project, id: 4) }
let(:worker) { described_class.new }
diff --git a/spec/workers/gitlab/import/stuck_project_import_jobs_worker_spec.rb b/spec/workers/gitlab/import/stuck_project_import_jobs_worker_spec.rb
index 5afc5717b82..510c41cba21 100644
--- a/spec/workers/gitlab/import/stuck_project_import_jobs_worker_spec.rb
+++ b/spec/workers/gitlab/import/stuck_project_import_jobs_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Import::StuckProjectImportJobsWorker do
+RSpec.describe Gitlab::Import::StuckProjectImportJobsWorker do
let(:worker) { described_class.new }
describe 'with scheduled import_status' do
diff --git a/spec/workers/gitlab/jira_import/import_issue_worker_spec.rb b/spec/workers/gitlab/jira_import/import_issue_worker_spec.rb
index 2de609761e2..4a4ef5700fa 100644
--- a/spec/workers/gitlab/jira_import/import_issue_worker_spec.rb
+++ b/spec/workers/gitlab/jira_import/import_issue_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::ImportIssueWorker do
+RSpec.describe Gitlab::JiraImport::ImportIssueWorker do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:jira_issue_label_1) { create(:label, project: project) }
diff --git a/spec/workers/gitlab/jira_import/stage/finish_import_worker_spec.rb b/spec/workers/gitlab/jira_import/stage/finish_import_worker_spec.rb
index 084302be7d8..23a764bd972 100644
--- a/spec/workers/gitlab/jira_import/stage/finish_import_worker_spec.rb
+++ b/spec/workers/gitlab/jira_import/stage/finish_import_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::Stage::FinishImportWorker do
+RSpec.describe Gitlab::JiraImport::Stage::FinishImportWorker do
let_it_be(:project) { create(:project) }
let_it_be(:worker) { described_class.new }
diff --git a/spec/workers/gitlab/jira_import/stage/import_attachments_worker_spec.rb b/spec/workers/gitlab/jira_import/stage/import_attachments_worker_spec.rb
index 34981d974cd..28da93b8d00 100644
--- a/spec/workers/gitlab/jira_import/stage/import_attachments_worker_spec.rb
+++ b/spec/workers/gitlab/jira_import/stage/import_attachments_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::Stage::ImportAttachmentsWorker do
+RSpec.describe Gitlab::JiraImport::Stage::ImportAttachmentsWorker do
let_it_be(:project) { create(:project, import_type: 'jira') }
describe 'modules' do
diff --git a/spec/workers/gitlab/jira_import/stage/import_issues_worker_spec.rb b/spec/workers/gitlab/jira_import/stage/import_issues_worker_spec.rb
index 40f6cf75412..f82f6ccd9d6 100644
--- a/spec/workers/gitlab/jira_import/stage/import_issues_worker_spec.rb
+++ b/spec/workers/gitlab/jira_import/stage/import_issues_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::Stage::ImportIssuesWorker do
+RSpec.describe Gitlab::JiraImport::Stage::ImportIssuesWorker do
include JiraServiceHelper
let_it_be(:user) { create(:user) }
diff --git a/spec/workers/gitlab/jira_import/stage/import_labels_worker_spec.rb b/spec/workers/gitlab/jira_import/stage/import_labels_worker_spec.rb
index 1215b41bd9f..0b7a35a92e2 100644
--- a/spec/workers/gitlab/jira_import/stage/import_labels_worker_spec.rb
+++ b/spec/workers/gitlab/jira_import/stage/import_labels_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::Stage::ImportLabelsWorker do
+RSpec.describe Gitlab::JiraImport::Stage::ImportLabelsWorker do
include JiraServiceHelper
let_it_be(:user) { create(:user) }
diff --git a/spec/workers/gitlab/jira_import/stage/import_notes_worker_spec.rb b/spec/workers/gitlab/jira_import/stage/import_notes_worker_spec.rb
index a0a9ad6f695..2502bbf1df4 100644
--- a/spec/workers/gitlab/jira_import/stage/import_notes_worker_spec.rb
+++ b/spec/workers/gitlab/jira_import/stage/import_notes_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::Stage::ImportNotesWorker do
+RSpec.describe Gitlab::JiraImport::Stage::ImportNotesWorker do
let_it_be(:project) { create(:project, import_type: 'jira') }
describe 'modules' do
diff --git a/spec/workers/gitlab/jira_import/stage/start_import_worker_spec.rb b/spec/workers/gitlab/jira_import/stage/start_import_worker_spec.rb
index a4fc761accf..7066e6e912f 100644
--- a/spec/workers/gitlab/jira_import/stage/start_import_worker_spec.rb
+++ b/spec/workers/gitlab/jira_import/stage/start_import_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::JiraImport::Stage::StartImportWorker do
+RSpec.describe Gitlab::JiraImport::Stage::StartImportWorker do
let_it_be(:project) { create(:project, import_type: 'jira') }
let_it_be(:jid) { '12345678' }
let(:worker) { described_class.new }
diff --git a/spec/workers/gitlab/jira_import/stuck_jira_import_jobs_worker_spec.rb b/spec/workers/gitlab/jira_import/stuck_jira_import_jobs_worker_spec.rb
index fae52cec2b4..8271af4db2f 100644
--- a/spec/workers/gitlab/jira_import/stuck_jira_import_jobs_worker_spec.rb
+++ b/spec/workers/gitlab/jira_import/stuck_jira_import_jobs_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::Gitlab::JiraImport::StuckJiraImportJobsWorker do
+RSpec.describe ::Gitlab::JiraImport::StuckJiraImportJobsWorker do
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) }
let(:worker) { described_class.new }
diff --git a/spec/workers/gitlab/phabricator_import/base_worker_spec.rb b/spec/workers/gitlab/phabricator_import/base_worker_spec.rb
index d46d908a3e3..18fa484aa7a 100644
--- a/spec/workers/gitlab/phabricator_import/base_worker_spec.rb
+++ b/spec/workers/gitlab/phabricator_import/base_worker_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::PhabricatorImport::BaseWorker do
+RSpec.describe Gitlab::PhabricatorImport::BaseWorker do
let(:subclass) do
# Creating an anonymous class for a worker is complicated, as we generate the
# queue name from the class name.
diff --git a/spec/workers/gitlab/phabricator_import/import_tasks_worker_spec.rb b/spec/workers/gitlab/phabricator_import/import_tasks_worker_spec.rb
index 1e38ef8aaa5..221b6202166 100644
--- a/spec/workers/gitlab/phabricator_import/import_tasks_worker_spec.rb
+++ b/spec/workers/gitlab/phabricator_import/import_tasks_worker_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::PhabricatorImport::ImportTasksWorker do
+RSpec.describe Gitlab::PhabricatorImport::ImportTasksWorker do
describe '#perform' do
it 'calls the correct importer' do
project = create(:project, :import_started, import_url: "https://the.phab.ulr")
diff --git a/spec/workers/gitlab_shell_worker_spec.rb b/spec/workers/gitlab_shell_worker_spec.rb
index 0e63f48d3e8..c46ef87333a 100644
--- a/spec/workers/gitlab_shell_worker_spec.rb
+++ b/spec/workers/gitlab_shell_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabShellWorker do
+RSpec.describe GitlabShellWorker do
let(:worker) { described_class.new }
describe '#perform' do
diff --git a/spec/workers/gitlab_usage_ping_worker_spec.rb b/spec/workers/gitlab_usage_ping_worker_spec.rb
index 198daf40493..05d6f2e585b 100644
--- a/spec/workers/gitlab_usage_ping_worker_spec.rb
+++ b/spec/workers/gitlab_usage_ping_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GitlabUsagePingWorker do
+RSpec.describe GitlabUsagePingWorker do
subject { described_class.new }
it 'delegates to SubmitUsagePingService' do
diff --git a/spec/workers/group_destroy_worker_spec.rb b/spec/workers/group_destroy_worker_spec.rb
index 90a4150a31a..ab3dd19dec1 100644
--- a/spec/workers/group_destroy_worker_spec.rb
+++ b/spec/workers/group_destroy_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupDestroyWorker do
+RSpec.describe GroupDestroyWorker do
let(:group) { create(:group) }
let(:user) { create(:admin) }
let!(:project) { create(:project, namespace: group) }
diff --git a/spec/workers/group_export_worker_spec.rb b/spec/workers/group_export_worker_spec.rb
index 350777df912..5697e66b7d1 100644
--- a/spec/workers/group_export_worker_spec.rb
+++ b/spec/workers/group_export_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupExportWorker do
+RSpec.describe GroupExportWorker do
let!(:user) { create(:user) }
let!(:group) { create(:group) }
diff --git a/spec/workers/group_import_worker_spec.rb b/spec/workers/group_import_worker_spec.rb
index 324a5fa6978..fb2d49c21af 100644
--- a/spec/workers/group_import_worker_spec.rb
+++ b/spec/workers/group_import_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupImportWorker do
+RSpec.describe GroupImportWorker do
let!(:user) { create(:user) }
let!(:group) { create(:group) }
diff --git a/spec/workers/hashed_storage/migrator_worker_spec.rb b/spec/workers/hashed_storage/migrator_worker_spec.rb
index ac76a306f43..e014297756e 100644
--- a/spec/workers/hashed_storage/migrator_worker_spec.rb
+++ b/spec/workers/hashed_storage/migrator_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe HashedStorage::MigratorWorker do
+RSpec.describe HashedStorage::MigratorWorker do
subject(:worker) { described_class.new }
let(:projects) { create_list(:project, 2, :legacy_storage, :empty_repo) }
diff --git a/spec/workers/hashed_storage/project_migrate_worker_spec.rb b/spec/workers/hashed_storage/project_migrate_worker_spec.rb
index 4b1b5f84fd3..fd460888932 100644
--- a/spec/workers/hashed_storage/project_migrate_worker_spec.rb
+++ b/spec/workers/hashed_storage/project_migrate_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe HashedStorage::ProjectMigrateWorker, :clean_gitlab_redis_shared_state do
+RSpec.describe HashedStorage::ProjectMigrateWorker, :clean_gitlab_redis_shared_state do
include ExclusiveLeaseHelpers
let(:migration_service) { ::Projects::HashedStorage::MigrationService }
diff --git a/spec/workers/hashed_storage/project_rollback_worker_spec.rb b/spec/workers/hashed_storage/project_rollback_worker_spec.rb
index d833553c0ec..fc89ac728b1 100644
--- a/spec/workers/hashed_storage/project_rollback_worker_spec.rb
+++ b/spec/workers/hashed_storage/project_rollback_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe HashedStorage::ProjectRollbackWorker, :clean_gitlab_redis_shared_state do
+RSpec.describe HashedStorage::ProjectRollbackWorker, :clean_gitlab_redis_shared_state do
include ExclusiveLeaseHelpers
describe '#perform' do
diff --git a/spec/workers/hashed_storage/rollbacker_worker_spec.rb b/spec/workers/hashed_storage/rollbacker_worker_spec.rb
index 55fc4fb0fe1..46cca068273 100644
--- a/spec/workers/hashed_storage/rollbacker_worker_spec.rb
+++ b/spec/workers/hashed_storage/rollbacker_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe HashedStorage::RollbackerWorker do
+RSpec.describe HashedStorage::RollbackerWorker do
subject(:worker) { described_class.new }
let(:projects) { create_list(:project, 2, :empty_repo) }
diff --git a/spec/workers/import_issues_csv_worker_spec.rb b/spec/workers/import_issues_csv_worker_spec.rb
index 03944cfb05d..c5420b00e8a 100644
--- a/spec/workers/import_issues_csv_worker_spec.rb
+++ b/spec/workers/import_issues_csv_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ImportIssuesCsvWorker do
+RSpec.describe ImportIssuesCsvWorker do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:upload) { create(:upload) }
diff --git a/spec/workers/incident_management/pager_duty/process_incident_worker_spec.rb b/spec/workers/incident_management/pager_duty/process_incident_worker_spec.rb
new file mode 100644
index 00000000000..e2be91516b9
--- /dev/null
+++ b/spec/workers/incident_management/pager_duty/process_incident_worker_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe IncidentManagement::PagerDuty::ProcessIncidentWorker do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:incident_management_setting) { create(:project_incident_management_setting, project: project, pagerduty_active: true) }
+
+ describe '#perform' do
+ subject(:perform) { described_class.new.perform(project.id, incident_payload) }
+
+ context 'with valid incident payload' do
+ let(:incident_payload) do
+ {
+ 'url' => 'https://webdemo.pagerduty.com/incidents/PRORDTY',
+ 'incident_number' => 33,
+ 'title' => 'My new incident',
+ 'status' => 'triggered',
+ 'created_at' => '2017-09-26T15:14:36Z',
+ 'urgency' => 'high',
+ 'incident_key' => nil,
+ 'assignees' => [{
+ 'summary' => 'Laura Haley', 'url' => 'https://webdemo.pagerduty.com/users/P553OPV'
+ }],
+ 'impacted_services' => [{
+ 'summary' => 'Production XDB Cluster', 'url' => 'https://webdemo.pagerduty.com/services/PN49J75'
+ }]
+ }
+ end
+
+ it 'creates a GitLab issue' do
+ expect { perform }.to change(Issue, :count).by(1)
+ end
+ end
+
+ context 'with invalid incident payload' do
+ let(:incident_payload) { {} }
+
+ before do
+ allow(Gitlab::AppLogger).to receive(:warn).and_call_original
+ end
+
+ it 'does not create a GitLab issue' do
+ expect { perform }.not_to change(Issue, :count)
+ end
+
+ it 'logs a warning' do
+ perform
+
+ expect(Gitlab::AppLogger).to have_received(:warn).with(
+ message: 'Cannot create issue for PagerDuty incident',
+ issue_errors: "Title can't be blank"
+ )
+ end
+ end
+ end
+end
diff --git a/spec/workers/incident_management/process_alert_worker_spec.rb b/spec/workers/incident_management/process_alert_worker_spec.rb
index 0470552d933..75696d15ab8 100644
--- a/spec/workers/incident_management/process_alert_worker_spec.rb
+++ b/spec/workers/incident_management/process_alert_worker_spec.rb
@@ -2,40 +2,36 @@
require 'spec_helper'
-describe IncidentManagement::ProcessAlertWorker do
+RSpec.describe IncidentManagement::ProcessAlertWorker do
let_it_be(:project) { create(:project) }
let_it_be(:settings) { create(:project_incident_management_setting, project: project, create_issue: true) }
describe '#perform' do
- let(:alert_management_alert_id) { nil }
- let(:alert_payload) do
- {
- 'annotations' => { 'title' => 'title' },
- 'startsAt' => Time.now.rfc3339
- }
- end
-
- let(:created_issue) { Issue.last }
+ let_it_be(:started_at) { Time.now.rfc3339 }
+ let_it_be(:payload) { { 'title' => 'title', 'start_time' => started_at } }
+ let_it_be(:parsed_payload) { Gitlab::Alerting::NotificationPayloadParser.call(payload, project) }
+ let_it_be(:alert) { create(:alert_management_alert, project: project, payload: payload, started_at: started_at) }
+ let(:created_issue) { Issue.last! }
- subject { described_class.new.perform(project.id, alert_payload, alert_management_alert_id) }
+ subject { described_class.new.perform(nil, nil, alert.id) }
before do
allow(IncidentManagement::CreateIssueService)
- .to receive(:new).with(project, alert_payload)
+ .to receive(:new).with(alert.project, parsed_payload)
.and_call_original
end
it 'creates an issue' do
expect(IncidentManagement::CreateIssueService)
- .to receive(:new).with(project, alert_payload)
+ .to receive(:new).with(alert.project, parsed_payload)
expect { subject }.to change { Issue.count }.by(1)
end
- context 'with invalid project' do
- let(:invalid_project_id) { non_existing_record_id }
+ context 'with invalid alert' do
+ let(:invalid_alert_id) { non_existing_record_id }
- subject { described_class.new.perform(invalid_project_id, alert_payload) }
+ subject { described_class.new.perform(nil, nil, invalid_alert_id) }
it 'does not create issues' do
expect(IncidentManagement::CreateIssueService).not_to receive(:new)
@@ -44,16 +40,8 @@ describe IncidentManagement::ProcessAlertWorker do
end
end
- context 'when alert_management_alert_id is present' do
- let!(:alert) { create(:alert_management_alert, project: project) }
- let(:alert_management_alert_id) { alert.id }
-
+ context 'with valid alert' do
before do
- allow(AlertManagement::Alert)
- .to receive(:find_by_id)
- .with(alert_management_alert_id)
- .and_return(alert)
-
allow(Gitlab::AppLogger).to receive(:warn).and_call_original
end
@@ -69,24 +57,24 @@ describe IncidentManagement::ProcessAlertWorker do
expect(Gitlab::AppLogger).not_to have_received(:warn)
end
- end
- context 'when alert cannot be updated' do
- let(:alert) { create(:alert_management_alert, :with_validation_errors, project: project) }
+ context 'when alert cannot be updated' do
+ let_it_be(:alert) { create(:alert_management_alert, :with_validation_errors, project: project, payload: payload) }
- it 'updates AlertManagement::Alert#issue_id' do
- expect { subject }.not_to change { alert.reload.issue_id }
- end
+ it 'updates AlertManagement::Alert#issue_id' do
+ expect { subject }.not_to change { alert.reload.issue_id }
+ end
- it 'logs a warning' do
- subject
+ it 'logs a warning' do
+ subject
- expect(Gitlab::AppLogger).to have_received(:warn).with(
- message: 'Cannot link an Issue with Alert',
- issue_id: created_issue.id,
- alert_id: alert_management_alert_id,
- alert_errors: { hosts: ['hosts array is over 255 chars'] }
- )
+ expect(Gitlab::AppLogger).to have_received(:warn).with(
+ message: 'Cannot link an Issue with Alert',
+ issue_id: created_issue.id,
+ alert_id: alert.id,
+ alert_errors: { hosts: ['hosts array is over 255 chars'] }
+ )
+ end
end
end
end
diff --git a/spec/workers/incident_management/process_prometheus_alert_worker_spec.rb b/spec/workers/incident_management/process_prometheus_alert_worker_spec.rb
index c9ea96df5c2..c294892a66f 100644
--- a/spec/workers/incident_management/process_prometheus_alert_worker_spec.rb
+++ b/spec/workers/incident_management/process_prometheus_alert_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IncidentManagement::ProcessPrometheusAlertWorker do
+RSpec.describe IncidentManagement::ProcessPrometheusAlertWorker do
describe '#perform' do
let_it_be(:project) { create(:project) }
let_it_be(:prometheus_alert) { create(:prometheus_alert, project: project) }
@@ -19,137 +19,9 @@ describe IncidentManagement::ProcessPrometheusAlertWorker do
}.with_indifferent_access
end
- it 'creates an issue' do
+ it 'does nothing' do
expect { subject.perform(project.id, alert_params) }
- .to change(Issue, :count)
- .by(1)
- end
-
- it 'relates issue to an event' do
- expect { subject.perform(project.id, alert_params) }
- .to change(prometheus_alert.related_issues, :count)
- .from(0)
- .to(1)
- end
-
- context 'resolved event' do
- let(:issue) { create(:issue, project: project) }
-
- before do
- prometheus_alert_event.related_issues << issue
- prometheus_alert_event.resolve
- end
-
- it 'does not create an issue' do
- expect { subject.perform(project.id, alert_params) }
- .not_to change(Issue, :count)
- end
-
- it 'closes the existing issue' do
- expect { subject.perform(project.id, alert_params) }
- .to change { issue.reload.state }
- .from('opened')
- .to('closed')
- end
-
- it 'leaves a system note on the issue' do
- expect(SystemNoteService)
- .to receive(:auto_resolve_prometheus_alert)
-
- subject.perform(project.id, alert_params)
- end
- end
-
- context 'when project could not be found' do
- let(:non_existing_project_id) { non_existing_record_id }
-
- it 'does not create an issue' do
- expect { subject.perform(non_existing_project_id, alert_params) }
- .not_to change(Issue, :count)
- end
-
- it 'does not relate issue to an event' do
- expect { subject.perform(non_existing_project_id, alert_params) }
- .not_to change(prometheus_alert.related_issues, :count)
- end
- end
-
- context 'when event could not be found' do
- before do
- alert_params[:labels][:gitlab_alert_id] = non_existing_record_id
- end
-
- it 'does not create an issue' do
- expect { subject.perform(project.id, alert_params) }
- .not_to change(Issue, :count)
- end
-
- it 'does not relate issue to an event' do
- expect { subject.perform(project.id, alert_params) }
- .not_to change(prometheus_alert.related_issues, :count)
- end
- end
-
- context 'when issue could not be created' do
- before do
- allow_next_instance_of(IncidentManagement::CreateIssueService) do |instance|
- allow(instance).to receive(:execute).and_return( { error: true } )
- end
- end
-
- it 'does not relate issue to an event' do
- expect { subject.perform(project.id, alert_params) }
- .not_to change(prometheus_alert.related_issues, :count)
- end
- end
-
- context 'self-managed alert' do
- let(:alert_name) { 'alert' }
- let(:starts_at) { Time.now.rfc3339 }
-
- let!(:prometheus_alert_event) do
- create(:self_managed_prometheus_alert_event, project: project, payload_key: payload_key)
- end
-
- let(:alert_params) do
- {
- startsAt: starts_at,
- generatorURL: 'http://localhost:9090/graph?g0.expr=vector%281%29&g0.tab=1',
- labels: {
- alertname: alert_name
- }
- }.with_indifferent_access
- end
-
- it 'creates an issue' do
- expect { subject.perform(project.id, alert_params) }
- .to change(Issue, :count)
- .by(1)
- end
-
- it 'relates issue to an event' do
- expect { subject.perform(project.id, alert_params) }
- .to change(prometheus_alert_event.related_issues, :count)
- .from(0)
- .to(1)
- end
-
- context 'when event could not be found' do
- before do
- alert_params[:generatorURL] = 'http://somethingelse.com'
- end
-
- it 'creates an issue' do
- expect { subject.perform(project.id, alert_params) }
- .to change(Issue, :count)
- .by(1)
- end
-
- it 'does not relate issue to an event' do
- expect { subject.perform(project.id, alert_params) }
- .not_to change(prometheus_alert.related_issues, :count)
- end
- end
+ .not_to change(Issue, :count)
end
end
end
diff --git a/spec/workers/invalid_gpg_signature_update_worker_spec.rb b/spec/workers/invalid_gpg_signature_update_worker_spec.rb
index 4f727469ea8..25c48b55cbb 100644
--- a/spec/workers/invalid_gpg_signature_update_worker_spec.rb
+++ b/spec/workers/invalid_gpg_signature_update_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe InvalidGpgSignatureUpdateWorker do
+RSpec.describe InvalidGpgSignatureUpdateWorker do
context 'when GpgKey is found' do
it 'calls NotificationService.new.run' do
gpg_key = create(:gpg_key)
diff --git a/spec/workers/irker_worker_spec.rb b/spec/workers/irker_worker_spec.rb
index 6b58c04d909..aa1f1d2fe1d 100644
--- a/spec/workers/irker_worker_spec.rb
+++ b/spec/workers/irker_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IrkerWorker, '#perform' do
+RSpec.describe IrkerWorker, '#perform' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let_it_be(:push_data) { HashWithIndifferentAccess.new(Gitlab::DataBuilder::Push.build_sample(project, user)) }
diff --git a/spec/workers/issue_due_scheduler_worker_spec.rb b/spec/workers/issue_due_scheduler_worker_spec.rb
index 61ad8330840..aecff4a3d93 100644
--- a/spec/workers/issue_due_scheduler_worker_spec.rb
+++ b/spec/workers/issue_due_scheduler_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe IssueDueSchedulerWorker do
+RSpec.describe IssueDueSchedulerWorker do
describe '#perform' do
it 'schedules one MailScheduler::IssueDueWorker per project with open issues due tomorrow' do
project1 = create(:project)
diff --git a/spec/workers/mail_scheduler/issue_due_worker_spec.rb b/spec/workers/mail_scheduler/issue_due_worker_spec.rb
index fa17775e9f2..c03cc0bda61 100644
--- a/spec/workers/mail_scheduler/issue_due_worker_spec.rb
+++ b/spec/workers/mail_scheduler/issue_due_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MailScheduler::IssueDueWorker do
+RSpec.describe MailScheduler::IssueDueWorker do
describe '#perform' do
let(:worker) { described_class.new }
let(:project) { create(:project) }
diff --git a/spec/workers/mail_scheduler/notification_service_worker_spec.rb b/spec/workers/mail_scheduler/notification_service_worker_spec.rb
index ac95a6d7e43..ff4a1646d09 100644
--- a/spec/workers/mail_scheduler/notification_service_worker_spec.rb
+++ b/spec/workers/mail_scheduler/notification_service_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MailScheduler::NotificationServiceWorker do
+RSpec.describe MailScheduler::NotificationServiceWorker do
let(:worker) { described_class.new }
let(:method) { 'new_key' }
diff --git a/spec/workers/members_destroyer/unassign_issuables_worker_spec.rb b/spec/workers/members_destroyer/unassign_issuables_worker_spec.rb
new file mode 100644
index 00000000000..2a325be1225
--- /dev/null
+++ b/spec/workers/members_destroyer/unassign_issuables_worker_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe MembersDestroyer::UnassignIssuablesWorker do
+ let_it_be(:group) { create(:group, :private) }
+ let_it_be(:user, reload: true) { create(:user) }
+
+ context 'when unsupported membership source entity' do
+ it 'exits early and logs error' do
+ params = { message: "SomeEntity is not a supported entity.", entity_type: 'SomeEntity', entity_id: group.id, user_id: user.id }
+
+ expect(Sidekiq.logger).to receive(:error).with(params)
+
+ described_class.new.perform(user.id, group.id, 'SomeEntity')
+ end
+ end
+
+ it "calls the Members::UnassignIssuablesService with the params it was given" do
+ service = double
+
+ expect(Members::UnassignIssuablesService).to receive(:new).with(user, group).and_return(service)
+ expect(service).to receive(:execute)
+
+ described_class.new.perform(user.id, group.id, 'Group')
+ end
+end
diff --git a/spec/workers/merge_request_mergeability_check_worker_spec.rb b/spec/workers/merge_request_mergeability_check_worker_spec.rb
index 8909af1f685..0349de5cbb3 100644
--- a/spec/workers/merge_request_mergeability_check_worker_spec.rb
+++ b/spec/workers/merge_request_mergeability_check_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequestMergeabilityCheckWorker do
+RSpec.describe MergeRequestMergeabilityCheckWorker do
subject { described_class.new }
describe '#perform' do
diff --git a/spec/workers/merge_worker_spec.rb b/spec/workers/merge_worker_spec.rb
index dc98c9836fa..97e8aeb616e 100644
--- a/spec/workers/merge_worker_spec.rb
+++ b/spec/workers/merge_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeWorker do
+RSpec.describe MergeWorker do
describe "remove source branch" do
let!(:merge_request) { create(:merge_request, source_branch: "markdown") }
let!(:source_project) { merge_request.source_project }
diff --git a/spec/workers/metrics/dashboard/prune_old_annotations_worker_spec.rb b/spec/workers/metrics/dashboard/prune_old_annotations_worker_spec.rb
index bab5a5d8740..d93612afe37 100644
--- a/spec/workers/metrics/dashboard/prune_old_annotations_worker_spec.rb
+++ b/spec/workers/metrics/dashboard/prune_old_annotations_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::PruneOldAnnotationsWorker do
+RSpec.describe Metrics::Dashboard::PruneOldAnnotationsWorker do
let_it_be(:now) { DateTime.parse('2020-06-02T00:12:00Z') }
let_it_be(:two_weeks_old_annotation) { create(:metrics_dashboard_annotation, starting_at: now.advance(weeks: -2)) }
let_it_be(:one_day_old_annotation) { create(:metrics_dashboard_annotation, starting_at: now.advance(days: -1)) }
diff --git a/spec/workers/metrics/dashboard/schedule_annotations_prune_worker_spec.rb b/spec/workers/metrics/dashboard/schedule_annotations_prune_worker_spec.rb
index bfe6fe3a90e..e0a5a8fd448 100644
--- a/spec/workers/metrics/dashboard/schedule_annotations_prune_worker_spec.rb
+++ b/spec/workers/metrics/dashboard/schedule_annotations_prune_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Metrics::Dashboard::ScheduleAnnotationsPruneWorker do
+RSpec.describe Metrics::Dashboard::ScheduleAnnotationsPruneWorker do
describe '#perform' do
it 'schedules annotations prune job with default cut off date' do
expect(Metrics::Dashboard::PruneOldAnnotationsWorker).to receive(:perform_async)
diff --git a/spec/workers/migrate_external_diffs_worker_spec.rb b/spec/workers/migrate_external_diffs_worker_spec.rb
index 88d48cad14b..86d4680acbe 100644
--- a/spec/workers/migrate_external_diffs_worker_spec.rb
+++ b/spec/workers/migrate_external_diffs_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MigrateExternalDiffsWorker do
+RSpec.describe MigrateExternalDiffsWorker do
let(:worker) { described_class.new }
let(:diff) { create(:merge_request).merge_request_diff }
diff --git a/spec/workers/namespaceless_project_destroy_worker_spec.rb b/spec/workers/namespaceless_project_destroy_worker_spec.rb
index ceea7c8d8f5..ef396bc7fbb 100644
--- a/spec/workers/namespaceless_project_destroy_worker_spec.rb
+++ b/spec/workers/namespaceless_project_destroy_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NamespacelessProjectDestroyWorker do
+RSpec.describe NamespacelessProjectDestroyWorker do
include ProjectForksHelper
subject { described_class.new }
diff --git a/spec/workers/namespaces/prune_aggregation_schedules_worker_spec.rb b/spec/workers/namespaces/prune_aggregation_schedules_worker_spec.rb
index b069b080531..11f2501cbe3 100644
--- a/spec/workers/namespaces/prune_aggregation_schedules_worker_spec.rb
+++ b/spec/workers/namespaces/prune_aggregation_schedules_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Namespaces::PruneAggregationSchedulesWorker, '#perform', :clean_gitlab_redis_shared_state do
+RSpec.describe Namespaces::PruneAggregationSchedulesWorker, '#perform', :clean_gitlab_redis_shared_state do
include ExclusiveLeaseHelpers
let(:namespaces) { create_list(:namespace, 5, :with_aggregation_schedule) }
diff --git a/spec/workers/namespaces/root_statistics_worker_spec.rb b/spec/workers/namespaces/root_statistics_worker_spec.rb
index 910a5b23e17..0c6e3e89973 100644
--- a/spec/workers/namespaces/root_statistics_worker_spec.rb
+++ b/spec/workers/namespaces/root_statistics_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Namespaces::RootStatisticsWorker, '#perform' do
+RSpec.describe Namespaces::RootStatisticsWorker, '#perform' do
let(:group) { create(:group, :with_aggregation_schedule) }
subject(:worker) { described_class.new }
diff --git a/spec/workers/namespaces/schedule_aggregation_worker_spec.rb b/spec/workers/namespaces/schedule_aggregation_worker_spec.rb
index 2f4c7f8bc07..f2fe53d6112 100644
--- a/spec/workers/namespaces/schedule_aggregation_worker_spec.rb
+++ b/spec/workers/namespaces/schedule_aggregation_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Namespaces::ScheduleAggregationWorker, '#perform', :clean_gitlab_redis_shared_state do
+RSpec.describe Namespaces::ScheduleAggregationWorker, '#perform', :clean_gitlab_redis_shared_state do
let(:group) { create(:group) }
subject(:worker) { described_class.new }
diff --git a/spec/workers/new_issue_worker_spec.rb b/spec/workers/new_issue_worker_spec.rb
index 1584e9d5302..6386af8d253 100644
--- a/spec/workers/new_issue_worker_spec.rb
+++ b/spec/workers/new_issue_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NewIssueWorker do
+RSpec.describe NewIssueWorker do
describe '#perform' do
let(:worker) { described_class.new }
diff --git a/spec/workers/new_merge_request_worker_spec.rb b/spec/workers/new_merge_request_worker_spec.rb
index fe22226903f..37449540db5 100644
--- a/spec/workers/new_merge_request_worker_spec.rb
+++ b/spec/workers/new_merge_request_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe NewMergeRequestWorker do
+RSpec.describe NewMergeRequestWorker do
describe '#perform' do
let(:worker) { described_class.new }
diff --git a/spec/workers/new_note_worker_spec.rb b/spec/workers/new_note_worker_spec.rb
index 57269355180..21f10fa5bfb 100644
--- a/spec/workers/new_note_worker_spec.rb
+++ b/spec/workers/new_note_worker_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe NewNoteWorker do
+RSpec.describe NewNoteWorker do
context 'when Note found' do
let(:note) { create(:note) }
diff --git a/spec/workers/new_release_worker_spec.rb b/spec/workers/new_release_worker_spec.rb
deleted file mode 100644
index de4e1bac48f..00000000000
--- a/spec/workers/new_release_worker_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-# TODO: Worker can be removed in 13.2:
-# https://gitlab.com/gitlab-org/gitlab/-/issues/218231
-require 'spec_helper'
-
-describe NewReleaseWorker do
- let(:release) { create(:release) }
-
- it 'sends a new release notification' do
- expect_next_instance_of(NotificationService) do |instance|
- expect(instance).to receive(:send_new_release_notifications).with(release)
- end
-
- described_class.new.perform(release.id)
- end
-end
diff --git a/spec/workers/object_pool/create_worker_spec.rb b/spec/workers/object_pool/create_worker_spec.rb
index 06416489472..4ec409bdf47 100644
--- a/spec/workers/object_pool/create_worker_spec.rb
+++ b/spec/workers/object_pool/create_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ObjectPool::CreateWorker do
+RSpec.describe ObjectPool::CreateWorker do
let(:pool) { create(:pool_repository, :scheduled) }
subject { described_class.new }
diff --git a/spec/workers/object_pool/destroy_worker_spec.rb b/spec/workers/object_pool/destroy_worker_spec.rb
index 52d457b4b71..130a666a42e 100644
--- a/spec/workers/object_pool/destroy_worker_spec.rb
+++ b/spec/workers/object_pool/destroy_worker_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe ObjectPool::DestroyWorker do
+RSpec.describe ObjectPool::DestroyWorker do
describe '#perform' do
context 'when no pool is in the database' do
it "doesn't raise an error" do
diff --git a/spec/workers/object_pool/join_worker_spec.rb b/spec/workers/object_pool/join_worker_spec.rb
index 906bc22c8d2..335c45e14e0 100644
--- a/spec/workers/object_pool/join_worker_spec.rb
+++ b/spec/workers/object_pool/join_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ObjectPool::JoinWorker do
+RSpec.describe ObjectPool::JoinWorker do
let(:pool) { create(:pool_repository, :ready) }
let(:project) { pool.source_project }
let(:repository) { project.repository }
diff --git a/spec/workers/packages/nuget/extraction_worker_spec.rb b/spec/workers/packages/nuget/extraction_worker_spec.rb
new file mode 100644
index 00000000000..35b5f1baed5
--- /dev/null
+++ b/spec/workers/packages/nuget/extraction_worker_spec.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::ExtractionWorker, type: :worker do
+ describe '#perform' do
+ let!(:package) { create(:nuget_package) }
+ let(:package_file) { package.package_files.first }
+ let(:package_file_id) { package_file.id }
+
+ let_it_be(:package_name) { 'DummyProject.DummyPackage' }
+ let_it_be(:package_version) { '1.0.0' }
+
+ subject { described_class.new.perform(package_file_id) }
+
+ context 'with valid package file' do
+ it 'updates package and package file' do
+ expect { subject }
+ .to not_change { Packages::Package.count }
+ .and not_change { Packages::PackageFile.count }
+ end
+
+ context 'with exisiting package' do
+ let!(:existing_package) { create(:nuget_package, project: package.project, name: package_name, version: package_version) }
+
+ it 'reuses existing package and updates package file' do
+ expect { subject }
+ .to change { Packages::Package.count }.by(-1)
+ .and change { existing_package.reload.package_files.count }.by(1)
+ .and not_change { Packages::PackageFile.count }
+ end
+ end
+ end
+
+ context 'with invalid package file id' do
+ let(:package_file_id) { 5555 }
+
+ it "doesn't update package and package file" do
+ expect { subject }
+ .to not_change { package.reload.name }
+ .and not_change { package.version }
+ .and not_change { package_file.reload.file_name }
+ end
+ end
+
+ context 'with package file not containing a nuspec file' do
+ before do
+ allow_any_instance_of(Zip::File).to receive(:glob).and_return([])
+ end
+
+ it 'removes the package and the package file' do
+ expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
+ instance_of(::Packages::Nuget::MetadataExtractionService::ExtractionError),
+ project_id: package.project_id
+ )
+ expect { subject }
+ .to change { Packages::Package.count }.by(-1)
+ .and change { Packages::PackageFile.count }.by(-1)
+ end
+ end
+
+ context 'with package file with a blank package name' do
+ before do
+ allow_any_instance_of(::Packages::Nuget::UpdatePackageFromMetadataService).to receive(:package_name).and_return('')
+ end
+
+ it 'removes the package and the package file' do
+ expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
+ instance_of(::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError),
+ project_id: package.project_id
+ )
+ expect { subject }
+ .to change { Packages::Package.count }.by(-1)
+ .and change { Packages::PackageFile.count }.by(-1)
+ end
+ end
+
+ context 'with package file with a blank package version' do
+ before do
+ allow_any_instance_of(::Packages::Nuget::UpdatePackageFromMetadataService).to receive(:package_version).and_return('')
+ end
+
+ it 'removes the package and the package file' do
+ expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
+ instance_of(::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError),
+ project_id: package.project_id
+ )
+ expect { subject }
+ .to change { Packages::Package.count }.by(-1)
+ .and change { Packages::PackageFile.count }.by(-1)
+ end
+ end
+ end
+end
diff --git a/spec/workers/pages_domain_removal_cron_worker_spec.rb b/spec/workers/pages_domain_removal_cron_worker_spec.rb
index 2408ad54189..f152d019de6 100644
--- a/spec/workers/pages_domain_removal_cron_worker_spec.rb
+++ b/spec/workers/pages_domain_removal_cron_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PagesDomainRemovalCronWorker do
+RSpec.describe PagesDomainRemovalCronWorker do
subject(:worker) { described_class.new }
describe '#perform' do
diff --git a/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb b/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb
index 1349a80029b..7c745e51df5 100644
--- a/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb
+++ b/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PagesDomainSslRenewalCronWorker do
+RSpec.describe PagesDomainSslRenewalCronWorker do
include LetsEncryptHelpers
subject(:worker) { described_class.new }
diff --git a/spec/workers/pages_domain_ssl_renewal_worker_spec.rb b/spec/workers/pages_domain_ssl_renewal_worker_spec.rb
index a35965f49b2..f8149b23a08 100644
--- a/spec/workers/pages_domain_ssl_renewal_worker_spec.rb
+++ b/spec/workers/pages_domain_ssl_renewal_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PagesDomainSslRenewalWorker do
+RSpec.describe PagesDomainSslRenewalWorker do
include LetsEncryptHelpers
subject(:worker) { described_class.new }
diff --git a/spec/workers/pages_domain_verification_cron_worker_spec.rb b/spec/workers/pages_domain_verification_cron_worker_spec.rb
index 6dd6c33f5fe..01eaf984c90 100644
--- a/spec/workers/pages_domain_verification_cron_worker_spec.rb
+++ b/spec/workers/pages_domain_verification_cron_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PagesDomainVerificationCronWorker do
+RSpec.describe PagesDomainVerificationCronWorker do
subject(:worker) { described_class.new }
describe '#perform', :sidekiq do
diff --git a/spec/workers/pages_domain_verification_worker_spec.rb b/spec/workers/pages_domain_verification_worker_spec.rb
index f51ac1f4323..74b9730f7c1 100644
--- a/spec/workers/pages_domain_verification_worker_spec.rb
+++ b/spec/workers/pages_domain_verification_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PagesDomainVerificationWorker do
+RSpec.describe PagesDomainVerificationWorker do
subject(:worker) { described_class.new }
let(:domain) { create(:pages_domain) }
diff --git a/spec/workers/partition_creation_worker_spec.rb b/spec/workers/partition_creation_worker_spec.rb
new file mode 100644
index 00000000000..50ed9c901c1
--- /dev/null
+++ b/spec/workers/partition_creation_worker_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe PartitionCreationWorker do
+ describe '#perform' do
+ let(:creator) { double(create_partitions: nil) }
+
+ before do
+ allow(Gitlab::Database::Partitioning::PartitionCreator).to receive(:new).and_return(creator)
+ end
+
+ it 'delegates to PartitionCreator' do
+ expect(creator).to receive(:create_partitions)
+
+ described_class.new.perform
+ end
+ end
+end
diff --git a/spec/workers/pipeline_hooks_worker_spec.rb b/spec/workers/pipeline_hooks_worker_spec.rb
index 60df08f40da..7c75cdc8823 100644
--- a/spec/workers/pipeline_hooks_worker_spec.rb
+++ b/spec/workers/pipeline_hooks_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelineHooksWorker do
+RSpec.describe PipelineHooksWorker do
describe '#perform' do
context 'when pipeline exists' do
let(:pipeline) { create(:ci_pipeline) }
diff --git a/spec/workers/pipeline_metrics_worker_spec.rb b/spec/workers/pipeline_metrics_worker_spec.rb
index 6beecbcd114..c73b84e26a6 100644
--- a/spec/workers/pipeline_metrics_worker_spec.rb
+++ b/spec/workers/pipeline_metrics_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelineMetricsWorker do
+RSpec.describe PipelineMetricsWorker do
let(:project) { create(:project, :repository) }
let!(:merge_request) do
@@ -18,7 +18,7 @@ describe PipelineMetricsWorker do
ref: 'master',
sha: project.repository.commit('master').id,
started_at: 1.hour.ago,
- finished_at: Time.now)
+ finished_at: Time.current)
end
let(:status) { 'pending' }
diff --git a/spec/workers/pipeline_notification_worker_spec.rb b/spec/workers/pipeline_notification_worker_spec.rb
index f2cc2b56236..9a15864173c 100644
--- a/spec/workers/pipeline_notification_worker_spec.rb
+++ b/spec/workers/pipeline_notification_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelineNotificationWorker, :mailer do
+RSpec.describe PipelineNotificationWorker, :mailer do
let_it_be(:pipeline) { create(:ci_pipeline) }
describe '#execute' do
diff --git a/spec/workers/pipeline_process_worker_spec.rb b/spec/workers/pipeline_process_worker_spec.rb
index 8260d7ac320..a6e6b505a38 100644
--- a/spec/workers/pipeline_process_worker_spec.rb
+++ b/spec/workers/pipeline_process_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelineProcessWorker do
+RSpec.describe PipelineProcessWorker do
describe '#perform' do
context 'when pipeline exists' do
let(:pipeline) { create(:ci_pipeline) }
diff --git a/spec/workers/pipeline_schedule_worker_spec.rb b/spec/workers/pipeline_schedule_worker_spec.rb
index 758cfa82d5d..f59d8ad4615 100644
--- a/spec/workers/pipeline_schedule_worker_spec.rb
+++ b/spec/workers/pipeline_schedule_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelineScheduleWorker do
+RSpec.describe PipelineScheduleWorker do
include ExclusiveLeaseHelpers
subject { described_class.new.perform }
@@ -33,7 +33,7 @@ describe PipelineScheduleWorker do
expect(Ci::Pipeline.last).to be_schedule
pipeline_schedule.reload
- expect(pipeline_schedule.next_run_at).to be > Time.now
+ expect(pipeline_schedule.next_run_at).to be > Time.current
expect(pipeline_schedule).to eq(project.ci_pipelines.last.pipeline_schedule)
expect(pipeline_schedule).to be_active
end
diff --git a/spec/workers/pipeline_update_ci_ref_status_worker_service_spec.rb b/spec/workers/pipeline_update_ci_ref_status_worker_service_spec.rb
index 3fe8aa55142..4e7af16c63d 100644
--- a/spec/workers/pipeline_update_ci_ref_status_worker_service_spec.rb
+++ b/spec/workers/pipeline_update_ci_ref_status_worker_service_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
# NOTE: This class is unused and to be removed in 13.1~
-describe PipelineUpdateCiRefStatusWorker do
+RSpec.describe PipelineUpdateCiRefStatusWorker do
let(:worker) { described_class.new }
let(:pipeline) { create(:ci_pipeline) }
diff --git a/spec/workers/pipeline_update_worker_spec.rb b/spec/workers/pipeline_update_worker_spec.rb
index 187298034cc..c5c1cc0eefd 100644
--- a/spec/workers/pipeline_update_worker_spec.rb
+++ b/spec/workers/pipeline_update_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PipelineUpdateWorker do
+RSpec.describe PipelineUpdateWorker do
describe '#perform' do
context 'when pipeline exists' do
let(:pipeline) { create(:ci_pipeline) }
@@ -12,6 +12,14 @@ describe PipelineUpdateWorker do
described_class.new.perform(pipeline.id)
end
+
+ include_examples 'an idempotent worker' do
+ let(:job_args) { [pipeline.id] }
+
+ it 'sets pipeline status to skipped' do
+ expect { subject }.to change { pipeline.reload.status }.from('pending').to('skipped')
+ end
+ end
end
context 'when pipeline does not exist' do
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 18e06332eb3..f64ee4aa2f7 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PostReceive do
+RSpec.describe PostReceive do
let(:changes) { "123456 789012 refs/heads/tést\n654321 210987 refs/tags/tag" }
let(:wrongly_encoded_changes) { changes.encode("ISO-8859-1").force_encoding("UTF-8") }
let(:base64_changes) { Base64.encode64(wrongly_encoded_changes) }
@@ -428,7 +428,12 @@ describe PostReceive do
it 'expires the status cache' do
expect(snippet.repository).to receive(:empty?).and_return(true)
expect(snippet.repository).to receive(:expire_status_cache)
- expect(snippet.repository).to receive(:expire_statistics_caches)
+
+ perform
+ end
+
+ it 'updates snippet statistics' do
+ expect(Snippets::UpdateStatisticsService).to receive(:new).with(snippet).and_call_original
perform
end
diff --git a/spec/workers/process_commit_worker_spec.rb b/spec/workers/process_commit_worker_spec.rb
index d247668ac76..a33ee6e1da5 100644
--- a/spec/workers/process_commit_worker_spec.rb
+++ b/spec/workers/process_commit_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProcessCommitWorker do
+RSpec.describe ProcessCommitWorker do
let(:worker) { described_class.new }
let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) }
@@ -200,9 +200,9 @@ describe ProcessCommitWorker do
it 'parses date strings into Time instances' do
commit = worker.build_commit(project,
id: '123',
- authored_date: Time.now.to_s)
+ authored_date: Time.current.to_s)
- expect(commit.authored_date).to be_an_instance_of(Time)
+ expect(commit.authored_date).to be_a_kind_of(Time)
end
end
end
diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb
index fa02762d716..0f91f7af255 100644
--- a/spec/workers/project_cache_worker_spec.rb
+++ b/spec/workers/project_cache_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectCacheWorker do
+RSpec.describe ProjectCacheWorker do
include ExclusiveLeaseHelpers
let(:worker) { described_class.new }
diff --git a/spec/workers/project_daily_statistics_worker_spec.rb b/spec/workers/project_daily_statistics_worker_spec.rb
index 8640add99e5..fa9d938acca 100644
--- a/spec/workers/project_daily_statistics_worker_spec.rb
+++ b/spec/workers/project_daily_statistics_worker_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe ProjectDailyStatisticsWorker, '#perform' do
+RSpec.describe ProjectDailyStatisticsWorker, '#perform' do
let(:worker) { described_class.new }
let(:project) { create(:project) }
diff --git a/spec/workers/project_destroy_worker_spec.rb b/spec/workers/project_destroy_worker_spec.rb
index ec40900a5b7..00a4ddac29f 100644
--- a/spec/workers/project_destroy_worker_spec.rb
+++ b/spec/workers/project_destroy_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectDestroyWorker do
+RSpec.describe ProjectDestroyWorker do
let(:project) { create(:project, :repository, pending_delete: true) }
let(:path) do
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
diff --git a/spec/workers/project_export_worker_spec.rb b/spec/workers/project_export_worker_spec.rb
index 4c49939d34e..1f54b6766a4 100644
--- a/spec/workers/project_export_worker_spec.rb
+++ b/spec/workers/project_export_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ProjectExportWorker do
+RSpec.describe ProjectExportWorker do
let!(:user) { create(:user) }
let!(:project) { create(:project) }
@@ -69,4 +69,14 @@ describe ProjectExportWorker do
end
end
end
+
+ describe 'sidekiq options' do
+ it 'disables retry' do
+ expect(described_class.sidekiq_options['retry']).to eq(false)
+ end
+
+ it 'sets default status expiration' do
+ expect(described_class.sidekiq_options['status_expiration']).to eq(StuckExportJobsWorker::EXPORT_JOBS_EXPIRATION)
+ end
+ end
end
diff --git a/spec/workers/project_service_worker_spec.rb b/spec/workers/project_service_worker_spec.rb
index 56934f122e4..c638b7472ff 100644
--- a/spec/workers/project_service_worker_spec.rb
+++ b/spec/workers/project_service_worker_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe ProjectServiceWorker, '#perform' do
+RSpec.describe ProjectServiceWorker, '#perform' do
let(:worker) { described_class.new }
let(:service) { JiraService.new }
diff --git a/spec/workers/project_update_repository_storage_worker_spec.rb b/spec/workers/project_update_repository_storage_worker_spec.rb
index 98856480b21..f75bb3d1642 100644
--- a/spec/workers/project_update_repository_storage_worker_spec.rb
+++ b/spec/workers/project_update_repository_storage_worker_spec.rb
@@ -1,9 +1,8 @@
# frozen_string_literal: true
require 'spec_helper'
-require 'securerandom'
-describe ProjectUpdateRepositoryStorageWorker do
+RSpec.describe ProjectUpdateRepositoryStorageWorker do
let(:project) { create(:project, :repository) }
subject { described_class.new }
diff --git a/spec/workers/prometheus/create_default_alerts_worker_spec.rb b/spec/workers/prometheus/create_default_alerts_worker_spec.rb
index 1b1867d5bb6..105fa0415d9 100644
--- a/spec/workers/prometheus/create_default_alerts_worker_spec.rb
+++ b/spec/workers/prometheus/create_default_alerts_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Prometheus::CreateDefaultAlertsWorker do
+RSpec.describe Prometheus::CreateDefaultAlertsWorker do
let_it_be(:project) { create(:project) }
let(:worker) { described_class.new }
let(:logger) { worker.send(:logger) }
diff --git a/spec/workers/propagate_integration_worker_spec.rb b/spec/workers/propagate_integration_worker_spec.rb
index e49869a38e9..a0fdd37b3c0 100644
--- a/spec/workers/propagate_integration_worker_spec.rb
+++ b/spec/workers/propagate_integration_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PropagateIntegrationWorker do
+RSpec.describe PropagateIntegrationWorker do
describe '#perform' do
let(:integration) do
PushoverService.create(
diff --git a/spec/workers/propagate_service_template_worker_spec.rb b/spec/workers/propagate_service_template_worker_spec.rb
index fb4ced77832..4cba313a23f 100644
--- a/spec/workers/propagate_service_template_worker_spec.rb
+++ b/spec/workers/propagate_service_template_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PropagateServiceTemplateWorker do
+RSpec.describe PropagateServiceTemplateWorker do
include ExclusiveLeaseHelpers
describe '#perform' do
diff --git a/spec/workers/prune_old_events_worker_spec.rb b/spec/workers/prune_old_events_worker_spec.rb
index 14235bde070..c1ba9128475 100644
--- a/spec/workers/prune_old_events_worker_spec.rb
+++ b/spec/workers/prune_old_events_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PruneOldEventsWorker do
+RSpec.describe PruneOldEventsWorker do
describe '#perform' do
let(:user) { create(:user) }
diff --git a/spec/workers/prune_web_hook_logs_worker_spec.rb b/spec/workers/prune_web_hook_logs_worker_spec.rb
index e57334967fd..6cd7a54ac7a 100644
--- a/spec/workers/prune_web_hook_logs_worker_spec.rb
+++ b/spec/workers/prune_web_hook_logs_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe PruneWebHookLogsWorker do
+RSpec.describe PruneWebHookLogsWorker do
describe '#perform' do
before do
hook = create(:project_hook)
diff --git a/spec/workers/reactive_caching_worker_spec.rb b/spec/workers/reactive_caching_worker_spec.rb
index dcb804a7e6e..63b26817a7a 100644
--- a/spec/workers/reactive_caching_worker_spec.rb
+++ b/spec/workers/reactive_caching_worker_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe ReactiveCachingWorker do
+RSpec.describe ReactiveCachingWorker do
it_behaves_like 'reactive cacheable worker'
end
diff --git a/spec/workers/rebase_worker_spec.rb b/spec/workers/rebase_worker_spec.rb
index 578b8cf7451..9246b283be5 100644
--- a/spec/workers/rebase_worker_spec.rb
+++ b/spec/workers/rebase_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RebaseWorker, '#perform' do
+RSpec.describe RebaseWorker, '#perform' do
include ProjectForksHelper
context 'when rebasing an MR from a fork where upstream has protected branches' do
diff --git a/spec/workers/remote_mirror_notification_worker_spec.rb b/spec/workers/remote_mirror_notification_worker_spec.rb
index f0fb46f84d9..c6fd614fdea 100644
--- a/spec/workers/remote_mirror_notification_worker_spec.rb
+++ b/spec/workers/remote_mirror_notification_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RemoteMirrorNotificationWorker, :mailer do
+RSpec.describe RemoteMirrorNotificationWorker, :mailer do
let_it_be(:project) { create(:project, :repository, :remote_mirror) }
let_it_be(:mirror) { project.remote_mirrors.first }
diff --git a/spec/workers/remove_expired_group_links_worker_spec.rb b/spec/workers/remove_expired_group_links_worker_spec.rb
index b637802cd78..91031768632 100644
--- a/spec/workers/remove_expired_group_links_worker_spec.rb
+++ b/spec/workers/remove_expired_group_links_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RemoveExpiredGroupLinksWorker do
+RSpec.describe RemoveExpiredGroupLinksWorker do
describe '#perform' do
context 'ProjectGroupLinks' do
let!(:expired_project_group_link) { create(:project_group_link, expires_at: 1.hour.ago) }
diff --git a/spec/workers/remove_expired_members_worker_spec.rb b/spec/workers/remove_expired_members_worker_spec.rb
index 69a5725bb35..cbdd5a68698 100644
--- a/spec/workers/remove_expired_members_worker_spec.rb
+++ b/spec/workers/remove_expired_members_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RemoveExpiredMembersWorker do
+RSpec.describe RemoveExpiredMembersWorker do
let(:worker) { described_class.new }
describe '#perform' do
diff --git a/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb b/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb
index 0e21933a9a5..e716d4806d3 100644
--- a/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb
+++ b/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RemoveUnreferencedLfsObjectsWorker do
+RSpec.describe RemoveUnreferencedLfsObjectsWorker do
let(:worker) { described_class.new }
describe '#perform' do
diff --git a/spec/workers/repository_check/batch_worker_spec.rb b/spec/workers/repository_check/batch_worker_spec.rb
index 12e7d1879d0..643b55af573 100644
--- a/spec/workers/repository_check/batch_worker_spec.rb
+++ b/spec/workers/repository_check/batch_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RepositoryCheck::BatchWorker do
+RSpec.describe RepositoryCheck::BatchWorker do
let(:shard_name) { 'default' }
subject { described_class.new }
diff --git a/spec/workers/repository_check/clear_worker_spec.rb b/spec/workers/repository_check/clear_worker_spec.rb
index 7ad9e287204..b5f09e8a05f 100644
--- a/spec/workers/repository_check/clear_worker_spec.rb
+++ b/spec/workers/repository_check/clear_worker_spec.rb
@@ -2,12 +2,12 @@
require 'spec_helper'
-describe RepositoryCheck::ClearWorker do
+RSpec.describe RepositoryCheck::ClearWorker do
it 'clears repository check columns' do
project = create(:project)
project.update_columns(
last_repository_check_failed: true,
- last_repository_check_at: Time.now
+ last_repository_check_at: Time.current
)
described_class.new.perform
diff --git a/spec/workers/repository_check/dispatch_worker_spec.rb b/spec/workers/repository_check/dispatch_worker_spec.rb
index e4bb0bf4046..5e1bc76ec8e 100644
--- a/spec/workers/repository_check/dispatch_worker_spec.rb
+++ b/spec/workers/repository_check/dispatch_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RepositoryCheck::DispatchWorker do
+RSpec.describe RepositoryCheck::DispatchWorker do
subject { described_class.new }
it 'does nothing when repository checks are disabled' do
diff --git a/spec/workers/repository_check/single_repository_worker_spec.rb b/spec/workers/repository_check/single_repository_worker_spec.rb
index 43998f912ef..28e3f43d374 100644
--- a/spec/workers/repository_check/single_repository_worker_spec.rb
+++ b/spec/workers/repository_check/single_repository_worker_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'fileutils'
-describe RepositoryCheck::SingleRepositoryWorker do
+RSpec.describe RepositoryCheck::SingleRepositoryWorker do
subject(:worker) { described_class.new }
it 'skips when the project has no push events' do
diff --git a/spec/workers/repository_cleanup_worker_spec.rb b/spec/workers/repository_cleanup_worker_spec.rb
index e58664cf22a..41bfeabb7f3 100644
--- a/spec/workers/repository_cleanup_worker_spec.rb
+++ b/spec/workers/repository_cleanup_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RepositoryCleanupWorker do
+RSpec.describe RepositoryCleanupWorker do
let(:project) { create(:project) }
let(:user) { create(:user) }
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index 7209c40646f..0f2d4eb4b65 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RepositoryForkWorker do
+RSpec.describe RepositoryForkWorker do
include ProjectForksHelper
describe 'modules' do
diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb
index 507098582c9..a2c19debdfd 100644
--- a/spec/workers/repository_import_worker_spec.rb
+++ b/spec/workers/repository_import_worker_spec.rb
@@ -2,13 +2,7 @@
require 'spec_helper'
-describe RepositoryImportWorker do
- describe 'modules' do
- it 'includes ProjectImportOptions' do
- expect(described_class).to include_module(ProjectImportOptions)
- end
- end
-
+RSpec.describe RepositoryImportWorker do
describe '#perform' do
let(:project) { create(:project, :import_scheduled) }
let(:import_state) { project.import_state }
diff --git a/spec/workers/repository_remove_remote_worker_spec.rb b/spec/workers/repository_remove_remote_worker_spec.rb
index afac9212fab..7d66131f34e 100644
--- a/spec/workers/repository_remove_remote_worker_spec.rb
+++ b/spec/workers/repository_remove_remote_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RepositoryRemoveRemoteWorker do
+RSpec.describe RepositoryRemoveRemoteWorker do
include ExclusiveLeaseHelpers
include GitHelpers
@@ -32,7 +32,7 @@ describe RepositoryRemoveRemoteWorker do
expect(subject)
.to receive(:log_error)
- .with('Cannot obtain an exclusive lease. There must be another instance already in execution.')
+ .with("Cannot obtain an exclusive lease for #{subject.class.name}. There must be another instance already in execution.")
subject.perform(project.id, remote_name)
end
diff --git a/spec/workers/repository_update_remote_mirror_worker_spec.rb b/spec/workers/repository_update_remote_mirror_worker_spec.rb
index 7bc499d480d..37eed57cf2e 100644
--- a/spec/workers/repository_update_remote_mirror_worker_spec.rb
+++ b/spec/workers/repository_update_remote_mirror_worker_spec.rb
@@ -2,14 +2,14 @@
require 'spec_helper'
-describe RepositoryUpdateRemoteMirrorWorker, :clean_gitlab_redis_shared_state do
+RSpec.describe RepositoryUpdateRemoteMirrorWorker, :clean_gitlab_redis_shared_state do
subject { described_class.new }
let(:remote_mirror) { create(:remote_mirror) }
- let(:scheduled_time) { Time.now - 5.minutes }
+ let(:scheduled_time) { Time.current - 5.minutes }
around do |example|
- Timecop.freeze(Time.now) { example.run }
+ Timecop.freeze(Time.current) { example.run }
end
def expect_mirror_service_to_return(mirror, result, tries = 0)
@@ -26,7 +26,7 @@ describe RepositoryUpdateRemoteMirrorWorker, :clean_gitlab_redis_shared_state do
end
it 'does not do anything if the mirror was already updated' do
- remote_mirror.update(last_update_started_at: Time.now, update_status: :finished)
+ remote_mirror.update(last_update_started_at: Time.current, update_status: :finished)
expect(Projects::UpdateRemoteMirrorService).not_to receive(:new)
@@ -48,7 +48,7 @@ describe RepositoryUpdateRemoteMirrorWorker, :clean_gitlab_redis_shared_state do
expect_next_instance_of(Projects::UpdateRemoteMirrorService) do |service|
expect(service).to receive(:execute).with(remote_mirror, 1).and_raise('Unexpected!')
end
- expect { subject.perform(remote_mirror.id, Time.now, 1) }.to raise_error('Unexpected!')
+ expect { subject.perform(remote_mirror.id, Time.current, 1) }.to raise_error('Unexpected!')
lease = Gitlab::ExclusiveLease.new("#{described_class.name}:#{remote_mirror.id}", timeout: 1.second)
diff --git a/spec/workers/run_pipeline_schedule_worker_spec.rb b/spec/workers/run_pipeline_schedule_worker_spec.rb
index afeee4bac73..4999909934b 100644
--- a/spec/workers/run_pipeline_schedule_worker_spec.rb
+++ b/spec/workers/run_pipeline_schedule_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe RunPipelineScheduleWorker do
+RSpec.describe RunPipelineScheduleWorker do
describe '#perform' do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
diff --git a/spec/workers/schedule_migrate_external_diffs_worker_spec.rb b/spec/workers/schedule_migrate_external_diffs_worker_spec.rb
index 9d6fecc9f4e..09a0124f6e0 100644
--- a/spec/workers/schedule_migrate_external_diffs_worker_spec.rb
+++ b/spec/workers/schedule_migrate_external_diffs_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ScheduleMigrateExternalDiffsWorker do
+RSpec.describe ScheduleMigrateExternalDiffsWorker do
include ExclusiveLeaseHelpers
let(:worker) { described_class.new }
diff --git a/spec/workers/self_monitoring_project_create_worker_spec.rb b/spec/workers/self_monitoring_project_create_worker_spec.rb
index 00c288bdc46..b618b8ede99 100644
--- a/spec/workers/self_monitoring_project_create_worker_spec.rb
+++ b/spec/workers/self_monitoring_project_create_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SelfMonitoringProjectCreateWorker do
+RSpec.describe SelfMonitoringProjectCreateWorker do
describe '#perform' do
let(:service_class) { Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService }
let(:service) { instance_double(service_class) }
diff --git a/spec/workers/self_monitoring_project_delete_worker_spec.rb b/spec/workers/self_monitoring_project_delete_worker_spec.rb
index 3685c73513e..9a53fe59a40 100644
--- a/spec/workers/self_monitoring_project_delete_worker_spec.rb
+++ b/spec/workers/self_monitoring_project_delete_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SelfMonitoringProjectDeleteWorker do
+RSpec.describe SelfMonitoringProjectDeleteWorker do
let_it_be(:jid) { 'b5b28910d97563e58c2fe55f' }
let_it_be(:data_key) { "self_monitoring_delete_result:#{jid}" }
diff --git a/spec/workers/service_desk_email_receiver_worker_spec.rb b/spec/workers/service_desk_email_receiver_worker_spec.rb
new file mode 100644
index 00000000000..d3bfa51348e
--- /dev/null
+++ b/spec/workers/service_desk_email_receiver_worker_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe ServiceDeskEmailReceiverWorker, :mailer do
+ describe '#perform' do
+ let(:worker) { described_class.new }
+ let(:email) { fixture_file('emails/service_desk_custom_address.eml') }
+
+ context 'when service_desk_email config is enabled' do
+ before do
+ stub_service_desk_email_setting(enabled: true, address: 'foo')
+ end
+
+ it 'does not ignore the email' do
+ expect(Gitlab::Email::ServiceDeskReceiver).to receive(:new)
+
+ worker.perform(email)
+ end
+
+ context 'when service desk receiver raises an exception' do
+ before do
+ allow_next_instance_of(Gitlab::Email::ServiceDeskReceiver) do |receiver|
+ allow(receiver).to receive(:find_handler).and_return(nil)
+ end
+ end
+
+ it 'sends a rejection email' do
+ perform_enqueued_jobs do
+ worker.perform(email)
+ end
+
+ reply = ActionMailer::Base.deliveries.last
+ expect(reply).not_to be_nil
+ expect(reply.to).to eq(['jake@adventuretime.ooo'])
+ expect(reply.subject).to include('Rejected')
+ end
+ end
+ end
+
+ context 'when service_desk_email config is disabled' do
+ before do
+ stub_service_desk_email_setting(enabled: false, address: 'foo')
+ end
+
+ it 'ignores the email' do
+ expect(Gitlab::Email::ServiceDeskReceiver).not_to receive(:new)
+
+ worker.perform(email)
+ end
+ end
+ end
+end
diff --git a/spec/workers/stage_update_worker_spec.rb b/spec/workers/stage_update_worker_spec.rb
index dc7158cfd2f..75b324a9e0a 100644
--- a/spec/workers/stage_update_worker_spec.rb
+++ b/spec/workers/stage_update_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe StageUpdateWorker do
+RSpec.describe StageUpdateWorker do
describe '#perform' do
context 'when stage exists' do
let(:stage) { create(:ci_stage_entity) }
diff --git a/spec/workers/stuck_ci_jobs_worker_spec.rb b/spec/workers/stuck_ci_jobs_worker_spec.rb
index c994a5dcb78..b96d506771d 100644
--- a/spec/workers/stuck_ci_jobs_worker_spec.rb
+++ b/spec/workers/stuck_ci_jobs_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe StuckCiJobsWorker do
+RSpec.describe StuckCiJobsWorker do
include ExclusiveLeaseHelpers
let!(:runner) { create :ci_runner }
diff --git a/spec/workers/stuck_export_jobs_worker_spec.rb b/spec/workers/stuck_export_jobs_worker_spec.rb
index fc5758fdadf..cbc7adc8e3f 100644
--- a/spec/workers/stuck_export_jobs_worker_spec.rb
+++ b/spec/workers/stuck_export_jobs_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe StuckExportJobsWorker do
+RSpec.describe StuckExportJobsWorker do
let(:worker) { described_class.new }
shared_examples 'project export job detection' do
diff --git a/spec/workers/stuck_import_jobs_worker_spec.rb b/spec/workers/stuck_import_jobs_worker_spec.rb
deleted file mode 100644
index f8d7f8747d5..00000000000
--- a/spec/workers/stuck_import_jobs_worker_spec.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe StuckImportJobsWorker do
- let(:worker) { described_class.new }
-
- describe 'with scheduled import_status' do
- it_behaves_like 'stuck import job detection' do
- let(:import_state) { create(:project, :import_scheduled).import_state }
-
- before do
- import_state.update(jid: '123')
- end
- end
- end
-
- describe 'with started import_status' do
- it_behaves_like 'stuck import job detection' do
- let(:import_state) { create(:project, :import_started).import_state }
-
- before do
- import_state.update(jid: '123')
- end
- end
- end
-end
diff --git a/spec/workers/stuck_merge_jobs_worker_spec.rb b/spec/workers/stuck_merge_jobs_worker_spec.rb
index 8ceaf1fc555..bade2e1ca1b 100644
--- a/spec/workers/stuck_merge_jobs_worker_spec.rb
+++ b/spec/workers/stuck_merge_jobs_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe StuckMergeJobsWorker do
+RSpec.describe StuckMergeJobsWorker do
describe 'perform' do
let(:worker) { described_class.new }
diff --git a/spec/workers/system_hook_push_worker_spec.rb b/spec/workers/system_hook_push_worker_spec.rb
index 890a622d11a..43a3f8e3e19 100644
--- a/spec/workers/system_hook_push_worker_spec.rb
+++ b/spec/workers/system_hook_push_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe SystemHookPushWorker do
+RSpec.describe SystemHookPushWorker do
include RepoHelpers
subject { described_class.new }
diff --git a/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb b/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb
index 0907e2768ba..86202fac1ed 100644
--- a/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb
+++ b/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TodosDestroyer::ConfidentialIssueWorker do
+RSpec.describe TodosDestroyer::ConfidentialIssueWorker do
let(:service) { double }
it "calls the Todos::Destroy::ConfidentialIssueService with issue_id parameter" do
diff --git a/spec/workers/todos_destroyer/entity_leave_worker_spec.rb b/spec/workers/todos_destroyer/entity_leave_worker_spec.rb
index cb14fac0910..db3b0252056 100644
--- a/spec/workers/todos_destroyer/entity_leave_worker_spec.rb
+++ b/spec/workers/todos_destroyer/entity_leave_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TodosDestroyer::EntityLeaveWorker do
+RSpec.describe TodosDestroyer::EntityLeaveWorker do
it "calls the Todos::Destroy::EntityLeaveService with the params it was given" do
service = double
diff --git a/spec/workers/todos_destroyer/group_private_worker_spec.rb b/spec/workers/todos_destroyer/group_private_worker_spec.rb
index d9a240136d5..4903edd4bbe 100644
--- a/spec/workers/todos_destroyer/group_private_worker_spec.rb
+++ b/spec/workers/todos_destroyer/group_private_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TodosDestroyer::GroupPrivateWorker do
+RSpec.describe TodosDestroyer::GroupPrivateWorker do
it "calls the Todos::Destroy::GroupPrivateService with the params it was given" do
service = double
diff --git a/spec/workers/todos_destroyer/private_features_worker_spec.rb b/spec/workers/todos_destroyer/private_features_worker_spec.rb
index abd04acc3bd..f346a004670 100644
--- a/spec/workers/todos_destroyer/private_features_worker_spec.rb
+++ b/spec/workers/todos_destroyer/private_features_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TodosDestroyer::PrivateFeaturesWorker do
+RSpec.describe TodosDestroyer::PrivateFeaturesWorker do
it "calls the Todos::Destroy::PrivateFeaturesService with the params it was given" do
service = double
diff --git a/spec/workers/todos_destroyer/project_private_worker_spec.rb b/spec/workers/todos_destroyer/project_private_worker_spec.rb
index c1bb0438ec3..4e54fbdb275 100644
--- a/spec/workers/todos_destroyer/project_private_worker_spec.rb
+++ b/spec/workers/todos_destroyer/project_private_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TodosDestroyer::ProjectPrivateWorker do
+RSpec.describe TodosDestroyer::ProjectPrivateWorker do
it "calls the Todos::Destroy::ProjectPrivateService with the params it was given" do
service = double
diff --git a/spec/workers/trending_projects_worker_spec.rb b/spec/workers/trending_projects_worker_spec.rb
index 6e524085662..1f1e312e457 100644
--- a/spec/workers/trending_projects_worker_spec.rb
+++ b/spec/workers/trending_projects_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe TrendingProjectsWorker do
+RSpec.describe TrendingProjectsWorker do
describe '#perform' do
it 'refreshes the trending projects' do
expect(TrendingProject).to receive(:refresh!)
diff --git a/spec/workers/update_container_registry_info_worker_spec.rb b/spec/workers/update_container_registry_info_worker_spec.rb
new file mode 100644
index 00000000000..ace9e55cbce
--- /dev/null
+++ b/spec/workers/update_container_registry_info_worker_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe UpdateContainerRegistryInfoWorker do
+ describe '#perform' do
+ it 'calls UpdateContainerRegistryInfoService' do
+ expect_next_instance_of(UpdateContainerRegistryInfoService) do |service|
+ expect(service).to receive(:execute)
+ end
+
+ subject.perform
+ end
+ end
+end
diff --git a/spec/workers/update_external_pull_requests_worker_spec.rb b/spec/workers/update_external_pull_requests_worker_spec.rb
index afac0357b2d..80f22470977 100644
--- a/spec/workers/update_external_pull_requests_worker_spec.rb
+++ b/spec/workers/update_external_pull_requests_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UpdateExternalPullRequestsWorker do
+RSpec.describe UpdateExternalPullRequestsWorker do
describe '#perform' do
let_it_be(:project) { create(:project, import_source: 'tanuki/repository') }
let_it_be(:user) { create(:user) }
diff --git a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
index 8fe3f27c8b1..e6f4f415987 100644
--- a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
+++ b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UpdateHeadPipelineForMergeRequestWorker do
+RSpec.describe UpdateHeadPipelineForMergeRequestWorker do
describe '#perform' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/workers/update_highest_role_worker_spec.rb b/spec/workers/update_highest_role_worker_spec.rb
index 3f377208a62..19512fb0cfc 100644
--- a/spec/workers/update_highest_role_worker_spec.rb
+++ b/spec/workers/update_highest_role_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UpdateHighestRoleWorker, :clean_gitlab_redis_shared_state do
+RSpec.describe UpdateHighestRoleWorker, :clean_gitlab_redis_shared_state do
include ExclusiveLeaseHelpers
let(:worker) { described_class.new }
diff --git a/spec/workers/update_merge_requests_worker_spec.rb b/spec/workers/update_merge_requests_worker_spec.rb
index 14a363f9d59..fb12086c2f4 100644
--- a/spec/workers/update_merge_requests_worker_spec.rb
+++ b/spec/workers/update_merge_requests_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UpdateMergeRequestsWorker do
+RSpec.describe UpdateMergeRequestsWorker do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/workers/update_project_statistics_worker_spec.rb b/spec/workers/update_project_statistics_worker_spec.rb
index 191075f1a52..1f840e363ea 100644
--- a/spec/workers/update_project_statistics_worker_spec.rb
+++ b/spec/workers/update_project_statistics_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UpdateProjectStatisticsWorker do
+RSpec.describe UpdateProjectStatisticsWorker do
let(:worker) { described_class.new }
let(:project) { create(:project, :repository) }
let(:statistics) { %w(repository_size) }
diff --git a/spec/workers/upload_checksum_worker_spec.rb b/spec/workers/upload_checksum_worker_spec.rb
index 44e9e4f048d..75d7509b6e6 100644
--- a/spec/workers/upload_checksum_worker_spec.rb
+++ b/spec/workers/upload_checksum_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe UploadChecksumWorker do
+RSpec.describe UploadChecksumWorker do
describe '#perform' do
subject { described_class.new }
diff --git a/spec/workers/users/create_statistics_worker_spec.rb b/spec/workers/users/create_statistics_worker_spec.rb
index 3b2b72a832d..e3f082313a0 100644
--- a/spec/workers/users/create_statistics_worker_spec.rb
+++ b/spec/workers/users/create_statistics_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Users::CreateStatisticsWorker do
+RSpec.describe Users::CreateStatisticsWorker do
describe '#perform' do
subject { described_class.new.perform }
diff --git a/spec/workers/wait_for_cluster_creation_worker_spec.rb b/spec/workers/wait_for_cluster_creation_worker_spec.rb
index b21a9b612af..9079dff1afe 100644
--- a/spec/workers/wait_for_cluster_creation_worker_spec.rb
+++ b/spec/workers/wait_for_cluster_creation_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe WaitForClusterCreationWorker do
+RSpec.describe WaitForClusterCreationWorker do
describe '#perform' do
context 'when provider type is gcp' do
let(:cluster) { create(:cluster, provider_type: :gcp, provider_gcp: provider) }
diff --git a/spec/workers/x509_certificate_revoke_worker_spec.rb b/spec/workers/x509_certificate_revoke_worker_spec.rb
index 1e0cbf61267..392cb52d084 100644
--- a/spec/workers/x509_certificate_revoke_worker_spec.rb
+++ b/spec/workers/x509_certificate_revoke_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe X509CertificateRevokeWorker do
+RSpec.describe X509CertificateRevokeWorker do
describe '#perform' do
context 'with a revoked certificate' do
subject { described_class.new }
diff --git a/spec/workers/x509_issuer_crl_check_worker_spec.rb b/spec/workers/x509_issuer_crl_check_worker_spec.rb
index f052812b86b..5564147d274 100644
--- a/spec/workers/x509_issuer_crl_check_worker_spec.rb
+++ b/spec/workers/x509_issuer_crl_check_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe X509IssuerCrlCheckWorker do
+RSpec.describe X509IssuerCrlCheckWorker do
subject(:worker) { described_class.new }
let(:project) { create(:project, :public, :repository) }