From 9dc93a4519d9d5d7be48ff274127136236a3adb3 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 20 Apr 2021 23:50:22 +0000 Subject: Add latest changes from gitlab-org/gitlab@13-11-stable-ee --- spec/benchmarks/banzai_benchmark.rb | 7 +- spec/config/mail_room_spec.rb | 37 +- .../metrics/aggregates/aggregated_metrics_spec.rb | 95 ++ .../admin/application_settings_controller_spec.rb | 9 +- spec/controllers/admin/clusters_controller_spec.rb | 14 +- .../admin/dev_ops_report_controller_spec.rb | 8 +- spec/controllers/admin/groups_controller_spec.rb | 6 + .../admin/impersonations_controller_spec.rb | 2 +- spec/controllers/admin/runners_controller_spec.rb | 4 +- spec/controllers/admin/services_controller_spec.rb | 2 +- spec/controllers/application_controller_spec.rb | 4 +- spec/controllers/boards/issues_controller_spec.rb | 2 +- spec/controllers/chaos_controller_spec.rb | 22 +- .../concerns/enforces_admin_authentication_spec.rb | 6 +- spec/controllers/concerns/redis_tracking_spec.rb | 32 +- spec/controllers/concerns/renders_commits_spec.rb | 12 + .../dashboard/snippets_controller_spec.rb | 19 +- .../explore/snippets_controller_spec.rb | 4 + spec/controllers/graphql_controller_spec.rb | 32 +- spec/controllers/groups/boards_controller_spec.rb | 18 + .../controllers/groups/clusters_controller_spec.rb | 14 +- .../dependency_proxy_auth_controller_spec.rb | 2 + ...endency_proxy_for_containers_controller_spec.rb | 3 + .../groups/group_links_controller_spec.rb | 9 +- .../groups/group_members_controller_spec.rb | 24 +- spec/controllers/groups/labels_controller_spec.rb | 18 + .../groups/milestones_controller_spec.rb | 10 +- .../registry/repositories_controller_spec.rb | 1 + spec/controllers/groups/runners_controller_spec.rb | 16 +- .../settings/applications_controller_spec.rb | 219 +++++ spec/controllers/groups/uploads_controller_spec.rb | 2 +- .../groups/variables_controller_spec.rb | 1 + spec/controllers/groups_controller_spec.rb | 113 ++- spec/controllers/invites_controller_spec.rb | 81 +- .../oauth/authorizations_controller_spec.rb | 4 +- .../omniauth_callbacks_controller_spec.rb | 8 +- .../profiles/notifications_controller_spec.rb | 50 +- .../alerting/notifications_controller_spec.rb | 2 + .../projects/artifacts_controller_spec.rb | 6 +- .../controllers/projects/boards_controller_spec.rb | 18 + .../projects/clusters_controller_spec.rb | 14 +- .../controllers/projects/commit_controller_spec.rb | 98 ++ .../cycle_analytics/events_controller_spec.rb | 2 +- .../designs/raw_images_controller_spec.rb | 12 +- .../designs/resized_image_controller_spec.rb | 1 + .../projects/discussions_controller_spec.rb | 4 +- .../projects/environments_controller_spec.rb | 1 + .../projects/feature_flags_controller_spec.rb | 1 + spec/controllers/projects/forks_controller_spec.rb | 45 +- .../projects/group_links_controller_spec.rb | 11 +- .../projects/imports_controller_spec.rb | 8 +- .../projects/incidents_controller_spec.rb | 1 + .../controllers/projects/issues_controller_spec.rb | 68 +- spec/controllers/projects/jobs_controller_spec.rb | 1 + .../controllers/projects/labels_controller_spec.rb | 26 +- .../merge_requests/content_controller_spec.rb | 19 +- .../merge_requests/creations_controller_spec.rb | 32 + .../merge_requests/drafts_controller_spec.rb | 2 +- .../projects/merge_requests_controller_spec.rb | 49 +- .../projects/milestones_controller_spec.rb | 12 +- spec/controllers/projects/notes_controller_spec.rb | 6 +- .../dashboards_controller_spec.rb | 1 + .../projects/pipelines_controller_spec.rb | 64 +- .../projects/pipelines_settings_controller_spec.rb | 1 + .../projects/project_members_controller_spec.rb | 8 +- spec/controllers/projects/raw_controller_spec.rb | 1 + .../registry/repositories_controller_spec.rb | 58 +- .../projects/registry/tags_controller_spec.rb | 7 +- .../projects/releases/evidences_controller_spec.rb | 3 +- .../projects/releases_controller_spec.rb | 1 + .../projects/repositories_controller_spec.rb | 22 - .../projects/runners_controller_spec.rb | 4 +- .../projects/services_controller_spec.rb | 34 +- .../settings/access_tokens_controller_spec.rb | 23 +- .../projects/settings/ci_cd_controller_spec.rb | 1 + .../settings/operations_controller_spec.rb | 1 + .../projects/snippets_controller_spec.rb | 4 + .../projects/starrers_controller_spec.rb | 2 +- .../projects/static_site_editor_controller_spec.rb | 1 + spec/controllers/projects/todos_controller_spec.rb | 1 + .../projects/uploads_controller_spec.rb | 2 +- spec/controllers/projects_controller_spec.rb | 30 +- .../registrations/welcome_controller_spec.rb | 22 +- spec/controllers/root_controller_spec.rb | 20 - spec/controllers/sessions_controller_spec.rb | 14 +- spec/db/schema_spec.rb | 7 +- spec/deprecation_toolkit_env.rb | 8 +- spec/experiments/application_experiment_spec.rb | 2 +- .../members/invite_email_experiment_spec.rb | 69 +- .../new_project_readme_experiment_spec.rb | 4 - spec/experiments/strategy/round_robin_spec.rb | 68 -- spec/factories/atlassian_identities.rb | 2 +- spec/factories/bulk_import/trackers.rb | 14 +- spec/factories/ci/builds.rb | 15 + spec/factories/ci/pipeline_artifacts.rb | 16 + .../ci/reports/codequality_degradations.rb | 43 + spec/factories/ci/test_case.rb | 8 - spec/factories/ci/test_case_failure.rb | 9 - spec/factories/ci/unit_test.rb | 10 + spec/factories/ci/unit_test_failure.rb | 9 + spec/factories/clusters/applications/helm.rb | 79 +- spec/factories/clusters/integrations/prometheus.rb | 12 + spec/factories/draft_note.rb | 17 +- spec/factories/events.rb | 11 +- spec/factories/git_wiki_commit_details.rb | 2 +- spec/factories/gitaly/commit.rb | 4 +- .../background_migration/batched_migrations.rb | 1 + spec/factories/group_group_links.rb | 4 +- spec/factories/import_export_uploads.rb | 2 +- spec/factories/packages.rb | 8 +- spec/factories/packages/package_file.rb | 26 + spec/factories/sequences.rb | 1 + spec/factories/services.rb | 8 +- spec/factories/subscriptions.rb | 6 + spec/factories/timelogs.rb | 19 +- spec/factories/users/in_product_marketing_email.rb | 10 + spec/factories_spec.rb | 14 +- spec/features/admin/admin_abuse_reports_spec.rb | 2 +- spec/features/admin/admin_appearance_spec.rb | 2 +- spec/features/admin/admin_groups_spec.rb | 45 + spec/features/admin/admin_labels_spec.rb | 6 +- spec/features/admin/admin_mode/login_spec.rb | 2 +- spec/features/admin/admin_mode/logout_spec.rb | 2 + spec/features/admin/admin_mode_spec.rb | 8 +- spec/features/admin/admin_runners_spec.rb | 30 +- spec/features/admin/admin_search_settings_spec.rb | 6 +- .../admin/admin_sees_project_statistics_spec.rb | 2 +- .../admin/admin_sees_projects_statistics_spec.rb | 2 +- spec/features/admin/admin_settings_spec.rb | 18 +- .../admin/admin_users_impersonation_tokens_spec.rb | 2 +- .../services/admin_activates_prometheus_spec.rb | 24 - .../admin_visits_service_templates_spec.rb | 44 +- .../user_views_alerts_settings_spec.rb | 2 +- spec/features/boards/add_issues_modal_spec.rb | 270 ------ spec/features/boards/boards_spec.rb | 94 +- spec/features/boards/focus_mode_spec.rb | 4 +- spec/features/boards/modal_filter_spec.rb | 228 ----- spec/features/boards/multi_select_spec.rb | 4 + spec/features/boards/multiple_boards_spec.rb | 1 + spec/features/boards/new_issue_spec.rb | 34 +- .../boards/reload_boards_on_browser_back_spec.rb | 4 +- spec/features/boards/sidebar_assignee_spec.rb | 122 +++ spec/features/boards/sidebar_due_date_spec.rb | 46 + spec/features/boards/sidebar_labels_spec.rb | 166 ++++ spec/features/boards/sidebar_milestones_spec.rb | 65 ++ spec/features/boards/sidebar_spec.rb | 403 +-------- spec/features/boards/sub_group_project_spec.rb | 3 +- .../boards/user_adds_lists_to_board_spec.rb | 11 +- spec/features/calendar_spec.rb | 4 +- .../callouts/service_templates_deprecation_spec.rb | 59 ++ spec/features/clusters/cluster_detail_page_spec.rb | 6 +- .../clusters/cluster_health_dashboard_spec.rb | 2 +- spec/features/commits_spec.rb | 2 +- spec/features/dashboard/active_tab_spec.rb | 2 + .../dashboard/datetime_on_tooltips_spec.rb | 2 +- ...ard_with_external_authorization_service_spec.rb | 2 + spec/features/dashboard/issuables_counter_spec.rb | 4 +- spec/features/dashboard/milestones_spec.rb | 2 +- .../project_member_activity_index_spec.rb | 2 +- spec/features/dashboard/projects_spec.rb | 23 +- spec/features/dashboard/shortcuts_spec.rb | 6 +- .../discussion_comments/merge_request_spec.rb | 2 - spec/features/error_pages_spec.rb | 9 +- .../user_filters_errors_by_status_spec.rb | 1 + .../user_searches_sentry_errors_spec.rb | 1 + .../error_tracking/user_sees_error_index_spec.rb | 3 +- spec/features/file_uploads/attachment_spec.rb | 44 + spec/features/file_uploads/maven_package_spec.rb | 21 +- spec/features/file_uploads/nuget_package_spec.rb | 4 +- spec/features/file_uploads/rubygem_package_spec.rb | 45 + .../frequently_visited_projects_and_groups_spec.rb | 2 + spec/features/gitlab_experiments_spec.rb | 44 + spec/features/groups/board_spec.rb | 2 +- spec/features/groups/clusters/user_spec.rb | 2 +- ...age_with_external_authorization_service_spec.rb | 4 +- spec/features/groups/group_settings_spec.rb | 2 +- spec/features/groups/issues_spec.rb | 2 +- spec/features/groups/labels/index_spec.rb | 2 +- ...master_adds_member_with_expiration_date_spec.rb | 1 + .../features/groups/members/request_access_spec.rb | 2 +- spec/features/groups/merge_requests_spec.rb | 2 +- spec/features/groups/milestone_spec.rb | 6 +- spec/features/groups/navbar_spec.rb | 6 +- .../settings/user_searches_in_settings_spec.rb | 23 +- spec/features/groups_spec.rb | 66 ++ spec/features/ide/clientside_preview_csp_spec.rb | 10 +- spec/features/import/manifest_import_spec.rb | 11 +- spec/features/invites_spec.rb | 17 +- .../features/issues/bulk_assignment_labels_spec.rb | 4 +- ..._issue_for_discussions_in_merge_request_spec.rb | 6 +- ..._for_single_discussion_in_merge_request_spec.rb | 6 +- spec/features/issues/form_spec.rb | 2 +- spec/features/issues/gfm_autocomplete_spec.rb | 984 ++++++++++----------- spec/features/issues/issue_sidebar_spec.rb | 215 ++++- spec/features/issues/markdown_toolbar_spec.rb | 28 +- spec/features/issues/note_polling_spec.rb | 2 +- spec/features/issues/spam_issues_spec.rb | 22 +- .../features/issues/user_comments_on_issue_spec.rb | 18 +- .../user_creates_branch_and_merge_request_spec.rb | 2 +- ...user_creates_confidential_merge_request_spec.rb | 2 +- spec/features/issues/user_creates_issue_spec.rb | 10 +- spec/features/issues/user_edits_issue_spec.rb | 198 +++-- spec/features/issues/user_filters_issues_spec.rb | 2 +- .../issues/user_interacts_with_awards_spec.rb | 4 + .../issues/user_invites_from_a_comment_spec.rb | 25 + spec/features/issues/user_sees_live_update_spec.rb | 2 +- .../user_sees_sidebar_updates_in_realtime_spec.rb | 5 +- spec/features/issues/user_sorts_issues_spec.rb | 46 +- spec/features/markdown/markdown_spec.rb | 11 +- spec/features/merge_request/batch_comments_spec.rb | 120 ++- ...ows_commits_from_memebers_who_can_merge_spec.rb | 2 +- .../merge_request/user_awards_emoji_spec.rb | 29 +- .../user_creates_merge_request_spec.rb | 6 +- .../user_invites_from_a_comment_spec.rb | 25 + .../merge_request/user_posts_notes_spec.rb | 8 +- ...lves_diff_notes_and_discussions_resolve_spec.rb | 32 +- .../user_sees_breadcrumb_links_spec.rb | 2 +- .../user_sees_merge_request_pipelines_spec.rb | 38 +- .../merge_request/user_sees_merge_widget_spec.rb | 2 +- .../user_sees_mini_pipeline_graph_spec.rb | 6 +- .../user_sees_notes_from_forked_project_spec.rb | 4 +- .../merge_request/user_sees_pipelines_spec.rb | 12 +- .../user_selects_branches_for_new_mr_spec.rb | 4 +- .../user_squashes_merge_request_spec.rb | 4 +- .../user_views_open_merge_request_spec.rb | 2 +- .../milestones/user_creates_milestone_spec.rb | 2 +- .../milestones/user_views_milestone_spec.rb | 4 +- .../milestones/user_views_milestones_spec.rb | 3 +- spec/features/participants_autocomplete_spec.rb | 44 +- spec/features/profiles/emails_spec.rb | 8 +- spec/features/profiles/password_spec.rb | 2 +- .../profiles/personal_access_tokens_spec.rb | 2 +- spec/features/profiles/user_edit_profile_spec.rb | 8 +- .../features/profiles/user_search_settings_spec.rb | 6 +- spec/features/projects/active_tabs_spec.rb | 2 +- .../blobs/blob_line_permalink_updater_spec.rb | 22 - .../user_views_pipeline_editor_button_spec.rb | 42 + .../projects/branches/user_creates_branch_spec.rb | 4 +- .../projects/branches/user_deletes_branch_spec.rb | 9 +- spec/features/projects/branches_spec.rb | 29 +- spec/features/projects/clusters/gcp_spec.rb | 2 +- spec/features/projects/clusters/user_spec.rb | 2 +- spec/features/projects/clusters_spec.rb | 4 +- spec/features/projects/commit/cherry_pick_spec.rb | 13 +- .../commit/comments/user_edits_comments_spec.rb | 2 +- .../projects/commit/mini_pipeline_graph_spec.rb | 50 +- .../projects/commit/user_reverts_commit_spec.rb | 17 +- .../projects/commits/user_browses_commits_spec.rb | 9 +- spec/features/projects/features_visibility_spec.rb | 2 +- .../projects/files/gitlab_ci_yml_dropdown_spec.rb | 42 +- .../projects/files/user_creates_directory_spec.rb | 2 +- .../projects/files/user_uploads_files_spec.rb | 35 +- spec/features/projects/fork_spec.rb | 48 +- spec/features/projects/jobs/permissions_spec.rb | 2 + .../projects/jobs/user_browses_jobs_spec.rb | 9 +- spec/features/projects/jobs_spec.rb | 5 +- .../projects/labels/user_removes_labels_spec.rb | 10 +- spec/features/projects/members/list_spec.rb | 2 +- .../projects/members/user_requests_access_spec.rb | 2 +- .../features/projects/merge_request_button_spec.rb | 4 +- spec/features/projects/navbar_spec.rb | 4 +- spec/features/projects/new_project_spec.rb | 66 ++ .../projects/pages/user_edits_settings_spec.rb | 1 - spec/features/projects/pipelines/pipeline_spec.rb | 10 +- spec/features/projects/pipelines/pipelines_spec.rb | 89 +- .../releases/user_views_edit_release_spec.rb | 2 +- .../projects/releases/user_views_release_spec.rb | 43 +- spec/features/projects/remote_mirror_spec.rb | 4 +- .../projects/services/disable_triggers_spec.rb | 4 +- .../projects/services/user_activates_asana_spec.rb | 2 +- .../user_activates_atlassian_bamboo_ci_spec.rb | 11 +- .../services/user_activates_hipchat_spec.rb | 40 - .../user_activates_jetbrains_teamcity_ci_spec.rb | 6 +- .../projects/services/user_activates_jira_spec.rb | 71 +- ...user_activates_mattermost_slash_command_spec.rb | 2 +- .../services/user_activates_pushover_spec.rb | 2 +- .../user_activates_slack_notifications_spec.rb | 2 +- .../projects/services/user_views_services_spec.rb | 4 +- .../projects/settings/access_tokens_spec.rb | 106 ++- .../settings/forked_project_settings_spec.rb | 5 +- .../projects/settings/operations_settings_spec.rb | 2 +- .../projects/settings/registry_settings_spec.rb | 4 +- .../user_manages_merge_requests_settings_spec.rb | 34 + .../settings/user_searches_in_settings_spec.rb | 44 +- .../user_sees_revoke_deploy_token_modal_spec.rb | 8 +- .../user_sees_deletion_failure_message_spec.rb | 2 +- .../show/user_sees_setup_shortcut_buttons_spec.rb | 5 +- .../projects/show/user_uploads_files_spec.rb | 12 +- .../snippets/user_comments_on_snippet_spec.rb | 1 - spec/features/projects/sub_group_issuables_spec.rb | 10 +- spec/features/projects/user_sees_sidebar_spec.rb | 2 +- .../projects/user_sees_user_popover_spec.rb | 2 +- spec/features/projects/user_uses_shortcuts_spec.rb | 2 +- spec/features/projects_spec.rb | 20 - spec/features/protected_branches_spec.rb | 12 +- spec/features/registrations/welcome_spec.rb | 21 + spec/features/runners_spec.rb | 16 +- .../search/user_uses_header_search_field_spec.rb | 2 +- .../security/project/internal_access_spec.rb | 12 +- .../security/project/private_access_spec.rb | 14 +- .../security/project/public_access_spec.rb | 12 +- .../snippets/notes_on_personal_snippets_spec.rb | 3 - spec/features/users/anonymous_sessions_spec.rb | 2 +- spec/features/users/login_spec.rb | 6 +- spec/features/users/show_spec.rb | 6 +- spec/features/users/terms_spec.rb | 2 +- spec/features/whats_new_spec.rb | 62 +- spec/finders/applications_finder_spec.rb | 40 +- spec/finders/ci/variables_finder_spec.rb | 65 +- .../concerns/finder_with_group_hierarchy_spec.rb | 112 +++ .../concerns/packages/finder_helper_spec.rb | 209 +++-- .../design_management/designs_finder_spec.rb | 1 + .../design_management/versions_finder_spec.rb | 1 + .../environments_by_deployments_finder_spec.rb | 127 +++ spec/finders/environments_finder_spec.rb | 126 +-- spec/finders/group_members_finder_spec.rb | 282 +++--- spec/finders/issues_finder_spec.rb | 18 + .../oldest_per_commit_finder_spec.rb | 39 + spec/finders/merge_requests_finder_spec.rb | 12 + .../metrics/dashboards/annotations_finder_spec.rb | 1 + .../users_starred_dashboards_finder_spec.rb | 1 + spec/finders/notes_finder_spec.rb | 18 + spec/finders/packages/go/package_finder_spec.rb | 71 ++ spec/finders/packages/maven/package_finder_spec.rb | 157 +++- spec/finders/pending_todos_finder_spec.rb | 12 +- spec/finders/projects_finder_spec.rb | 6 + .../repositories/branch_names_finder_spec.rb | 25 + .../repositories/changelog_tag_finder_spec.rb | 54 ++ .../repositories/previous_tag_finder_spec.rb | 45 - ...user_group_notification_settings_finder_spec.rb | 33 + spec/fixtures/api/schemas/entities/member.json | 2 + .../fixtures/api/schemas/entities/member_user.json | 3 +- spec/fixtures/api/schemas/external_validation.json | 15 +- .../graphql/packages/package_conan_metadata.json | 37 + .../schemas/graphql/packages/package_details.json | 36 +- .../api/schemas/public_api/v4/user/public.json | 3 +- spec/fixtures/ce_sample_schema.json | 0 .../fixtures/config/mail_room_enabled_ms_graph.yml | 26 + spec/fixtures/emails/update_commands_only.eml | 22 + .../sample_metric.yml | 21 + .../sample_metric_with_name_suggestions.yml | 22 + .../lib/gitlab/performance_bar/peek_data.json | 2 + spec/fixtures/packages/rubygems/package-0.0.1.gem | Bin 4096 -> 4608 bytes spec/fixtures/packages/rubygems/package.gem | Bin 0 -> 4608 bytes spec/fixtures/packages/rubygems/package.gemspec | 47 +- .../security_reports/master/gl-sast-report.json | 945 ++------------------ spec/fixtures/unsafe_javascript.xml | 10 + .../frontend/__helpers__/experimentation_helper.js | 13 + spec/frontend/__helpers__/mock_apollo_helper.js | 12 +- spec/frontend/__helpers__/vue_test_utils_helper.js | 73 +- .../__helpers__/vue_test_utils_helper_spec.js | 208 ++++- spec/frontend/__helpers__/web_worker_fake.js | 71 ++ spec/frontend/__helpers__/web_worker_mock.js | 10 - .../frontend/__helpers__/web_worker_transformer.js | 18 + spec/frontend/__mocks__/vue/index.js | 7 + spec/frontend/access_tokens/index_spec.js | 20 +- .../components/signup_checkbox_spec.js | 66 ++ .../components/signup_form_spec.js | 331 +++++++ .../admin/signup_restrictions/mock_data.js | 41 + spec/frontend/admin/signup_restrictions/utils.js | 19 + .../admin/signup_restrictions/utils_spec.js | 22 + .../admin/users/components/user_date_spec.js | 2 +- .../admin/users/components/users_table_spec.js | 2 +- spec/frontend/admin/users/new_spec.js | 76 ++ .../alerts_settings_form_spec.js.snap | 524 ----------- .../components/alerts_settings_form_spec.js | 258 ++++-- .../components/alerts_settings_wrapper_spec.js | 120 ++- .../analytics/usage_trends/components/app_spec.js | 2 +- spec/frontend/api_spec.js | 68 +- .../batch_comments/components/preview_item_spec.js | 12 + .../behaviors/markdown/render_mermaid_spec.js | 25 + .../behaviors/shortcuts/shortcuts_issuable_spec.js | 2 +- spec/frontend/blob/file_template_selector_spec.js | 61 ++ spec/frontend/boards/board_card_inner_spec.js | 87 +- .../boards/board_new_issue_deprecated_spec.js | 16 +- .../__snapshots__/board_blocked_icon_spec.js.snap | 30 + .../components/board_add_new_column_form_spec.js | 32 +- .../boards/components/board_add_new_column_spec.js | 10 + .../boards/components/board_blocked_icon_spec.js | 226 +++++ .../components/board_content_sidebar_spec.js | 140 +++ .../boards/components/board_content_spec.js | 13 +- spec/frontend/boards/components/board_form_spec.js | 2 +- .../boards/components/board_new_issue_spec.js | 6 +- .../components/board_settings_sidebar_spec.js | 43 +- .../boards/components/filtered_search_spec.js | 65 -- .../boards/components/issue_time_estimate_spec.js | 6 +- .../sidebar/board_sidebar_issue_title_spec.js | 182 ---- .../sidebar/board_sidebar_labels_select_spec.js | 14 +- .../sidebar/board_sidebar_subscription_spec.js | 28 +- .../sidebar/board_sidebar_time_tracker_spec.js | 58 ++ .../components/sidebar/board_sidebar_title_spec.js | 182 ++++ spec/frontend/boards/mock_data.js | 130 ++- spec/frontend/boards/modal_store_spec.js | 134 --- spec/frontend/boards/stores/actions_spec.js | 708 +++++++++++---- spec/frontend/boards/stores/getters_spec.js | 41 +- spec/frontend/boards/stores/mutations_spec.js | 239 +++-- .../branches/components/sort_dropdown_spec.js | 91 ++ spec/frontend/captcha/apollo_captcha_link_spec.js | 165 ++++ .../components/lock_popovers_spec.js | 152 ++++ .../components/ci_variable_modal_spec.js | 43 +- .../components/ci_variable_table_spec.js | 14 - .../clusters/components/application_row_spec.js | 6 + .../services/application_state_machine_spec.js | 30 +- .../components/content_editor_spec.js | 26 + .../content_editor/markdown_processing_examples.js | 19 + .../content_editor/markdown_processing_spec.js | 12 + .../content_editor/services/create_editor_spec.js | 39 + .../__snapshots__/contributors_spec.js.snap | 17 +- .../frontend/create_merge_request_dropdown_spec.js | 18 +- spec/frontend/cycle_analytics/banner_spec.js | 46 +- .../cycle_analytics/total_time_component_spec.js | 34 +- spec/frontend/delete_label_modal_spec.js | 83 ++ .../components/deploy_freeze_modal_spec.js | 71 +- .../components/deploy_freeze_table_spec.js | 24 +- spec/frontend/deploy_freeze/store/actions_spec.js | 82 +- .../frontend/deploy_freeze/store/mutations_spec.js | 15 +- .../deploy_tokens/components/revoke_button_spec.js | 108 +++ .../__snapshots__/design_navigation_spec.js.snap | 2 + .../toolbar/__snapshots__/index_spec.js.snap | 1 + .../upload/__snapshots__/button_spec.js.snap | 8 +- spec/frontend/diffs/components/app_spec.js | 34 +- spec/frontend/diffs/components/commit_item_spec.js | 130 --- .../diffs/components/compare_versions_spec.js | 135 ++- spec/frontend/diffs/components/diff_row_spec.js | 17 + .../diffs/components/inline_diff_table_row_spec.js | 13 + .../components/parallel_diff_table_row_spec.js | 23 + spec/frontend/diffs/create_diffs_store.js | 2 + spec/frontend/diffs/find_interop_attributes.js | 20 + spec/frontend/diffs/store/actions_spec.js | 47 +- spec/frontend/diffs/store/getters_spec.js | 44 +- spec/frontend/diffs/store/mutations_spec.js | 14 +- spec/frontend/diffs/utils/interoperability_spec.js | 67 ++ .../editor/editor_lite_extension_base_spec.js | 271 +++++- .../emoji/awards_app/store/actions_spec.js | 155 ++++ .../emoji/awards_app/store/mutations_spec.js | 65 ++ .../environments/enable_review_app_modal_spec.js | 23 +- .../components/error_tracking_list_spec.js | 1 - .../components/error_tracking_form_spec.js | 12 +- .../error_tracking_settings/store/getters_spec.js | 4 +- .../experimentation/components/experiment_spec.js | 72 ++ spec/frontend/experimentation/utils_spec.js | 101 ++- .../frontend/feature_flags/components/form_spec.js | 4 + .../feature_highlight_popover_spec.js | 1 - spec/frontend/fixtures/api_markdown.rb | 34 + spec/frontend/fixtures/api_markdown.yml | 50 ++ spec/frontend/fixtures/autocomplete.rb | 41 + spec/frontend/fixtures/issues.rb | 2 - spec/frontend/fixtures/merge_requests_diffs.rb | 20 +- .../fixtures/static/mini_dropdown_graph.html | 13 - .../fixtures/static/whats_new_notification.html | 2 +- spec/frontend/flash_spec.js | 62 +- spec/frontend/gfm_auto_complete_spec.js | 35 + .../__snapshots__/grafana_integration_spec.js.snap | 2 +- .../ide/components/cannot_push_code_alert_spec.js | 72 ++ .../ide/components/commit_sidebar/form_spec.js | 6 +- spec/frontend/ide/components/ide_spec.js | 28 +- spec/frontend/ide/stores/getters_spec.js | 126 ++- .../incidents_settings_tabs_spec.js.snap | 4 +- .../edit/components/dynamic_field_spec.js | 11 - .../edit/components/jira_issues_fields_spec.js | 49 +- .../edit/components/jira_trigger_fields_spec.js | 106 ++- .../edit/components/jira_upgrade_cta_spec.js | 30 + .../edit/components/trigger_fields_spec.js | 4 +- .../index/components/integrations_list_spec.js | 26 + .../index/components/integrations_table_spec.js | 53 ++ spec/frontend/integrations/index/mock_data.js | 50 ++ .../components/invite_member_modal_spec.js | 2 +- .../components/invite_member_trigger_spec.js | 2 +- .../components/invite_members_modal_spec.js | 48 +- .../components/invite_members_trigger_spec.js | 82 +- .../issuable/components/csv_export_modal_spec.js | 6 +- .../components/csv_import_export_buttons_spec.js | 9 +- .../components/issuable_list_root_spec.js | 124 ++- .../issuable_list/components/issuable_tabs_spec.js | 15 +- spec/frontend/issuable_list/mock_data.js | 2 +- spec/frontend/issuable_show/mock_data.js | 1 + .../__snapshots__/info_popover_spec.js.snap | 52 ++ .../components/info_popover_spec.js | 20 + .../issue_show/components/edit_actions_spec.js | 8 +- .../issues_list/components/issues_list_app_spec.js | 540 ++++++++++- spec/frontend/jira_connect/api_spec.js | 17 +- .../__snapshots__/group_item_name_spec.js.snap | 44 + spec/frontend/jira_connect/components/app_spec.js | 30 +- .../components/group_item_name_spec.js | 28 + .../components/groups_list_item_spec.js | 57 +- .../jira_connect/components/groups_list_spec.js | 103 ++- .../components/subscriptions_list_spec.js | 122 +++ spec/frontend/jira_connect/index_spec.js | 34 +- spec/frontend/jira_connect/mock_data.js | 6 + spec/frontend/jira_connect/utils_spec.js | 114 ++- spec/frontend/jobs/components/commit_block_spec.js | 105 +-- .../job_sidebar_details_container_spec.js | 15 +- .../jobs/components/manual_variables_form_spec.js | 153 ++-- spec/frontend/jobs/components/sidebar_spec.js | 30 + .../jobs/components/stages_dropdown_spec.js | 155 ++-- .../jobs/components/table/jobs_table_spec.js | 31 + .../jobs/components/table/jobs_table_tabs_spec.js | 42 + spec/frontend/jobs/mock_data.js | 215 +++++ spec/frontend/lib/utils/color_utils_spec.js | 27 +- spec/frontend/lib/utils/common_utils_spec.js | 10 + spec/frontend/lib/utils/datetime_utility_spec.js | 80 +- spec/frontend/lib/utils/forms_spec.js | 163 +++- .../access_request_action_buttons_spec.js | 1 + .../approve_access_request_button_spec.js | 15 +- .../action_buttons/invite_action_buttons_spec.js | 2 + .../remove_group_link_button_spec.js | 11 +- .../action_buttons/remove_member_button_spec.js | 21 +- .../action_buttons/resend_invite_button_spec.js | 15 +- .../action_buttons/user_action_buttons_spec.js | 46 +- spec/frontend/members/components/app_spec.js | 25 +- .../members/components/avatars/user_avatar_spec.js | 16 +- .../filter_sort/filter_sort_container_spec.js | 27 +- .../members_filtered_search_bar_spec.js | 50 +- .../components/filter_sort/sort_dropdown_spec.js | 29 +- .../members/components/modals/leave_modal_spec.js | 42 +- .../modals/remove_group_link_modal_spec.js | 22 +- .../components/table/expiration_datepicker_spec.js | 10 +- .../components/table/members_table_cell_spec.js | 12 +- .../members/components/table/members_table_spec.js | 32 +- .../members/components/table/role_dropdown_spec.js | 10 +- spec/frontend/members/index_spec.js | 47 +- spec/frontend/members/mock_data.js | 2 + .../components/merge_conflict_resolver_app_spec.js | 131 +++ spec/frontend/merge_conflicts/mock_data.js | 340 +++++++ .../frontend/merge_conflicts/store/actions_spec.js | 125 ++- .../frontend/merge_conflicts/store/getters_spec.js | 187 ++++ .../merge_conflicts/store/mutations_spec.js | 99 +++ spec/frontend/merge_conflicts/utils_spec.js | 106 +++ .../merge_request/components/status_box_spec.js | 2 +- spec/frontend/mini_pipeline_graph_dropdown_spec.js | 104 --- .../frontend/mocks/ce/diffs/workers/tree_worker.js | 1 - spec/frontend/mocks/ce/ide/lib/diff/diff_worker.js | 1 - spec/frontend/mr_notes/stores/actions_spec.js | 92 ++ spec/frontend/mr_notes/stores/mutations_spec.js | 27 + spec/frontend/notebook/cells/markdown_spec.js | 106 ++- .../frontend/notes/components/comment_form_spec.js | 67 +- .../notes/components/discussion_navigator_spec.js | 13 +- .../frontend/notes/components/note_actions_spec.js | 16 + spec/frontend/notes/components/note_body_spec.js | 17 +- .../notes/components/noteable_discussion_spec.js | 9 +- .../notes/components/noteable_note_spec.js | 133 ++- spec/frontend/notes/components/notes_app_spec.js | 30 + spec/frontend/notes/mock_data.js | 16 + spec/frontend/notes/stores/getters_spec.js | 69 +- .../packages/details/store/getters_spec.js | 2 + .../__snapshots__/packages_list_app_spec.js.snap | 10 +- .../list/components/packages_list_app_spec.js | 133 ++- .../list/components/packages_search_spec.js | 29 +- .../list/components/packages_title_spec.js | 18 +- spec/frontend/packages/list/utils_spec.js | 11 +- spec/frontend/packages/mock_data.js | 17 + .../__snapshots__/package_list_row_spec.js.snap | 15 +- .../components/package_icon_and_name_spec.js | 32 + .../shared/components/package_list_row_spec.js | 41 +- spec/frontend/packages/shared/utils_spec.js | 1 + .../infrastructure_icon_and_name_spec.js | 28 + .../components/infrastructure_search_spec.js | 135 +++ .../components/infrastructure_title_spec.js | 75 ++ .../group/components/maven_settings_spec.js | 5 +- .../packages_and_registries/shared/utils_spec.js | 59 ++ spec/frontend/pager_spec.js | 71 +- .../__snapshots__/delete_user_modal_spec.js.snap | 4 +- .../users/components/delete_user_modal_spec.js | 8 +- spec/frontend/pages/admin/users/new/index_spec.js | 41 - .../forks/new/components/fork_form_spec.js | 35 +- .../__snapshots__/learn_gitlab_a_spec.js.snap | 374 ++++++-- .../__snapshots__/learn_gitlab_b_spec.js.snap | 70 +- .../learn_gitlab_section_card_spec.js.snap | 67 ++ .../learn_gitlab/components/learn_gitlab_a_spec.js | 30 +- .../learn_gitlab/components/learn_gitlab_b_spec.js | 6 +- .../components/learn_gitlab_section_card_spec.js | 26 + .../components/learn_gitlab_section_link_spec.js | 49 + .../projects/learn_gitlab/components/mock_data.js | 5 + .../permissions/components/settings_panel_spec.js | 45 +- .../shared/wikis/components/wiki_alert_spec.js | 40 + .../shared/wikis/components/wiki_form_spec.js | 222 +++++ .../frontend/pages/shared/wikis/wiki_alert_spec.js | 40 - .../components/detailed_metric_spec.js | 304 ++++++- .../stores/performance_bar_store_spec.js | 40 + .../code_snippet_alert/code_snippet_alert_spec.js | 61 ++ .../editor/ci_config_merged_preview_spec.js | 21 +- .../components/file-nav/branch_switcher_spec.js | 123 +++ .../file-nav/pipeline_editor_file_nav_spec.js | 49 + .../header/pipeline_editor_header_spec.js | 16 +- .../components/header/pipeline_status_spec.js | 55 +- .../components/header/validation_segment_spec.js | 93 +- .../components/lint/ci_lint_results_spec.js | 5 +- .../components/lint/ci_lint_spec.js | 17 +- .../components/pipeline_editor_tabs_spec.js | 54 +- .../components/ui/editor_tab_spec.js | 57 +- .../pipeline_editor/graphql/resolvers_spec.js | 18 +- spec/frontend/pipeline_editor/mock_data.js | 14 + .../pipeline_editor/pipeline_editor_app_spec.js | 91 +- .../pipeline_editor/pipeline_editor_home_spec.js | 7 + spec/frontend/pipelines/blank_state_spec.js | 20 - .../pipelines_list/pipeline_stage_spec.js | 9 + .../pipelines/graph/action_component_spec.js | 2 +- .../pipelines/graph/graph_component_spec.js | 31 +- .../graph/graph_component_wrapper_spec.js | 172 +++- .../pipelines/graph/job_group_dropdown_spec.js | 8 +- spec/frontend/pipelines/graph/job_item_spec.js | 4 +- .../pipelines/graph/job_name_component_spec.js | 2 +- .../graph/linked_pipelines_column_spec.js | 33 +- spec/frontend/pipelines/graph/mock_data.js | 18 +- .../pipelines/graph/stage_column_component_spec.js | 30 +- .../pipelines/graph_shared/links_inner_spec.js | 2 +- .../pipelines/graph_shared/links_layer_spec.js | 13 +- spec/frontend/pipelines/nav_controls_spec.js | 60 +- .../notification/pipeline_notification_spec.js | 79 ++ .../frontend/pipelines/pipeline_graph/mock_data.js | 36 + .../pipeline_graph/pipeline_graph_spec.js | 60 +- .../pipelines/pipelines_ci_templates_spec.js | 111 +++ spec/frontend/pipelines/pipelines_spec.js | 45 +- .../frontend/pipelines/pipelines_table_row_spec.js | 239 ----- spec/frontend/pipelines/pipelines_table_spec.js | 56 +- spec/frontend/pipelines/time_ago_spec.js | 69 +- spec/frontend/pipelines/unwrapping_utils_spec.js | 4 +- .../commit/components/branches_dropdown_spec.js | 6 +- .../components/commit_comments_button_spec.js | 42 + .../components/commit_options_dropdown_spec.js | 123 +++ .../projects/commit/components/form_modal_spec.js | 11 +- .../commit/components/form_trigger_spec.js | 44 - spec/frontend/projects/commit/mock_data.js | 2 +- .../projects/commit/store/mutations_spec.js | 6 +- .../projects/commit_box/info/load_branches_spec.js | 41 +- .../projects/compare/components/app_legacy_spec.js | 55 +- .../compare/components/repo_dropdown_spec.js | 22 +- .../components/revision_dropdown_legacy_spec.js | 25 +- .../compare/components/revision_dropdown_spec.js | 41 +- .../components/app_spec.js | 52 ++ .../components/welcome_spec.js | 31 +- .../ci_cd_analytics_area_chart_spec.js.snap | 6 +- .../pipelines/charts/components/app_spec.js | 57 +- spec/frontend/registry/explorer/pages/list_spec.js | 91 +- .../settings/components/settings_form_spec.js | 82 +- .../releases/components/app_edit_new_spec.js | 10 +- .../frontend/releases/components/app_index_spec.js | 8 +- spec/frontend/releases/components/app_show_spec.js | 189 +++- .../releases/components/asset_links_form_spec.js | 2 +- .../release_block_milestone_info_spec.js | 2 +- .../components/releases_pagination_graphql_spec.js | 16 +- .../components/releases_pagination_rest_spec.js | 14 +- .../releases/components/releases_sort_spec.js | 12 +- .../releases/components/tag_field_exsting_spec.js | 6 +- .../releases/components/tag_field_new_spec.js | 14 +- .../frontend/releases/components/tag_field_spec.js | 6 +- .../releases/stores/modules/detail/actions_spec.js | 10 +- .../releases/stores/modules/detail/getters_spec.js | 4 +- .../stores/modules/detail/mutations_spec.js | 8 +- .../releases/stores/modules/list/actions_spec.js | 6 +- .../releases/stores/modules/list/helpers.js | 2 +- .../releases/stores/modules/list/mutations_spec.js | 6 +- .../reports/components/report_section_spec.js | 22 +- .../grouped_test_report/components/modal_spec.js | 9 +- .../components/test_issue_body_spec.js | 2 +- .../grouped_test_reports_app_spec.js | 41 +- .../grouped_test_report/store/actions_spec.js | 17 +- .../grouped_test_report/store/mutations_spec.js | 8 +- .../grouped_test_report/store/utils_spec.js | 14 + .../directory_download_links_spec.js.snap | 40 +- .../components/blob_content_viewer_spec.js | 86 ++ .../repository/components/breadcrumbs_spec.js | 84 +- .../repository/components/table/row_spec.js | 5 +- spec/frontend/repository/pages/blob_spec.js | 25 + spec/frontend/repository/router_spec.js | 2 + .../runner/runner_detail/runner_detail_app_spec.js | 29 + .../configuration_table_spec.js | 2 +- .../security_configuration/manage_sast_spec.js | 2 +- .../__snapshots__/empty_state_spec.js.snap | 4 +- .../set_status_modal_wrapper_spec.js | 58 +- spec/frontend/sidebar/assignees_realtime_spec.js | 15 +- .../assignees/sidebar_assignees_widget_spec.js | 558 ++++++++++++ .../assignees/sidebar_editable_item_spec.js | 33 +- .../assignees/sidebar_invite_members_spec.js | 59 ++ .../assignees/sidebar_participant_spec.js | 43 + .../sidebar_confidentiality_form_spec.js | 4 +- .../sidebar_confidentiality_widget_spec.js | 4 +- .../components/copy_email_to_clipboard_spec.js | 19 +- .../due_date/sidebar_due_date_widget_spec.js | 106 +++ .../reference/sidebar_reference_widget_spec.js | 70 +- spec/frontend/sidebar/issuable_assignees_spec.js | 19 +- spec/frontend/sidebar/mock_data.js | 156 ++++ .../snippet_description_edit_spec.js.snap | 2 + spec/frontend/snippets/components/edit_spec.js | 71 +- .../frontend/tags/components/sort_dropdown_spec.js | 81 ++ spec/frontend/tracking_spec.js | 76 +- spec/frontend/users_select/index_spec.js | 223 +++++ spec/frontend/vue_alerts_spec.js | 8 +- .../components/mr_widget_author_time_spec.js | 43 +- .../components/mr_widget_header_spec.js | 156 ++-- .../components/mr_widget_pipeline_spec.js | 91 +- .../states/mr_widget_auto_merge_enabled_spec.js | 29 +- .../components/states/mr_widget_conflicts_spec.js | 19 +- .../states/mr_widget_failed_to_merge_spec.js | 166 ++-- .../vue_shared/alert_details/alert_details_spec.js | 10 +- .../vue_shared/alert_details/alert_status_spec.js | 19 + .../alert_details/sidebar/alert_sidebar_spec.js | 16 - .../sidebar/alert_sidebar_status_spec.js | 31 +- .../__snapshots__/clone_dropdown_spec.js.snap | 2 + .../components/alert_details_table_spec.js | 83 +- .../__snapshots__/simple_viewer_spec.js.snap | 141 +-- .../components/blob_viewers/simple_viewer_spec.js | 41 +- .../components/delete_label_modal_spec.js | 64 ++ .../vue_shared/components/deprecated_modal_spec.js | 73 -- .../vue_shared/components/ensure_data_spec.js | 145 +++ .../components/filtered_search_bar/mock_data.js | 47 + .../filtered_search_bar/tokens/emoji_token_spec.js | 217 +++++ .../filtered_search_bar/tokens/epic_token_spec.js | 180 ++++ .../filtered_search_bar/tokens/label_token_spec.js | 16 + .../vue_shared/components/gl_toggle_vuex_spec.js | 114 --- .../vue_shared/components/help_popover_spec.js | 14 +- .../components/lib/utils/props_utils_spec.js | 91 ++ .../markdown/suggestion_diff_header_spec.js | 34 +- .../vue_shared/components/markdown/toolbar_spec.js | 64 +- .../components/recaptcha_eventhub_spec.js | 21 - .../vue_shared/components/recaptcha_modal_spec.js | 35 - .../components/registry/registry_search_spec.js | 57 +- .../components/remove_member_modal_spec.js | 61 +- .../components/runner_instructions/mock_data.js | 16 +- .../runner_instructions_modal_spec.js | 184 ++++ .../runner_instructions_spec.js | 110 +-- .../components/sidebar/copyable_field_spec.js | 74 ++ .../vue_shared/components/url_sync_spec.js | 97 ++ .../components/user_popover/user_popover_spec.js | 6 +- .../vue_shared/oncall_schedules_list_spec.js | 87 ++ spec/frontend/whats_new/components/app_spec.js | 59 +- spec/frontend/whats_new/store/actions_spec.js | 11 +- spec/frontend/whats_new/utils/notification_spec.js | 23 +- spec/frontend/wikis_spec.js | 153 ---- .../diffs/diffs_interopability_api.js | 25 + .../diffs/diffs_interopability_spec.js | 161 ++++ spec/frontend_integration/test_helpers/fixtures.js | 6 + .../test_helpers/mock_server/graphql.js | 11 +- .../test_helpers/mock_server/index.js | 2 +- .../test_helpers/mock_server/routes/diffs.js | 22 + .../test_helpers/mock_server/routes/index.js | 1 + spec/graphql/features/authorization_spec.rb | 134 ++- spec/graphql/gitlab_schema_spec.rb | 39 +- .../boards/issues/issue_move_list_spec.rb | 66 +- .../mutations/can_mutate_spammable_spec.rb | 46 - .../mutations/design_management/upload_spec.rb | 14 +- .../graphql/mutations/issues/set_assignees_spec.rb | 7 +- .../mutations/merge_requests/set_assignees_spec.rb | 7 +- .../mutations/release_asset_links/delete_spec.rb | 58 ++ .../mutations/release_asset_links/update_spec.rb | 2 +- .../http_integrations_resolver_spec.rb | 20 +- .../alert_management/integrations_resolver_spec.rb | 39 +- spec/graphql/resolvers/blobs_resolver_spec.rb | 74 ++ .../resolvers/board_list_issues_resolver_spec.rb | 18 + spec/graphql/resolvers/ci/jobs_resolver_spec.rb | 17 +- .../resolvers/ci/runner_platforms_resolver_spec.rb | 2 +- .../resolvers/ci/runner_setup_resolver_spec.rb | 77 +- .../ci/test_report_summary_resolver_spec.rb | 46 + .../resolvers/ci/test_suite_resolver_spec.rb | 54 ++ .../graphql/resolvers/concerns/looks_ahead_spec.rb | 9 +- .../resolvers/group_milestones_resolver_spec.rb | 51 ++ .../resolvers/issue_status_counts_resolver_spec.rb | 8 + spec/graphql/resolvers/issues_resolver_spec.rb | 45 +- .../resolvers/merge_requests_resolver_spec.rb | 64 +- .../resolvers/namespace_projects_resolver_spec.rb | 2 +- .../resolvers/project_jobs_resolver_spec.rb | 51 ++ .../resolvers/project_pipeline_resolver_spec.rb | 15 +- .../repository_branch_names_resolver_spec.rb | 36 + spec/graphql/resolvers/timelog_resolver_spec.rb | 168 ++++ .../resolvers/users/snippets_resolver_spec.rb | 14 +- .../usage_trends/measurement_type_spec.rb | 3 +- .../prometheus_integration_type_spec.rb | 24 +- spec/graphql/types/base_enum_spec.rb | 48 +- spec/graphql/types/base_object_spec.rb | 432 +++++++++ spec/graphql/types/board_type_spec.rb | 14 +- .../types/boards/board_issue_input_type_spec.rb | 4 +- spec/graphql/types/ci/job_status_enum_spec.rb | 13 + spec/graphql/types/ci/job_type_spec.rb | 28 +- spec/graphql/types/ci/pipeline_type_spec.rb | 5 +- spec/graphql/types/ci/recent_failures_type_spec.rb | 15 + spec/graphql/types/ci/stage_type_spec.rb | 1 + .../graphql/types/ci/test_case_status_enum_spec.rb | 13 + spec/graphql/types/ci/test_case_type_spec.rb | 15 + .../types/ci/test_report_summary_type_spec.rb | 15 + .../types/ci/test_report_total_type_spec.rb | 15 + .../types/ci/test_suite_summary_type_spec.rb | 15 + spec/graphql/types/ci/test_suite_type_spec.rb | 15 + spec/graphql/types/global_id_type_spec.rb | 1 + spec/graphql/types/issue_type_spec.rb | 2 +- .../types/merge_request_review_state_enum_spec.rb | 18 + .../types/merge_requests/reviewer_type_spec.rb | 50 ++ spec/graphql/types/milestone_type_spec.rb | 2 +- .../packages/conan/file_metadatum_type_spec.rb | 13 + .../conan/metadatum_file_type_enum_spec.rb | 13 + .../types/packages/conan/metadatum_type_spec.rb | 13 + .../types/packages/package_details_type_spec.rb | 13 + .../types/packages/package_file_type_spec.rb | 13 + spec/graphql/types/packages/package_type_spec.rb | 2 +- .../packages/package_without_versions_type_spec.rb | 13 - spec/graphql/types/project_type_spec.rb | 32 +- spec/graphql/types/query_type_spec.rb | 2 +- spec/graphql/types/repository/blob_type_spec.rb | 9 + spec/graphql/types/repository_type_spec.rb | 4 + spec/graphql/types/snippet_type_spec.rb | 2 + spec/graphql/types/timelog_type_spec.rb | 35 + .../user_merge_request_interaction_type_spec.rb | 116 +++ spec/helpers/application_helper_spec.rb | 18 +- spec/helpers/avatars_helper_spec.rb | 24 +- spec/helpers/blob_helper_spec.rb | 10 + spec/helpers/boards_helper_spec.rb | 24 + spec/helpers/broadcast_messages_helper_spec.rb | 1 + spec/helpers/button_helper_spec.rb | 7 + spec/helpers/ci/pipeline_editor_helper_spec.rb | 32 + spec/helpers/ci/runners_helper_spec.rb | 30 +- spec/helpers/commits_helper_spec.rb | 127 +-- spec/helpers/diff_helper_spec.rb | 38 +- spec/helpers/graph_helper_spec.rb | 4 +- spec/helpers/groups_helper_spec.rb | 161 ++-- spec/helpers/ide_helper_spec.rb | 8 +- spec/helpers/invite_members_helper_spec.rb | 3 + spec/helpers/issuables_helper_spec.rb | 54 ++ spec/helpers/issues_helper_spec.rb | 51 ++ spec/helpers/jira_connect_helper_spec.rb | 1 + spec/helpers/labels_helper_spec.rb | 1 + spec/helpers/learn_gitlab_helper_spec.rb | 1 + spec/helpers/markup_helper_spec.rb | 25 +- spec/helpers/namespaces_helper_spec.rb | 71 ++ spec/helpers/nav_helper_spec.rb | 6 +- spec/helpers/notes_helper_spec.rb | 2 + spec/helpers/page_layout_helper_spec.rb | 52 +- spec/helpers/preferences_helper_spec.rb | 2 +- spec/helpers/profiles_helper_spec.rb | 40 + .../projects/alert_management_helper_spec.rb | 1 + .../projects/issues/service_desk_helper_spec.rb | 1 + .../projects/project_members_helper_spec.rb | 2 + spec/helpers/projects/terraform_helper_spec.rb | 1 + spec/helpers/projects_helper_spec.rb | 1 + spec/helpers/search_helper_spec.rb | 1 + spec/helpers/services_helper_spec.rb | 14 + spec/helpers/sidebars_helper_spec.rb | 45 + spec/helpers/snippets_helper_spec.rb | 28 +- spec/helpers/tab_helper_spec.rb | 115 +-- spec/helpers/timeboxes_helper_spec.rb | 36 - spec/helpers/todos_helper_spec.rb | 20 - spec/helpers/tracking_helper_spec.rb | 2 +- spec/helpers/user_callouts_helper_spec.rb | 42 +- spec/helpers/whats_new_helper_spec.rb | 48 +- spec/helpers/wiki_helper_spec.rb | 2 +- spec/initializers/active_record_locking_spec.rb | 4 +- .../fog_google_https_private_urls_spec.rb | 2 + spec/initializers/json_validator_patch_spec.rb | 39 - spec/initializers/kramdown_patch_spec.rb | 38 - spec/initializers/pages_storage_check_spec.rb | 94 ++ spec/knapsack_env.rb | 11 + spec/lib/api/entities/clusters/agent_spec.rb | 16 + .../api/entities/design_management/design_spec.rb | 1 + .../lib/api/entities/merge_request_changes_spec.rb | 1 + .../project_import_failed_relation_spec.rb | 2 +- spec/lib/api/entities/release_spec.rb | 1 + spec/lib/api/helpers/authentication_spec.rb | 15 + spec/lib/api/helpers/caching_spec.rb | 139 +++ .../packages/dependency_proxy_helpers_spec.rb | 10 +- .../packages_manager_clients_helpers_spec.rb | 3 + spec/lib/api/helpers/variables_helpers_spec.rb | 43 + spec/lib/api/helpers_spec.rb | 109 ++- .../serializers/pull_request_entity_spec.rb | 2 +- .../filter/abstract_reference_filter_spec.rb | 102 --- .../banzai/filter/alert_reference_filter_spec.rb | 223 ----- .../filter/commit_range_reference_filter_spec.rb | 255 ------ .../banzai/filter/commit_reference_filter_spec.rb | 272 ------ .../banzai/filter/commit_trailers_filter_spec.rb | 6 + .../banzai/filter/design_reference_filter_spec.rb | 287 ------ .../filter/external_issue_reference_filter_spec.rb | 256 ------ .../filter/feature_flag_reference_filter_spec.rb | 223 ----- spec/lib/banzai/filter/gollum_tags_filter_spec.rb | 12 +- .../filter/inline_metrics_redactor_filter_spec.rb | 3 + .../banzai/filter/issue_reference_filter_spec.rb | 549 ------------ .../banzai/filter/label_reference_filter_spec.rb | 705 --------------- spec/lib/banzai/filter/math_filter_spec.rb | 10 +- .../filter/merge_request_reference_filter_spec.rb | 289 ------ .../filter/milestone_reference_filter_spec.rb | 463 ---------- .../banzai/filter/project_reference_filter_spec.rb | 100 --- spec/lib/banzai/filter/reference_filter_spec.rb | 224 ----- .../references/abstract_reference_filter_spec.rb | 102 +++ .../references/alert_reference_filter_spec.rb | 223 +++++ .../commit_range_reference_filter_spec.rb | 255 ++++++ .../references/commit_reference_filter_spec.rb | 272 ++++++ .../references/design_reference_filter_spec.rb | 287 ++++++ .../external_issue_reference_filter_spec.rb | 257 ++++++ .../feature_flag_reference_filter_spec.rb | 223 +++++ .../references/issue_reference_filter_spec.rb | 549 ++++++++++++ .../references/label_reference_filter_spec.rb | 705 +++++++++++++++ .../merge_request_reference_filter_spec.rb | 289 ++++++ .../references/milestone_reference_filter_spec.rb | 463 ++++++++++ .../references/project_reference_filter_spec.rb | 100 +++ .../filter/references/reference_filter_spec.rb | 224 +++++ .../references/snippet_reference_filter_spec.rb | 222 +++++ .../references/user_reference_filter_spec.rb | 204 +++++ .../banzai/filter/snippet_reference_filter_spec.rb | 222 ----- spec/lib/banzai/filter/suggestion_filter_spec.rb | 4 +- .../banzai/filter/syntax_highlight_filter_spec.rb | 12 +- spec/lib/banzai/filter/upload_link_filter_spec.rb | 2 + .../banzai/filter/user_reference_filter_spec.rb | 204 ----- spec/lib/banzai/filter/wiki_link_filter_spec.rb | 9 + spec/lib/banzai/pipeline/gfm_pipeline_spec.rb | 3 +- spec/lib/banzai/pipeline/wiki_pipeline_spec.rb | 25 + .../reference_parser/external_issue_parser_spec.rb | 2 +- spec/lib/banzai/reference_redactor_spec.rb | 2 +- .../common/extractors/rest_extractor_spec.rb | 31 + .../user_reference_transformer_spec.rb | 28 +- .../groups/extractors/subgroups_extractor_spec.rb | 3 +- .../groups/graphql/get_group_query_spec.rb | 6 +- .../groups/graphql/get_labels_query_spec.rb | 4 +- .../groups/graphql/get_members_query_spec.rb | 4 +- .../groups/graphql/get_milestones_query_spec.rb | 4 +- .../groups/loaders/group_loader_spec.rb | 11 +- .../groups/pipelines/badges_pipeline_spec.rb | 116 +++ .../groups/pipelines/entity_finisher_spec.rb | 37 + .../groups/pipelines/group_pipeline_spec.rb | 14 +- .../groups/pipelines/labels_pipeline_spec.rb | 87 +- .../groups/pipelines/members_pipeline_spec.rb | 14 +- .../groups/pipelines/milestones_pipeline_spec.rb | 90 +- .../pipelines/subgroup_entities_pipeline_spec.rb | 30 +- .../groups/rest/get_badges_query_spec.rb | 22 + .../group_attributes_transformer_spec.rb | 23 +- .../member_attributes_transformer_spec.rb | 3 +- .../bulk_imports/importers/group_importer_spec.rb | 57 -- spec/lib/bulk_imports/pipeline/context_spec.rb | 45 +- .../bulk_imports/pipeline/extracted_data_spec.rb | 2 +- spec/lib/bulk_imports/pipeline/runner_spec.rb | 65 +- spec/lib/bulk_imports/pipeline_spec.rb | 10 +- spec/lib/constraints/admin_constrainer_spec.rb | 6 +- spec/lib/feature_spec.rb | 92 ++ .../redis_hll_generator_spec.rb | 30 + .../usage_metric_definition_generator_spec.rb | 70 +- .../alert_management/alert_status_counts_spec.rb | 1 + .../gitlab/alert_management/payload/base_spec.rb | 1 + .../alert_management/payload/generic_spec.rb | 1 + .../payload/managed_prometheus_spec.rb | 2 + .../alert_management/payload/prometheus_spec.rb | 1 + spec/lib/gitlab/alert_management/payload_spec.rb | 1 + .../cycle_analytics/base_query_builder_spec.rb | 1 + .../analytics/cycle_analytics/median_spec.rb | 1 + .../cycle_analytics/records_fetcher_spec.rb | 44 +- .../stage_events/code_stage_start_spec.rb | 2 +- spec/lib/gitlab/analytics/unique_visits_spec.rb | 22 +- spec/lib/gitlab/application_context_spec.rb | 16 +- spec/lib/gitlab/asciidoc_spec.rb | 6 +- spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb | 2 +- spec/lib/gitlab/auth/otp/strategies/devise_spec.rb | 1 + .../otp/strategies/forti_authenticator_spec.rb | 1 + .../auth/otp/strategies/forti_token_cloud_spec.rb | 1 + spec/lib/gitlab/auth_spec.rb | 2 + .../backfill_snippet_repositories_spec.rb | 2 +- ...y_column_using_background_migration_job_spec.rb | 8 + .../migrate_pages_to_zip_storage_spec.rb | 43 + spec/lib/gitlab/bullet/exclusions_spec.rb | 155 ++++ spec/lib/gitlab/bullet_spec.rb | 51 ++ spec/lib/gitlab/changelog/config_spec.rb | 4 +- spec/lib/gitlab/checks/project_created_spec.rb | 1 + spec/lib/gitlab/checks/project_moved_spec.rb | 3 + spec/lib/gitlab/ci/ansi2json/style_spec.rb | 4 +- spec/lib/gitlab/ci/config/entry/bridge_spec.rb | 6 + spec/lib/gitlab/ci/config/entry/cache_spec.rb | 8 + spec/lib/gitlab/ci/config/entry/job_spec.rb | 2 + spec/lib/gitlab/ci/config/entry/jobs_spec.rb | 4 + .../lib/gitlab/ci/config/entry/processable_spec.rb | 26 +- spec/lib/gitlab/ci/config/entry/root_spec.rb | 20 + spec/lib/gitlab/ci/config/external/mapper_spec.rb | 34 + .../gitlab/ci/config/external/processor_spec.rb | 35 + .../ci/config/normalizer/matrix_strategy_spec.rb | 20 + spec/lib/gitlab/ci/lint_spec.rb | 2 +- .../ci/parsers/codequality/code_climate_spec.rb | 1 - spec/lib/gitlab/ci/pipeline/chain/command_spec.rb | 21 + .../pipeline/chain/evaluate_workflow_rules_spec.rb | 20 +- spec/lib/gitlab/ci/pipeline/chain/helpers_spec.rb | 61 ++ .../ci/pipeline/chain/limit/deployments_spec.rb | 13 +- .../ci/pipeline/chain/pipeline/process_spec.rb | 31 + spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb | 6 + spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb | 159 +++- .../ci/pipeline/chain/validate/external_spec.rb | 201 ++++- spec/lib/gitlab/ci/pipeline/seed/build_spec.rb | 139 ++- spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb | 4 +- spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb | 3 +- .../reports/codequality_reports_comparer_spec.rb | 109 ++- .../gitlab/ci/reports/codequality_reports_spec.rb | 34 +- .../gitlab/ci/reports/test_failure_history_spec.rb | 4 +- spec/lib/gitlab/ci/runner_instructions_spec.rb | 116 +-- spec/lib/gitlab/ci/status/build/common_spec.rb | 2 +- spec/lib/gitlab/ci/status/composite_spec.rb | 8 +- spec/lib/gitlab/ci/trace_spec.rb | 6 +- spec/lib/gitlab/ci/variables/helpers_spec.rb | 46 + spec/lib/gitlab/ci/yaml_processor/result_spec.rb | 2 +- spec/lib/gitlab/ci/yaml_processor_spec.rb | 139 ++- spec/lib/gitlab/composer/version_index_spec.rb | 5 + spec/lib/gitlab/conflict/file_spec.rb | 6 +- spec/lib/gitlab/crypto_helper_spec.rb | 58 +- spec/lib/gitlab/data_builder/build_spec.rb | 3 + spec/lib/gitlab/data_builder/pipeline_spec.rb | 1 - .../background_migration/batch_metrics_spec.rb | 31 + .../batched_migration_runner_spec.rb | 198 +++++ .../background_migration/batched_migration_spec.rb | 51 ++ .../batched_migration_wrapper_spec.rb | 94 +- .../background_migration/scheduler_spec.rb | 182 ---- spec/lib/gitlab/database/batch_count_spec.rb | 76 +- .../loose_index_scan_distinct_count_spec.rb | 71 ++ spec/lib/gitlab/database/migration_helpers_spec.rb | 241 +++-- .../background_migration_helpers_spec.rb | 28 +- .../table_management_helpers_spec.rb | 66 ++ spec/lib/gitlab/database/pg_class_spec.rb | 37 + .../v1/rename_base_spec.rb | 3 +- .../database/unidirectional_copy_trigger_spec.rb | 191 ++++ .../instance_administrators/create_group_spec.rb | 2 +- spec/lib/gitlab/database_spec.rb | 4 +- spec/lib/gitlab/diff/char_diff_spec.rb | 6 +- spec/lib/gitlab/diff/highlight_cache_spec.rb | 26 +- spec/lib/gitlab/diff/highlight_spec.rb | 20 + spec/lib/gitlab/diff/inline_diff_spec.rb | 74 +- spec/lib/gitlab/diff/line_spec.rb | 35 + spec/lib/gitlab/diff/lines_unfolder_spec.rb | 18 +- spec/lib/gitlab/diff/suggestions_parser_spec.rb | 3 +- spec/lib/gitlab/downtime_check/message_spec.rb | 41 - spec/lib/gitlab/downtime_check_spec.rb | 116 --- .../email/handler/create_note_handler_spec.rb | 8 +- .../create_note_on_issuable_handler_spec.rb | 2 +- .../email/handler/unsubscribe_handler_spec.rb | 2 +- .../processor/context_payload_processor_spec.rb | 93 +- .../processor/grpc_error_processor_spec.rb | 90 +- .../processor/sidekiq_processor_spec.rb | 96 +- spec/lib/gitlab/error_tracking_spec.rb | 108 ++- spec/lib/gitlab/experimentation_spec.rb | 1 - spec/lib/gitlab/git/diff_collection_spec.rb | 86 +- spec/lib/gitlab/git/repository_spec.rb | 40 +- spec/lib/gitlab/git/tag_spec.rb | 13 + spec/lib/gitlab/gitaly_client/blob_service_spec.rb | 43 +- spec/lib/gitlab/gitaly_client/call_spec.rb | 13 +- .../gitaly_client/object_pool_service_spec.rb | 4 +- .../gitlab/gitaly_client/operation_service_spec.rb | 26 +- .../gitaly_client/repository_service_spec.rb | 17 +- .../pull_request_merged_by_importer_spec.rb | 1 + .../importer/pull_request_review_importer_spec.rb | 1 + .../gitlab/github_import/milestone_finder_spec.rb | 1 + .../authorize/authorize_field_service_spec.rb | 253 ------ .../graphql/authorize/authorize_resource_spec.rb | 80 +- .../graphql/authorize/object_authorization_spec.rb | 64 ++ spec/lib/gitlab/graphql/batch_key_spec.rb | 1 + spec/lib/gitlab/graphql/deprecation_spec.rb | 213 +++++ spec/lib/gitlab/graphql/docs/renderer_spec.rb | 80 +- .../graphql/loaders/batch_lfs_oid_loader_spec.rb | 1 + spec/lib/gitlab/graphql/markdown_field_spec.rb | 1 + .../lib/gitlab/graphql/negatable_arguments_spec.rb | 45 + .../graphql/pagination/keyset/connection_spec.rb | 1 + .../graphql/pagination/keyset/last_items_spec.rb | 1 + .../query_analyzers/logger_analyzer_spec.rb | 47 +- spec/lib/gitlab/highlight_spec.rb | 15 + spec/lib/gitlab/hook_data/issue_builder_spec.rb | 1 + .../gitlab/hook_data/merge_request_builder_spec.rb | 1 + spec/lib/gitlab/hook_data/release_builder_spec.rb | 1 + spec/lib/gitlab/hook_data/user_builder_spec.rb | 90 ++ spec/lib/gitlab/http_connection_adapter_spec.rb | 125 --- spec/lib/gitlab/import_export/all_models.yml | 5 + .../gitlab/import_export/design_repo_saver_spec.rb | 1 + .../import_export/fast_hash_serializer_spec.rb | 1 + .../import_export/project/export_task_spec.rb | 1 + .../import_export/project/tree_restorer_spec.rb | 2 +- .../import_export/project/tree_saver_spec.rb | 1 + spec/lib/gitlab/import_export/repo_saver_spec.rb | 1 + .../import_export/snippet_repo_saver_spec.rb | 1 + .../import_export/snippets_repo_saver_spec.rb | 1 + .../gitlab/import_export/wiki_repo_saver_spec.rb | 1 + spec/lib/gitlab/instrumentation_helper_spec.rb | 47 - spec/lib/gitlab/jira_import/base_importer_spec.rb | 1 + .../jira_import/handle_labels_service_spec.rb | 1 + spec/lib/gitlab/jira_import_spec.rb | 1 + spec/lib/gitlab/json_spec.rb | 60 ++ .../gitlab/legacy_github_import/importer_spec.rb | 2 +- .../legacy_github_import/issue_formatter_spec.rb | 2 +- .../milestone_formatter_spec.rb | 2 +- .../pull_request_formatter_spec.rb | 2 +- .../markdown_cache/active_record/extension_spec.rb | 26 +- .../gitlab/markdown_cache/redis/extension_spec.rb | 3 +- spec/lib/gitlab/markdown_cache/redis/store_spec.rb | 4 +- spec/lib/gitlab/marker_range_spec.rb | 33 +- .../gitlab/metrics/background_transaction_spec.rb | 53 +- .../metrics/subscribers/active_record_spec.rb | 171 +++- .../metrics/subscribers/external_http_spec.rb | 57 +- .../rack_multipart_tempfile_factory_spec.rb | 94 ++ spec/lib/gitlab/object_hierarchy_spec.rb | 97 +- spec/lib/gitlab/pages/settings_spec.rb | 42 +- spec/lib/gitlab/pages/stores/local_store_spec.rb | 25 + spec/lib/gitlab/pages_transfer_spec.rb | 4 +- spec/lib/gitlab/pagination/keyset/order_spec.rb | 55 ++ .../offset_header_builder_with_controller_spec.rb | 54 ++ .../gitlab/phabricator_import/cache/map_spec.rb | 1 + .../issues/task_importer_spec.rb | 1 + spec/lib/gitlab/profiler_spec.rb | 9 +- spec/lib/gitlab/prometheus/adapter_spec.rb | 16 + spec/lib/gitlab/query_limiting/transaction_spec.rb | 10 +- spec/lib/gitlab/query_limiting_spec.rb | 82 +- .../quick_actions/command_definition_spec.rb | 8 +- spec/lib/gitlab/regex_spec.rb | 9 +- spec/lib/gitlab/repository_cache_adapter_spec.rb | 31 +- spec/lib/gitlab/repository_set_cache_spec.rb | 23 + spec/lib/gitlab/sanitizers/exif_spec.rb | 2 +- spec/lib/gitlab/search_context/builder_spec.rb | 29 + spec/lib/gitlab/sidekiq_cluster/cli_spec.rb | 2 +- spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb | 84 +- .../gitlab/sidekiq_config/worker_matcher_spec.rb | 129 +++ .../sidekiq_logging/structured_logger_spec.rb | 97 +- .../sidekiq_middleware/admin_mode/client_spec.rb | 4 +- .../sidekiq_middleware/admin_mode/server_spec.rb | 4 +- .../sidekiq_middleware/client_metrics_spec.rb | 153 +--- .../instrumentation_logger_spec.rb | 101 +++ .../sidekiq_middleware/server_metrics_spec.rb | 318 ++----- .../worker_context/server_spec.rb | 2 +- spec/lib/gitlab/sidekiq_middleware_spec.rb | 6 +- .../presenters/issue_comment_spec.rb | 1 + .../slash_commands/presenters/issue_move_spec.rb | 1 + .../slash_commands/presenters/issue_new_spec.rb | 13 +- spec/lib/gitlab/slash_commands/run_spec.rb | 20 + spec/lib/gitlab/snippet_search_results_spec.rb | 1 + spec/lib/gitlab/sourcegraph_spec.rb | 1 + spec/lib/gitlab/sql/cte_spec.rb | 20 +- spec/lib/gitlab/sql/recursive_cte_spec.rb | 13 + spec/lib/gitlab/subscription_portal_spec.rb | 56 +- .../template/finders/repo_template_finders_spec.rb | 1 + .../gitlab/tracking/destinations/snowplow_spec.rb | 23 - spec/lib/gitlab/tracking/standard_context_spec.rb | 12 +- spec/lib/gitlab/tracking_spec.rb | 32 +- spec/lib/gitlab/tree_summary_spec.rb | 1 + spec/lib/gitlab/untrusted_regexp_spec.rb | 18 + spec/lib/gitlab/url_builder_spec.rb | 1 + spec/lib/gitlab/usage/metric_definition_spec.rb | 85 +- .../aggregates/sources/postgres_hll_spec.rb | 1 + .../metrics/names_suggestions/generator_spec.rb | 51 +- .../relation_parsers/joins_spec.rb | 40 + .../usage_data_counters/aggregated_metrics_spec.rb | 95 -- .../usage_data_counters/code_review_events_spec.rb | 2 +- .../usage_data_counters/hll_redis_counter_spec.rb | 36 +- .../issue_activity_unique_counter_spec.rb | 18 +- .../quick_action_activity_unique_counter_spec.rb | 20 + spec/lib/gitlab/usage_data_non_sql_metrics_spec.rb | 55 ++ spec/lib/gitlab/usage_data_queries_spec.rb | 32 + spec/lib/gitlab/usage_data_spec.rb | 42 +- spec/lib/gitlab/utils/lazy_attributes_spec.rb | 6 +- spec/lib/gitlab/utils/usage_data_spec.rb | 19 + spec/lib/gitlab/utils_spec.rb | 8 +- .../lib/gitlab/web_ide/config/entry/global_spec.rb | 1 + .../gitlab/web_ide/config/entry/terminal_spec.rb | 5 +- spec/lib/gitlab/word_diff/chunk_collection_spec.rb | 23 + spec/lib/gitlab/word_diff/parser_spec.rb | 40 +- spec/lib/gitlab/workhorse_spec.rb | 1 + spec/lib/gitlab_spec.rb | 118 ++- spec/lib/kramdown/kramdown_spec.rb | 38 + spec/lib/marginalia_spec.rb | 52 +- spec/lib/mattermost/command_spec.rb | 2 +- spec/lib/mattermost/session_spec.rb | 2 +- spec/lib/mattermost/team_spec.rb | 2 +- spec/lib/peek/views/active_record_spec.rb | 27 +- spec/lib/peek/views/external_http_spec.rb | 33 +- spec/lib/quality/test_level_spec.rb | 225 ----- spec/lib/rouge/formatters/html_gitlab_spec.rb | 40 + spec/mailers/devise_mailer_spec.rb | 32 + spec/mailers/emails/in_product_marketing_spec.rb | 34 +- spec/mailers/emails/merge_requests_spec.rb | 24 +- spec/mailers/emails/profile_spec.rb | 100 +++ spec/mailers/emails/projects_spec.rb | 1 + spec/mailers/emails/releases_spec.rb | 1 + spec/mailers/emails/service_desk_spec.rb | 1 + spec/mailers/notify_spec.rb | 96 +- .../20210226141517_dedup_issue_metrics_spec.rb | 66 ++ ...otal_tuple_count_for_batched_migrations_spec.rb | 43 + ...schedule_artifact_expiry_backfill_again_spec.rb | 38 + spec/migrations/add_new_trail_plans_spec.rb | 95 ++ ...ackfill_operations_feature_flags_active_spec.rb | 4 +- .../backfill_operations_feature_flags_iid_spec.rb | 4 +- ...ean_up_noteable_id_for_notes_on_commits_spec.rb | 2 +- spec/migrations/confirm_support_bot_user_spec.rb | 86 ++ ..._internal_ids_where_feature_flags_usage_spec.rb | 4 +- .../delete_security_findings_without_uuid_spec.rb | 35 + .../migrate_bot_type_to_user_type_spec.rb | 2 +- .../migrate_elastic_index_settings_spec.rb | 44 + ...ps_feature_flags_scopes_target_user_ids_spec.rb | 4 +- ...r_registry_enabled_to_project_features2_spec.rb | 47 + ...er_registry_enabled_to_project_features_spec.rb | 43 - ...smissal_information_for_vulnerabilities_spec.rb | 61 ++ ...cords_without_group_from_webhooks_table_spec.rb | 27 + .../schedule_migrate_pages_to_zip_storage_spec.rb | 46 + spec/models/application_record_spec.rb | 36 +- spec/models/audit_event_archived_spec.rb | 52 -- spec/models/blob_viewer/gitlab_ci_yml_spec.rb | 1 + .../blob_viewer/metrics_dashboard_yml_spec.rb | 1 + spec/models/bulk_imports/entity_spec.rb | 64 -- spec/models/bulk_imports/stage_spec.rb | 50 ++ spec/models/bulk_imports/tracker_spec.rb | 56 ++ spec/models/ci/artifact_blob_spec.rb | 1 + spec/models/ci/bridge_spec.rb | 2 +- spec/models/ci/build_spec.rb | 162 ++-- spec/models/ci/build_trace_chunk_spec.rb | 1 + .../ci/daily_build_group_report_result_spec.rb | 1 + spec/models/ci/job_artifact_spec.rb | 16 + spec/models/ci/pipeline_schedule_spec.rb | 12 + spec/models/ci/pipeline_spec.rb | 102 +++ spec/models/ci/runner_spec.rb | 1 + spec/models/ci/stage_spec.rb | 13 + spec/models/ci/test_case_failure_spec.rb | 73 -- spec/models/ci/test_case_spec.rb | 31 - spec/models/ci/unit_test_failure_spec.rb | 73 ++ spec/models/ci/unit_test_spec.rb | 87 ++ spec/models/clusters/agent_token_spec.rb | 49 + .../clusters/applications/prometheus_spec.rb | 94 +- spec/models/clusters/cluster_spec.rb | 76 +- .../clusters/integrations/prometheus_spec.rb | 57 ++ spec/models/commit_spec.rb | 2 +- spec/models/commit_status_spec.rb | 118 ++- .../batch_destroy_dependent_associations_spec.rb | 1 + spec/models/concerns/cache_markdown_field_spec.rb | 10 +- spec/models/concerns/cacheable_attributes_spec.rb | 2 +- .../cascading_namespace_setting_attribute_spec.rb | 320 +++++++ spec/models/concerns/ci/artifactable_spec.rb | 28 + spec/models/concerns/featurable_spec.rb | 3 +- spec/models/concerns/has_timelogs_report_spec.rb | 51 ++ spec/models/concerns/issuable_spec.rb | 21 +- spec/models/concerns/milestoneable_spec.rb | 14 +- spec/models/concerns/milestoneish_spec.rb | 71 +- spec/models/concerns/participable_spec.rb | 71 +- spec/models/concerns/safe_url_spec.rb | 12 +- .../sidebars/container_with_html_options_spec.rb | 21 + .../concerns/sidebars/positionable_list_spec.rb | 59 ++ spec/models/concerns/sortable_spec.rb | 25 + spec/models/concerns/subscribable_spec.rb | 51 +- spec/models/concerns/token_authenticatable_spec.rb | 4 +- .../encrypted_spec.rb | 77 +- .../encryption_helper_spec.rb | 27 + spec/models/deploy_keys_project_spec.rb | 6 +- spec/models/deploy_token_spec.rb | 2 +- spec/models/deployment_spec.rb | 115 +-- spec/models/environment_spec.rb | 33 +- spec/models/event_spec.rb | 8 +- spec/models/experiment_spec.rb | 29 +- spec/models/group_spec.rb | 367 ++++++-- spec/models/hooks/system_hook_spec.rb | 4 +- spec/models/hooks/web_hook_spec.rb | 8 +- spec/models/integration_spec.rb | 11 +- spec/models/internal_id_spec.rb | 61 ++ spec/models/issue/metrics_spec.rb | 14 +- spec/models/issue_spec.rb | 38 +- spec/models/key_spec.rb | 22 + spec/models/label_spec.rb | 22 +- spec/models/list_spec.rb | 13 - spec/models/member_spec.rb | 28 + spec/models/members/group_member_spec.rb | 6 + .../members/last_group_owner_assigner_spec.rb | 98 ++ spec/models/members/project_member_spec.rb | 4 + spec/models/merge_request_spec.rb | 136 +-- spec/models/namespace/admin_note_spec.rb | 16 + spec/models/namespace/traversal_hierarchy_spec.rb | 56 +- spec/models/namespace_setting_spec.rb | 31 + spec/models/namespace_spec.rb | 169 +++- spec/models/note_spec.rb | 105 ++- spec/models/notification_setting_spec.rb | 14 + spec/models/packages/debian/file_entry_spec.rb | 98 ++ spec/models/packages/dependency_spec.rb | 2 +- spec/models/packages/go/module_version_spec.rb | 14 +- spec/models/packages/maven/metadatum_spec.rb | 14 +- spec/models/packages/package_spec.rb | 41 +- spec/models/pages/lookup_path_spec.rb | 10 +- spec/models/pages_deployment_spec.rb | 26 +- spec/models/preloaders/labels_preloader_spec.rb | 52 ++ ..._max_access_level_in_projects_preloader_spec.rb | 37 + spec/models/project_feature_usage_spec.rb | 90 +- .../chat_message/alert_message_spec.rb | 1 + .../chat_message/merge_message_spec.rb | 26 +- .../emails_on_push_service_spec.rb | 46 + spec/models/project_services/jira_service_spec.rb | 89 +- .../project_services/jira_tracker_data_spec.rb | 13 +- .../project_services/prometheus_service_spec.rb | 2 + spec/models/project_services/slack_service_spec.rb | 8 + spec/models/project_spec.rb | 348 ++++++-- spec/models/protected_tag_spec.rb | 2 +- spec/models/raw_usage_data_spec.rb | 10 +- spec/models/release_highlight_spec.rb | 45 +- spec/models/release_spec.rb | 2 +- spec/models/remote_mirror_spec.rb | 24 + spec/models/repository_spec.rb | 83 +- spec/models/sent_notification_spec.rb | 12 +- spec/models/service_spec.rb | 10 + spec/models/sidebars/menu_spec.rb | 67 ++ spec/models/sidebars/panel_spec.rb | 34 + spec/models/sidebars/projects/context_spec.rb | 13 + .../projects/menus/learn_gitlab/menu_spec.rb | 31 + .../project_overview/menu_items/releases_spec.rb | 38 + .../projects/menus/project_overview/menu_spec.rb | 18 + .../projects/menus/repository/menu_spec.rb | 38 + spec/models/sidebars/projects/panel_spec.rb | 14 + spec/models/timelog_spec.rb | 12 +- spec/models/todo_spec.rb | 24 + spec/models/user_callout_spec.rb | 38 +- spec/models/user_spec.rb | 78 +- .../users/in_product_marketing_email_spec.rb | 131 +++ .../models/users/merge_request_interaction_spec.rb | 97 ++ .../application_setting/term_policy_spec.rb | 1 + spec/policies/ci/build_policy_spec.rb | 1 + .../design_management/design_policy_spec.rb | 3 + .../group_deploy_keys_group_policy_spec.rb | 1 + spec/policies/group_policy_spec.rb | 51 ++ spec/policies/project_policy_spec.rb | 52 +- spec/policies/project_snippet_policy_spec.rb | 1 + spec/policies/service_policy_spec.rb | 1 + spec/presenters/ci/build_runner_presenter_spec.rb | 87 +- spec/presenters/ci/trigger_presenter_spec.rb | 2 +- spec/presenters/clusters/cluster_presenter_spec.rb | 4 +- .../packages/composer/packages_presenter_spec.rb | 9 +- .../packages/conan/package_presenter_spec.rb | 2 +- .../packages/detail/package_presenter_spec.rb | 1 - spec/presenters/project_hook_presenter_spec.rb | 4 +- .../settings/deploy_keys_presenter_spec.rb | 45 +- .../admin/clusters/integrations_controller_spec.rb | 25 + spec/requests/api/api_spec.rb | 10 +- spec/requests/api/applications_spec.rb | 8 +- spec/requests/api/ci/runner/jobs_artifacts_spec.rb | 42 +- spec/requests/api/ci/runner/jobs_put_spec.rb | 16 +- .../api/ci/runner/jobs_request_post_spec.rb | 33 +- spec/requests/api/ci/runner/jobs_trace_spec.rb | 18 +- spec/requests/api/commit_statuses_spec.rb | 8 +- spec/requests/api/commits_spec.rb | 36 +- spec/requests/api/composer_packages_spec.rb | 1 + spec/requests/api/conan_project_packages_spec.rb | 2 +- spec/requests/api/deploy_keys_spec.rb | 31 +- spec/requests/api/deployments_spec.rb | 39 +- spec/requests/api/environments_spec.rb | 2 +- spec/requests/api/files_spec.rb | 15 + spec/requests/api/generic_packages_spec.rb | 33 +- spec/requests/api/go_proxy_spec.rb | 4 +- spec/requests/api/graphql/ci/groups_spec.rb | 24 +- spec/requests/api/graphql/ci/job_spec.rb | 100 +++ .../api/graphql/custom_emoji_query_spec.rb | 10 +- spec/requests/api/graphql/gitlab_schema_spec.rb | 22 +- spec/requests/api/graphql/group/milestones_spec.rb | 36 +- spec/requests/api/graphql/group/timelogs_spec.rb | 121 +++ spec/requests/api/graphql/group_query_spec.rb | 10 +- .../boards/issues/issue_move_list_spec.rb | 37 +- .../mutations/merge_requests/set_assignees_spec.rb | 55 +- .../mutations/merge_requests/set_labels_spec.rb | 2 +- .../mutations/release_asset_links/delete_spec.rb | 49 + .../api/graphql/mutations/snippets/create_spec.rb | 4 + .../api/graphql/mutations/snippets/update_spec.rb | 7 + spec/requests/api/graphql/packages/package_spec.rb | 121 ++- .../alert_management/alert/assignees_spec.rb | 2 +- .../project/alert_management/alert/notes_spec.rb | 2 +- .../project/alert_management/alert/todos_spec.rb | 2 +- .../project/alert_management/integrations_spec.rb | 98 +- .../graphql/project/container_repositories_spec.rb | 2 +- spec/requests/api/graphql/project/issues_spec.rb | 84 +- .../api/graphql/project/merge_request_spec.rb | 288 ++++-- .../api/graphql/project/merge_requests_spec.rb | 13 +- spec/requests/api/graphql/project/pipeline_spec.rb | 240 ++++- .../api/graphql/project/repository/blobs_spec.rb | 36 + spec/requests/api/graphql/user_spec.rb | 8 +- spec/requests/api/graphql_spec.rb | 95 +- spec/requests/api/group_import_spec.rb | 2 + spec/requests/api/group_milestones_spec.rb | 24 +- spec/requests/api/group_variables_spec.rb | 3 + spec/requests/api/internal/base_spec.rb | 37 +- spec/requests/api/internal/kubernetes_spec.rb | 18 +- spec/requests/api/invitations_spec.rb | 9 +- spec/requests/api/issue_links_spec.rb | 28 +- spec/requests/api/issues/get_group_issues_spec.rb | 2 +- .../api/issues/post_projects_issues_spec.rb | 2 +- spec/requests/api/jobs_spec.rb | 24 +- spec/requests/api/labels_spec.rb | 6 +- spec/requests/api/lint_spec.rb | 12 +- spec/requests/api/maven_packages_spec.rb | 724 +++++++++++---- spec/requests/api/members_spec.rb | 30 +- spec/requests/api/merge_request_diffs_spec.rb | 8 + spec/requests/api/merge_requests_spec.rb | 19 +- spec/requests/api/namespaces_spec.rb | 73 ++ spec/requests/api/notes_spec.rb | 2 +- spec/requests/api/npm_project_packages_spec.rb | 38 +- spec/requests/api/nuget_group_packages_spec.rb | 41 +- spec/requests/api/nuget_project_packages_spec.rb | 8 + spec/requests/api/project_attributes.yml | 1 + spec/requests/api/project_import_spec.rb | 4 +- spec/requests/api/projects_spec.rb | 172 +++- spec/requests/api/pypi_packages_spec.rb | 2 +- spec/requests/api/repositories_spec.rb | 23 + spec/requests/api/resource_access_tokens_spec.rb | 17 + spec/requests/api/settings_spec.rb | 7 +- spec/requests/api/tags_spec.rb | 230 +++-- spec/requests/api/triggers_spec.rb | 35 +- .../api/usage_data_non_sql_metrics_spec.rb | 67 ++ spec/requests/api/usage_data_queries_spec.rb | 67 ++ spec/requests/api/usage_data_spec.rb | 19 + spec/requests/api/users_preferences_spec.rb | 65 ++ spec/requests/api/users_spec.rb | 12 +- spec/requests/api/v3/github_spec.rb | 53 +- .../customers_dot/proxy_controller_spec.rb | 37 + .../clusters/integrations_controller_spec.rb | 40 + .../groups/email_campaigns_controller_spec.rb | 59 +- spec/requests/groups/milestones_controller_spec.rb | 24 +- spec/requests/ide_controller_spec.rb | 53 +- spec/requests/jwt_controller_spec.rb | 7 +- .../clusters/integrations_controller_spec.rb | 38 + .../projects/cycle_analytics_events_spec.rb | 4 +- .../projects/issue_links_controller_spec.rb | 2 +- .../projects/merge_requests_discussions_spec.rb | 4 + spec/requests/rack_attack_global_spec.rb | 25 + spec/requests/users_controller_spec.rb | 16 +- spec/requests/whats_new_controller_spec.rb | 11 - spec/routing/project_routing_spec.rb | 68 +- .../cop/gitlab/delegate_predicate_methods_spec.rb | 40 + .../cop/gitlab/feature_available_usage_spec.rb | 96 ++ spec/rubocop/cop/graphql/descriptions_spec.rb | 16 +- .../migration/add_limit_to_text_columns_spec.rb | 9 - spec/rubocop/cop/migration/add_timestamps_spec.rb | 8 - .../migration/complex_indexes_require_name_spec.rb | 10 - spec/rubocop/cop/migration/datetime_spec.rb | 20 - spec/rubocop/cop/migration/prevent_strings_spec.rb | 12 - .../cop/migration/refer_to_index_by_name_spec.rb | 4 - spec/rubocop/cop/migration/timestamps_spec.rb | 8 - spec/rubocop/cop/rspec/env_assignment_spec.rb | 4 +- .../style/regexp_literal_mixed_preserve_spec.rb | 131 +++ spec/rubocop/cop/user_admin_spec.rb | 24 + spec/serializers/admin/user_entity_spec.rb | 3 +- spec/serializers/admin/user_serializer_spec.rb | 2 +- spec/serializers/build_artifact_entity_spec.rb | 25 +- spec/serializers/ci/dag_pipeline_entity_spec.rb | 4 +- spec/serializers/ci/group_variable_entity_spec.rb | 2 +- spec/serializers/ci/lint/result_serializer_spec.rb | 1 + spec/serializers/ci/pipeline_entity_spec.rb | 3 + .../container_repository_entity_spec.rb | 1 + spec/serializers/container_tag_entity_spec.rb | 1 + spec/serializers/deployment_serializer_spec.rb | 1 + spec/serializers/diff_file_entity_spec.rb | 10 + spec/serializers/environment_serializer_spec.rb | 50 +- spec/serializers/evidences/evidence_entity_spec.rb | 1 + spec/serializers/fork_namespace_entity_spec.rb | 1 + .../group_link/group_group_link_entity_spec.rb | 1 + .../group_link/project_group_link_entity_spec.rb | 1 + spec/serializers/issue_board_entity_spec.rb | 1 + spec/serializers/member_entity_spec.rb | 1 + spec/serializers/member_serializer_spec.rb | 65 +- spec/serializers/member_user_entity_spec.rb | 1 + spec/serializers/merge_request_diff_entity_spec.rb | 1 + ...merge_request_poll_cached_widget_entity_spec.rb | 88 +- .../merge_request_poll_widget_entity_spec.rb | 146 ++- spec/serializers/merge_request_user_entity_spec.rb | 1 + .../merge_requests/pipeline_entity_spec.rb | 2 +- spec/serializers/namespace_basic_entity_spec.rb | 1 + spec/serializers/pipeline_details_entity_spec.rb | 1 + spec/serializers/pipeline_serializer_spec.rb | 21 +- spec/serializers/project_import_entity_spec.rb | 1 + spec/serializers/project_serializer_spec.rb | 1 + spec/serializers/review_app_setup_entity_spec.rb | 1 + spec/serializers/runner_entity_spec.rb | 1 + spec/serializers/service_event_entity_spec.rb | 4 +- spec/serializers/service_field_entity_spec.rb | 12 +- .../find_records_due_for_refresh_service_spec.rb | 295 ++++++ .../recalculate_for_user_range_service_spec.rb | 6 +- spec/services/award_emojis/add_service_spec.rb | 1 + spec/services/award_emojis/destroy_service_spec.rb | 1 + spec/services/award_emojis/toggle_service_spec.rb | 1 + spec/services/boards/destroy_service_spec.rb | 23 + spec/services/boards/issues/move_service_spec.rb | 1 + spec/services/boards/lists/list_service_spec.rb | 46 +- spec/services/boards/lists/update_service_spec.rb | 41 - .../bulk_create_integration_service_spec.rb | 2 +- .../bulk_update_integration_service_spec.rb | 4 +- spec/services/ci/abort_pipelines_service_spec.rb | 94 ++ .../ci/abort_project_pipelines_service_spec.rb | 42 - spec/services/ci/after_requeue_job_service_spec.rb | 38 + spec/services/ci/archive_trace_service_spec.rb | 46 + .../ci/cancel_user_pipelines_service_spec.rb | 35 - .../ci/create_downstream_pipeline_service_spec.rb | 4 +- .../ci/create_job_artifacts_service_spec.rb | 279 ------ .../cross_project_pipeline_spec.rb | 1 + .../ci/create_pipeline_service/needs_spec.rb | 2 +- .../parent_child_pipeline_spec.rb | 1 + .../ci/create_pipeline_service/rules_spec.rb | 152 +++- spec/services/ci/create_pipeline_service_spec.rb | 37 +- .../ci/create_web_ide_terminal_service_spec.rb | 19 + .../destroy_expired_job_artifacts_service_spec.rb | 252 ------ ...disable_user_pipeline_schedules_service_spec.rb | 19 + spec/services/ci/drop_pipeline_service_spec.rb | 60 ++ .../create_pipeline_service_spec.rb | 2 +- .../ci/job_artifacts/create_service_spec.rb | 279 ++++++ .../destroy_all_expired_service_spec.rb | 252 ++++++ .../ci/job_artifacts/destroy_batch_service_spec.rb | 81 ++ .../ci/job_artifacts_destroy_batch_service_spec.rb | 81 -- .../destroy_all_expired_service_spec.rb | 81 ++ .../destroy_expired_artifacts_service_spec.rb | 81 -- .../shared_processing_service_tests_with_yaml.rb | 8 +- ...t_two_manual_review_test_staging_production.yml | 171 ++++ .../dag_test_manual_allow_failure_false.yml | 2 +- ...st_manual_allow_failure_false_deploy_always.yml | 2 +- ...anual_allow_failure_false_deploy_on_failure.yml | 2 +- .../dag_test_manual_allow_failure_true.yml | 55 -- ...manual_allow_failure_true_deploy_on_failure.yml | 4 +- .../dag_test_manual_allow_failure_true_fails.yml | 66 ++ ...dag_test_manual_allow_failure_true_succeeds.yml | 66 ++ .../stage_test_manual_allow_failure_false.yml | 2 +- .../stage_test_manual_allow_failure_true.yml | 2 +- ...manual_allow_failure_true_deploy_on_failure.yml | 4 +- spec/services/ci/pipeline_trigger_service_spec.rb | 11 - spec/services/ci/play_bridge_service_spec.rb | 22 + spec/services/ci/play_build_service_spec.rb | 22 + spec/services/ci/process_build_service_spec.rb | 23 - spec/services/ci/process_pipeline_service_spec.rb | 52 +- spec/services/ci/register_job_service_spec.rb | 39 +- spec/services/ci/retry_build_service_spec.rb | 2 +- spec/services/ci/retry_pipeline_service_spec.rb | 2 +- .../ci/test_failure_history_service_spec.rb | 42 +- .../clusters/integrations/create_service_spec.rb | 90 ++ .../link_merge_requests_service_spec.rb | 11 + .../copy_design_collection/copy_service_spec.rb | 1 + .../delete_designs_service_spec.rb | 1 + .../design_management/save_designs_service_spec.rb | 3 +- spec/services/draft_notes/publish_service_spec.rb | 2 +- .../environments/auto_stop_service_spec.rb | 2 + .../canary_ingress/update_service_spec.rb | 1 + .../environments/reset_auto_stop_service_spec.rb | 1 + spec/services/groups/auto_devops_service_spec.rb | 1 + .../groups/group_links/update_service_spec.rb | 1 + .../groups/merge_requests_count_service_spec.rb | 29 + .../groups/open_issues_count_service_spec.rb | 48 +- spec/services/groups/transfer_service_spec.rb | 3 + .../groups/update_shared_runners_service_spec.rb | 1 + spec/services/ide/base_config_service_spec.rb | 1 + spec/services/ide/schemas_config_service_spec.rb | 1 + spec/services/ide/terminal_config_service_spec.rb | 3 + spec/services/issuable/destroy_service_spec.rb | 21 +- spec/services/issuable/process_assignees_spec.rb | 32 +- spec/services/issue_links/create_service_spec.rb | 6 +- spec/services/issues/after_create_service_spec.rb | 52 ++ spec/services/issues/build_service_spec.rb | 1 + spec/services/issues/clone_service_spec.rb | 1 + spec/services/issues/create_service_spec.rb | 28 +- spec/services/issues/export_csv_service_spec.rb | 1 + spec/services/issues/move_service_spec.rb | 1 + .../issues/related_branches_service_spec.rb | 1 + .../keys/expiry_notification_service_spec.rb | 97 ++ spec/services/members/create_service_spec.rb | 4 +- spec/services/members/destroy_service_spec.rb | 157 +++- spec/services/members/invite_service_spec.rb | 79 +- .../add_todo_when_build_fails_service_spec.rb | 6 +- .../merge_requests/after_create_service_spec.rb | 104 +++ spec/services/merge_requests/base_service_spec.rb | 1 + .../merge_requests/create_pipeline_service_spec.rb | 2 + .../services/merge_requests/create_service_spec.rb | 85 +- .../merge_requests/export_csv_service_spec.rb | 2 + .../handle_assignees_change_service_spec.rb | 114 +++ .../merge_orchestration_service_spec.rb | 1 + spec/services/merge_requests/merge_service_spec.rb | 15 +- .../merge_requests/merge_to_ref_service_spec.rb | 7 +- .../push_options_handler_service_spec.rb | 208 ++--- .../merge_requests/refresh_service_spec.rb | 16 +- .../merge_requests/resolve_todos_service_spec.rb | 49 + .../update_assignees_service_spec.rb | 79 ++ .../services/merge_requests/update_service_spec.rb | 244 ++--- spec/services/milestones/destroy_service_spec.rb | 14 +- .../merge_requests_count_service_spec.rb | 21 + spec/services/milestones/transfer_service_spec.rb | 8 +- .../namespace_settings/update_service_spec.rb | 31 + .../in_product_marketing_emails_service_spec.rb | 133 ++- spec/services/notes/create_service_spec.rb | 18 + .../builder/default_spec.rb | 146 ++- spec/services/notification_service_spec.rb | 47 +- .../packages/create_dependency_service_spec.rb | 2 +- .../extract_changes_metadata_service_spec.rb | 160 ++++ .../debian/extract_deb_metadata_service_spec.rb | 22 +- .../debian/extract_metadata_service_spec.rb | 10 +- .../debian/parse_debian822_service_spec.rb | 74 +- .../debian/process_changes_service_spec.rb | 58 ++ .../packages/go/create_package_service_spec.rb | 73 ++ .../packages/go/sync_packages_service_spec.rb | 40 + .../packages/maven/metadata/sync_service_spec.rb | 15 +- .../rubygems/create_dependencies_service_spec.rb | 33 + .../rubygems/create_gemspec_service_spec.rb | 28 + .../rubygems/metadata_extraction_service_spec.rb | 50 ++ .../packages/rubygems/process_gem_service_spec.rb | 134 +++ spec/services/pages/delete_service_spec.rb | 80 ++ spec/services/pages/delete_services_spec.rb | 76 -- .../migrate_from_legacy_storage_service_spec.rb | 157 ++-- ...te_legacy_storage_to_deployment_service_spec.rb | 48 +- spec/services/pages/zip_directory_service_spec.rb | 44 +- spec/services/projects/create_service_spec.rb | 14 +- spec/services/projects/destroy_service_spec.rb | 22 +- .../update_pages_configuration_service_spec.rb | 2 +- .../services/projects/update_pages_service_spec.rb | 19 +- .../projects/update_remote_mirror_service_spec.rb | 16 +- .../quick_actions/interpret_service_spec.rb | 200 +++-- .../repositories/changelog_service_spec.rb | 19 +- .../ci_configuration/sast_parser_service_spec.rb | 4 +- spec/services/spam/spam_action_service_spec.rb | 57 +- spec/services/submit_usage_ping_service_spec.rb | 35 +- spec/services/submodules/update_service_spec.rb | 2 +- spec/services/system_hooks_service_spec.rb | 28 - .../system_notes/alert_management_service_spec.rb | 2 +- spec/services/todo_service_spec.rb | 51 +- .../destroy/destroyed_issuable_service_spec.rb | 34 + .../todos/destroy/entity_leave_service_spec.rb | 27 +- spec/services/upload_service_spec.rb | 23 + .../user_preferences/update_service_spec.rb | 33 + .../refresh_authorized_projects_service_spec.rb | 164 ---- .../users/update_todo_count_cache_service_spec.rb | 61 ++ .../has_spam_action_response_fields_spec.rb | 8 +- spec/spec_helper.rb | 55 +- spec/support/helpers/board_helpers.rb | 9 - .../helpers/ci_artifact_metadata_generator.rb | 2 +- spec/support/helpers/cycle_analytics_helpers.rb | 8 +- spec/support/helpers/graphql_helpers.rb | 16 +- .../support/helpers/javascript_fixtures_helpers.rb | 2 +- spec/support/helpers/jira_service_helper.rb | 2 +- spec/support/helpers/key_generator_helper.rb | 3 +- spec/support/helpers/navbar_structure_helper.rb | 12 +- spec/support/helpers/next_instance_of.rb | 15 +- spec/support/helpers/query_recorder.rb | 40 +- spec/support/helpers/reload_helpers.rb | 12 + spec/support/helpers/rubygems_helpers.rb | 11 + spec/support/helpers/seed_repo.rb | 60 +- spec/support/helpers/stub_env.rb | 2 +- spec/support/helpers/stub_requests.rb | 2 +- spec/support/helpers/test_env.rb | 11 +- spec/support/helpers/usage_data_helpers.rb | 24 + .../import_export/project_tree_expectations.rb | 4 +- spec/support/matchers/exceed_query_limit.rb | 5 + spec/support/matchers/graphql_matchers.rb | 14 +- .../track_self_describing_event_matcher.rb | 12 - .../shared_contexts/email_shared_context.rb | 11 +- .../structured_logger_shared_context.rb | 87 ++ .../server_metrics_shared_context.rb | 92 ++ .../shared_contexts/navbar_structure_context.rb | 5 +- .../project_service_jira_context.rb | 5 +- .../project_service_shared_context.rb | 5 +- .../requests/api/conan_packages_shared_context.rb | 7 - .../requests/api/go_modules_shared_context.rb | 14 + .../delete_tags_service_shared_context.rb | 2 +- .../shared_contexts/services_shared_context.rb | 2 + .../boards/destroy_service_shared_examples.rb | 30 + .../boards/lists/update_service_shared_examples.rb | 43 + .../controllers/snippet_blob_shared_examples.rb | 10 - .../controllers/snippet_shared_examples.rb | 28 + .../controllers/trackable_shared_examples.rb | 39 - .../controllers/unique_visits_shared_examples.rb | 11 +- .../features/cascading_settings_shared_examples.rb | 41 + .../creatable_merge_request_shared_examples.rb | 2 +- .../features/discussion_comments_shared_example.rb | 4 +- .../features/error_tracking_shared_example.rb | 2 +- .../issuable_invite_members_shared_examples.rb | 6 +- .../project_upload_files_shared_examples.rb | 73 +- ...olving_discussions_in_issues_shared_examples.rb | 6 +- .../features/search_settings_shared_examples.rb | 27 +- .../features/sidebar_shared_examples.rb | 165 ++++ .../wiki/user_creates_wiki_page_shared_examples.rb | 2 +- .../user_git_access_wiki_page_shared_examples.rb | 2 +- .../user_previews_wiki_changes_shared_examples.rb | 6 +- .../wiki/user_updates_wiki_page_shared_examples.rb | 6 +- .../wiki/user_views_wiki_empty_shared_examples.rb | 6 +- .../mutations/can_mutate_spammable_examples.rb | 6 +- .../mutations/set_assignees_shared_examples.rb | 28 +- .../sorted_paginated_query_shared_examples.rb | 12 +- .../graphql/spam_protection_shared_examples.rb | 85 ++ .../gitlab_style_deprecations_shared_examples.rb | 24 +- .../helpers/groups_shared_examples.rb | 53 ++ .../lib/api/ci/runner_shared_examples.rb | 2 +- .../lib/api/internal_base_shared_examples.rb | 11 - .../lib/gitlab/ci/ci_trace_shared_examples.rb | 11 + .../database/cte_materialized_shared_examples.rb | 44 + ...ware_with_worker_attribution_shared_examples.rb | 132 +++ .../lib/gitlab/sql/set_operator_shared_examples.rb | 29 + .../issuable_activity_shared_examples.rb | 4 - .../mailers/notify_shared_examples.rb | 2 +- .../active_record_subscriber_shared_examples.rb | 14 +- .../models/boards/listable_shared_examples.rb | 25 +- .../cluster_application_status_shared_examples.rb | 7 +- .../cluster_application_version_shared_examples.rb | 11 + .../models/clusters/prometheus_client_shared.rb | 86 ++ .../debian/architecture_shared_examples.rb | 4 +- .../packages/debian/component_shared_examples.rb | 4 +- .../debian/distribution_shared_examples.rb | 4 +- .../shared_examples/models/wiki_shared_examples.rb | 95 +- .../namespaces/namespace_traversal_examples.rb | 124 +++ .../namespaces/recursive_traversal_examples.rb | 78 -- .../shared_examples/nav_sidebar_shared_examples.rb | 10 + .../resource_access_token_shared_examples.rb | 76 +- .../shared_examples/querying_shared_examples.rb | 23 + .../issuable/close_quick_action_shared_examples.rb | 2 +- .../requests/api/conan_packages_shared_examples.rb | 29 +- .../integrations_shared_examples.rb | 49 + .../logging_application_context_shared_examples.rb | 10 +- .../api/nuget_endpoints_shared_examples.rb | 4 +- .../requests/api/packages_shared_examples.rb | 13 +- .../api/rubygems_packages_shared_examples.rb | 13 + .../integrations_controller_shared_examples.rb | 46 + .../environment_serializer_shared_examples.rb | 29 + .../lists_destroy_service_shared_examples.rb | 4 +- .../boards/lists_list_service_shared_examples.rb | 38 +- ...luster_applications_artifact_shared_examples.rb | 4 +- .../groups_count_service_shared_examples.rb | 55 ++ .../issuable/destroy_service_shared_examples.rb | 31 + .../services/merge_request_shared_examples.rb | 90 ++ .../notification_service_shared_examples.rb | 2 +- .../services/snippets_shared_examples.rb | 41 +- .../in_product_marketing_email_shared_example.rb | 15 + .../worker_with_data_consistency_shared_example.rb | 27 + spec/support/sidekiq_middleware.rb | 18 +- .../helpers/active_record/query_recorder_spec.rb | 70 +- .../matchers/exceed_query_limit_helpers_spec.rb | 10 + spec/tasks/gitlab/gitaly_rake_spec.rb | 11 +- spec/tasks/gitlab/pages_rake_spec.rb | 116 ++- spec/tasks/gitlab/usage_data_rake_spec.rb | 16 +- spec/tooling/danger/changelog_spec.rb | 153 +++- spec/tooling/danger/project_helper_spec.rb | 38 +- spec/tooling/lib/tooling/kubernetes_client_spec.rb | 10 + spec/tooling/merge_request_spec.rb | 31 + spec/tooling/quality/test_level_spec.rb | 225 +++++ spec/uploaders/object_storage_spec.rb | 19 + spec/validators/json_schema_validator_spec.rb | 30 - .../x509_certificate_credentials_validator_spec.rb | 4 +- spec/views/admin/dashboard/index.html.haml_spec.rb | 2 +- spec/views/admin/services/index.html.haml_spec.rb | 30 - .../dashboard/projects/index.html.haml_spec.rb | 28 + .../groups/settings/_remove.html.haml_spec.rb | 17 + spec/views/layouts/_search.html.haml_spec.rb | 68 ++ .../layouts/header/_new_dropdown.haml_spec.rb | 1 + .../layouts/nav/sidebar/_group.html.haml_spec.rb | 1 + .../layouts/nav/sidebar/_profile.html.haml_spec.rb | 1 + .../layouts/nav/sidebar/_project.html.haml_spec.rb | 132 ++- spec/views/layouts/profile.html.haml_spec.rb | 19 +- spec/views/profiles/keys/_form.html.haml_spec.rb | 50 ++ spec/views/profiles/keys/_key.html.haml_spec.rb | 123 +++ .../projects/commit/_commit_box.html.haml_spec.rb | 34 - spec/views/projects/empty.html.haml_spec.rb | 10 + .../projects/pipelines/_stage.html.haml_spec.rb | 73 -- .../settings/operations/show.html.haml_spec.rb | 4 +- spec/views/projects/tags/index.html.haml_spec.rb | 1 + .../registrations/welcome/show.html.haml_spec.rb | 26 +- spec/views/search/_results.html.haml_spec.rb | 15 + spec/views/shared/nav/_sidebar.html.haml_spec.rb | 44 + spec/views/shared/runners/show.html.haml_spec.rb | 6 +- .../user_refresh_over_user_range_worker_spec.rb | 63 +- spec/workers/background_migration_worker_spec.rb | 2 +- spec/workers/build_finished_worker_spec.rb | 38 +- spec/workers/build_hooks_worker_spec.rb | 5 + spec/workers/bulk_import_worker_spec.rb | 22 +- spec/workers/bulk_imports/entity_worker_spec.rb | 124 ++- spec/workers/bulk_imports/pipeline_worker_spec.rb | 125 +++ spec/workers/ci/drop_pipeline_worker_spec.rb | 36 + .../ci/initial_pipeline_process_worker_spec.rb | 21 + .../add_todo_when_build_fails_worker_spec.rb | 53 ++ .../expire_artifacts_worker_spec.rb | 2 +- .../workers/ci/test_failure_history_worker_spec.rb | 4 +- spec/workers/concerns/worker_attributes_spec.rb | 74 ++ spec/workers/concerns/worker_context_spec.rb | 2 +- .../container_expiration_policy_worker_spec.rb | 123 ++- .../batched_background_migration_worker_spec.rb | 121 +++ spec/workers/every_sidekiq_worker_spec.rb | 2 +- spec/workers/expire_build_artifacts_worker_spec.rb | 2 +- spec/workers/expire_job_cache_worker_spec.rb | 21 +- spec/workers/expire_pipeline_cache_worker_spec.rb | 17 + .../merge_requests/assignees_change_worker_spec.rb | 59 ++ .../merge_requests/create_pipeline_worker_spec.rb | 61 ++ .../handle_assignees_change_worker_spec.rb | 62 ++ .../merge_requests/resolve_todos_worker_spec.rb | 41 + .../in_product_marketing_emails_worker_spec.rb | 56 +- spec/workers/new_issue_worker_spec.rb | 11 +- spec/workers/new_merge_request_worker_spec.rb | 2 +- .../packages/go/sync_packages_worker_spec.rb | 101 +++ .../packages/maven/metadata/sync_worker_spec.rb | 6 +- .../packages/rubygems/extraction_worker_spec.rb | 54 ++ .../pages_update_configuration_worker_spec.rb | 2 +- spec/workers/post_receive_spec.rb | 5 +- spec/workers/projects/post_creation_worker_spec.rb | 86 ++ spec/workers/remove_expired_members_worker_spec.rb | 18 + .../repository_check/dispatch_worker_spec.rb | 7 + .../ssh_keys/expired_notification_worker_spec.rb | 58 ++ .../expiring_soon_notification_worker_spec.rb | 66 ++ .../destroyed_issuable_worker_spec.rb | 15 + 1773 files changed, 55087 insertions(+), 23078 deletions(-) create mode 100644 spec/config/metrics/aggregates/aggregated_metrics_spec.rb create mode 100644 spec/controllers/groups/settings/applications_controller_spec.rb delete mode 100644 spec/experiments/strategy/round_robin_spec.rb delete mode 100644 spec/factories/ci/test_case.rb delete mode 100644 spec/factories/ci/test_case_failure.rb create mode 100644 spec/factories/ci/unit_test.rb create mode 100644 spec/factories/ci/unit_test_failure.rb create mode 100644 spec/factories/clusters/integrations/prometheus.rb create mode 100644 spec/factories/users/in_product_marketing_email.rb delete mode 100644 spec/features/admin/services/admin_activates_prometheus_spec.rb delete mode 100644 spec/features/boards/add_issues_modal_spec.rb delete mode 100644 spec/features/boards/modal_filter_spec.rb create mode 100644 spec/features/boards/sidebar_assignee_spec.rb create mode 100644 spec/features/boards/sidebar_due_date_spec.rb create mode 100644 spec/features/boards/sidebar_labels_spec.rb create mode 100644 spec/features/boards/sidebar_milestones_spec.rb create mode 100644 spec/features/callouts/service_templates_deprecation_spec.rb create mode 100644 spec/features/file_uploads/attachment_spec.rb create mode 100644 spec/features/file_uploads/rubygem_package_spec.rb create mode 100644 spec/features/gitlab_experiments_spec.rb create mode 100644 spec/features/issues/user_invites_from_a_comment_spec.rb create mode 100644 spec/features/merge_request/user_invites_from_a_comment_spec.rb create mode 100644 spec/features/projects/blobs/user_views_pipeline_editor_button_spec.rb delete mode 100644 spec/features/projects/services/user_activates_hipchat_spec.rb create mode 100644 spec/features/registrations/welcome_spec.rb create mode 100644 spec/finders/concerns/finder_with_group_hierarchy_spec.rb create mode 100644 spec/finders/environments_by_deployments_finder_spec.rb create mode 100644 spec/finders/packages/go/package_finder_spec.rb create mode 100644 spec/finders/repositories/branch_names_finder_spec.rb create mode 100644 spec/finders/repositories/changelog_tag_finder_spec.rb delete mode 100644 spec/finders/repositories/previous_tag_finder_spec.rb create mode 100644 spec/fixtures/api/schemas/graphql/packages/package_conan_metadata.json create mode 100644 spec/fixtures/ce_sample_schema.json create mode 100644 spec/fixtures/config/mail_room_enabled_ms_graph.yml create mode 100644 spec/fixtures/emails/update_commands_only.eml create mode 100644 spec/fixtures/lib/generators/gitlab/usage_metric_definition_generator/sample_metric.yml create mode 100644 spec/fixtures/lib/generators/gitlab/usage_metric_definition_generator/sample_metric_with_name_suggestions.yml create mode 100644 spec/fixtures/packages/rubygems/package.gem create mode 100644 spec/fixtures/unsafe_javascript.xml create mode 100644 spec/frontend/__helpers__/web_worker_fake.js delete mode 100644 spec/frontend/__helpers__/web_worker_mock.js create mode 100644 spec/frontend/__helpers__/web_worker_transformer.js create mode 100644 spec/frontend/__mocks__/vue/index.js create mode 100644 spec/frontend/admin/signup_restrictions/components/signup_checkbox_spec.js create mode 100644 spec/frontend/admin/signup_restrictions/components/signup_form_spec.js create mode 100644 spec/frontend/admin/signup_restrictions/mock_data.js create mode 100644 spec/frontend/admin/signup_restrictions/utils.js create mode 100644 spec/frontend/admin/signup_restrictions/utils_spec.js create mode 100644 spec/frontend/admin/users/new_spec.js delete mode 100644 spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap create mode 100644 spec/frontend/behaviors/markdown/render_mermaid_spec.js create mode 100644 spec/frontend/blob/file_template_selector_spec.js create mode 100644 spec/frontend/boards/components/__snapshots__/board_blocked_icon_spec.js.snap create mode 100644 spec/frontend/boards/components/board_blocked_icon_spec.js create mode 100644 spec/frontend/boards/components/board_content_sidebar_spec.js delete mode 100644 spec/frontend/boards/components/filtered_search_spec.js delete mode 100644 spec/frontend/boards/components/sidebar/board_sidebar_issue_title_spec.js create mode 100644 spec/frontend/boards/components/sidebar/board_sidebar_time_tracker_spec.js create mode 100644 spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js delete mode 100644 spec/frontend/boards/modal_store_spec.js create mode 100644 spec/frontend/branches/components/sort_dropdown_spec.js create mode 100644 spec/frontend/captcha/apollo_captcha_link_spec.js create mode 100644 spec/frontend/cascading_settings/components/lock_popovers_spec.js create mode 100644 spec/frontend/content_editor/components/content_editor_spec.js create mode 100644 spec/frontend/content_editor/markdown_processing_examples.js create mode 100644 spec/frontend/content_editor/markdown_processing_spec.js create mode 100644 spec/frontend/content_editor/services/create_editor_spec.js create mode 100644 spec/frontend/delete_label_modal_spec.js create mode 100644 spec/frontend/deploy_tokens/components/revoke_button_spec.js create mode 100644 spec/frontend/diffs/find_interop_attributes.js create mode 100644 spec/frontend/diffs/utils/interoperability_spec.js create mode 100644 spec/frontend/emoji/awards_app/store/actions_spec.js create mode 100644 spec/frontend/emoji/awards_app/store/mutations_spec.js create mode 100644 spec/frontend/experimentation/components/experiment_spec.js create mode 100644 spec/frontend/fixtures/api_markdown.rb create mode 100644 spec/frontend/fixtures/api_markdown.yml create mode 100644 spec/frontend/fixtures/autocomplete.rb delete mode 100644 spec/frontend/fixtures/static/mini_dropdown_graph.html create mode 100644 spec/frontend/ide/components/cannot_push_code_alert_spec.js create mode 100644 spec/frontend/integrations/edit/components/jira_upgrade_cta_spec.js create mode 100644 spec/frontend/integrations/index/components/integrations_list_spec.js create mode 100644 spec/frontend/integrations/index/components/integrations_table_spec.js create mode 100644 spec/frontend/integrations/index/mock_data.js create mode 100644 spec/frontend/issuable_type_selector/components/__snapshots__/info_popover_spec.js.snap create mode 100644 spec/frontend/issuable_type_selector/components/info_popover_spec.js create mode 100644 spec/frontend/jira_connect/components/__snapshots__/group_item_name_spec.js.snap create mode 100644 spec/frontend/jira_connect/components/group_item_name_spec.js create mode 100644 spec/frontend/jira_connect/components/subscriptions_list_spec.js create mode 100644 spec/frontend/jobs/components/table/jobs_table_spec.js create mode 100644 spec/frontend/jobs/components/table/jobs_table_tabs_spec.js create mode 100644 spec/frontend/merge_conflicts/components/merge_conflict_resolver_app_spec.js create mode 100644 spec/frontend/merge_conflicts/mock_data.js create mode 100644 spec/frontend/merge_conflicts/store/getters_spec.js create mode 100644 spec/frontend/merge_conflicts/store/mutations_spec.js create mode 100644 spec/frontend/merge_conflicts/utils_spec.js delete mode 100644 spec/frontend/mini_pipeline_graph_dropdown_spec.js delete mode 100644 spec/frontend/mocks/ce/diffs/workers/tree_worker.js delete mode 100644 spec/frontend/mocks/ce/ide/lib/diff/diff_worker.js create mode 100644 spec/frontend/mr_notes/stores/actions_spec.js create mode 100644 spec/frontend/mr_notes/stores/mutations_spec.js create mode 100644 spec/frontend/packages/shared/components/package_icon_and_name_spec.js create mode 100644 spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_icon_and_name_spec.js create mode 100644 spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_search_spec.js create mode 100644 spec/frontend/packages_and_registries/infrastructure_registry/components/infrastructure_title_spec.js create mode 100644 spec/frontend/packages_and_registries/shared/utils_spec.js delete mode 100644 spec/frontend/pages/admin/users/new/index_spec.js create mode 100644 spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_section_card_spec.js.snap create mode 100644 spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_card_spec.js create mode 100644 spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js create mode 100644 spec/frontend/pages/shared/wikis/components/wiki_alert_spec.js create mode 100644 spec/frontend/pages/shared/wikis/components/wiki_form_spec.js delete mode 100644 spec/frontend/pages/shared/wikis/wiki_alert_spec.js create mode 100644 spec/frontend/pipeline_editor/components/code_snippet_alert/code_snippet_alert_spec.js create mode 100644 spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js create mode 100644 spec/frontend/pipeline_editor/components/file-nav/pipeline_editor_file_nav_spec.js delete mode 100644 spec/frontend/pipelines/blank_state_spec.js create mode 100644 spec/frontend/pipelines/notification/pipeline_notification_spec.js create mode 100644 spec/frontend/pipelines/pipelines_ci_templates_spec.js delete mode 100644 spec/frontend/pipelines/pipelines_table_row_spec.js create mode 100644 spec/frontend/projects/commit/components/commit_comments_button_spec.js create mode 100644 spec/frontend/projects/commit/components/commit_options_dropdown_spec.js delete mode 100644 spec/frontend/projects/commit/components/form_trigger_spec.js create mode 100644 spec/frontend/repository/components/blob_content_viewer_spec.js create mode 100644 spec/frontend/repository/pages/blob_spec.js create mode 100644 spec/frontend/runner/runner_detail/runner_detail_app_spec.js create mode 100644 spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js create mode 100644 spec/frontend/sidebar/components/assignees/sidebar_invite_members_spec.js create mode 100644 spec/frontend/sidebar/components/assignees/sidebar_participant_spec.js create mode 100644 spec/frontend/sidebar/components/due_date/sidebar_due_date_widget_spec.js create mode 100644 spec/frontend/tags/components/sort_dropdown_spec.js create mode 100644 spec/frontend/users_select/index_spec.js create mode 100644 spec/frontend/vue_shared/components/delete_label_modal_spec.js delete mode 100644 spec/frontend/vue_shared/components/deprecated_modal_spec.js create mode 100644 spec/frontend/vue_shared/components/ensure_data_spec.js create mode 100644 spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js create mode 100644 spec/frontend/vue_shared/components/filtered_search_bar/tokens/epic_token_spec.js delete mode 100644 spec/frontend/vue_shared/components/gl_toggle_vuex_spec.js create mode 100644 spec/frontend/vue_shared/components/lib/utils/props_utils_spec.js delete mode 100644 spec/frontend/vue_shared/components/recaptcha_eventhub_spec.js delete mode 100644 spec/frontend/vue_shared/components/recaptcha_modal_spec.js create mode 100644 spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js create mode 100644 spec/frontend/vue_shared/components/sidebar/copyable_field_spec.js create mode 100644 spec/frontend/vue_shared/components/url_sync_spec.js create mode 100644 spec/frontend/vue_shared/oncall_schedules_list_spec.js create mode 100644 spec/frontend_integration/diffs/diffs_interopability_api.js create mode 100644 spec/frontend_integration/diffs/diffs_interopability_spec.js create mode 100644 spec/frontend_integration/test_helpers/mock_server/routes/diffs.js delete mode 100644 spec/graphql/mutations/concerns/mutations/can_mutate_spammable_spec.rb create mode 100644 spec/graphql/mutations/release_asset_links/delete_spec.rb create mode 100644 spec/graphql/resolvers/blobs_resolver_spec.rb create mode 100644 spec/graphql/resolvers/ci/test_report_summary_resolver_spec.rb create mode 100644 spec/graphql/resolvers/ci/test_suite_resolver_spec.rb create mode 100644 spec/graphql/resolvers/project_jobs_resolver_spec.rb create mode 100644 spec/graphql/resolvers/repository_branch_names_resolver_spec.rb create mode 100644 spec/graphql/resolvers/timelog_resolver_spec.rb create mode 100644 spec/graphql/types/base_object_spec.rb create mode 100644 spec/graphql/types/ci/job_status_enum_spec.rb create mode 100644 spec/graphql/types/ci/recent_failures_type_spec.rb create mode 100644 spec/graphql/types/ci/test_case_status_enum_spec.rb create mode 100644 spec/graphql/types/ci/test_case_type_spec.rb create mode 100644 spec/graphql/types/ci/test_report_summary_type_spec.rb create mode 100644 spec/graphql/types/ci/test_report_total_type_spec.rb create mode 100644 spec/graphql/types/ci/test_suite_summary_type_spec.rb create mode 100644 spec/graphql/types/ci/test_suite_type_spec.rb create mode 100644 spec/graphql/types/merge_request_review_state_enum_spec.rb create mode 100644 spec/graphql/types/merge_requests/reviewer_type_spec.rb create mode 100644 spec/graphql/types/packages/conan/file_metadatum_type_spec.rb create mode 100644 spec/graphql/types/packages/conan/metadatum_file_type_enum_spec.rb create mode 100644 spec/graphql/types/packages/conan/metadatum_type_spec.rb create mode 100644 spec/graphql/types/packages/package_details_type_spec.rb create mode 100644 spec/graphql/types/packages/package_file_type_spec.rb delete mode 100644 spec/graphql/types/packages/package_without_versions_type_spec.rb create mode 100644 spec/graphql/types/repository/blob_type_spec.rb create mode 100644 spec/graphql/types/timelog_type_spec.rb create mode 100644 spec/graphql/types/user_merge_request_interaction_type_spec.rb create mode 100644 spec/helpers/sidebars_helper_spec.rb delete mode 100644 spec/initializers/json_validator_patch_spec.rb delete mode 100644 spec/initializers/kramdown_patch_spec.rb create mode 100644 spec/initializers/pages_storage_check_spec.rb create mode 100644 spec/knapsack_env.rb create mode 100644 spec/lib/api/entities/clusters/agent_spec.rb create mode 100644 spec/lib/api/helpers/caching_spec.rb create mode 100644 spec/lib/api/helpers/variables_helpers_spec.rb delete mode 100644 spec/lib/banzai/filter/abstract_reference_filter_spec.rb delete mode 100644 spec/lib/banzai/filter/alert_reference_filter_spec.rb delete mode 100644 spec/lib/banzai/filter/commit_range_reference_filter_spec.rb delete mode 100644 spec/lib/banzai/filter/commit_reference_filter_spec.rb delete mode 100644 spec/lib/banzai/filter/design_reference_filter_spec.rb delete mode 100644 spec/lib/banzai/filter/external_issue_reference_filter_spec.rb delete mode 100644 spec/lib/banzai/filter/feature_flag_reference_filter_spec.rb delete mode 100644 spec/lib/banzai/filter/issue_reference_filter_spec.rb delete mode 100644 spec/lib/banzai/filter/label_reference_filter_spec.rb delete mode 100644 spec/lib/banzai/filter/merge_request_reference_filter_spec.rb delete mode 100644 spec/lib/banzai/filter/milestone_reference_filter_spec.rb delete mode 100644 spec/lib/banzai/filter/project_reference_filter_spec.rb delete mode 100644 spec/lib/banzai/filter/reference_filter_spec.rb create mode 100644 spec/lib/banzai/filter/references/abstract_reference_filter_spec.rb create mode 100644 spec/lib/banzai/filter/references/alert_reference_filter_spec.rb create mode 100644 spec/lib/banzai/filter/references/commit_range_reference_filter_spec.rb create mode 100644 spec/lib/banzai/filter/references/commit_reference_filter_spec.rb create mode 100644 spec/lib/banzai/filter/references/design_reference_filter_spec.rb create mode 100644 spec/lib/banzai/filter/references/external_issue_reference_filter_spec.rb create mode 100644 spec/lib/banzai/filter/references/feature_flag_reference_filter_spec.rb create mode 100644 spec/lib/banzai/filter/references/issue_reference_filter_spec.rb create mode 100644 spec/lib/banzai/filter/references/label_reference_filter_spec.rb create mode 100644 spec/lib/banzai/filter/references/merge_request_reference_filter_spec.rb create mode 100644 spec/lib/banzai/filter/references/milestone_reference_filter_spec.rb create mode 100644 spec/lib/banzai/filter/references/project_reference_filter_spec.rb create mode 100644 spec/lib/banzai/filter/references/reference_filter_spec.rb create mode 100644 spec/lib/banzai/filter/references/snippet_reference_filter_spec.rb create mode 100644 spec/lib/banzai/filter/references/user_reference_filter_spec.rb delete mode 100644 spec/lib/banzai/filter/snippet_reference_filter_spec.rb delete mode 100644 spec/lib/banzai/filter/user_reference_filter_spec.rb create mode 100644 spec/lib/bulk_imports/common/extractors/rest_extractor_spec.rb create mode 100644 spec/lib/bulk_imports/groups/pipelines/badges_pipeline_spec.rb create mode 100644 spec/lib/bulk_imports/groups/pipelines/entity_finisher_spec.rb create mode 100644 spec/lib/bulk_imports/groups/rest/get_badges_query_spec.rb delete mode 100644 spec/lib/bulk_imports/importers/group_importer_spec.rb create mode 100644 spec/lib/generators/gitlab/usage_metric_definition/redis_hll_generator_spec.rb create mode 100644 spec/lib/gitlab/background_migration/migrate_pages_to_zip_storage_spec.rb create mode 100644 spec/lib/gitlab/bullet/exclusions_spec.rb create mode 100644 spec/lib/gitlab/bullet_spec.rb create mode 100644 spec/lib/gitlab/ci/pipeline/chain/helpers_spec.rb create mode 100644 spec/lib/gitlab/ci/pipeline/chain/pipeline/process_spec.rb create mode 100644 spec/lib/gitlab/database/background_migration/batch_metrics_spec.rb create mode 100644 spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb delete mode 100644 spec/lib/gitlab/database/background_migration/scheduler_spec.rb create mode 100644 spec/lib/gitlab/database/loose_index_scan_distinct_count_spec.rb create mode 100644 spec/lib/gitlab/database/pg_class_spec.rb create mode 100644 spec/lib/gitlab/database/unidirectional_copy_trigger_spec.rb delete mode 100644 spec/lib/gitlab/downtime_check/message_spec.rb delete mode 100644 spec/lib/gitlab/downtime_check_spec.rb delete mode 100644 spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb create mode 100644 spec/lib/gitlab/graphql/authorize/object_authorization_spec.rb create mode 100644 spec/lib/gitlab/graphql/deprecation_spec.rb create mode 100644 spec/lib/gitlab/graphql/negatable_arguments_spec.rb create mode 100644 spec/lib/gitlab/hook_data/user_builder_spec.rb create mode 100644 spec/lib/gitlab/middleware/rack_multipart_tempfile_factory_spec.rb create mode 100644 spec/lib/gitlab/pages/stores/local_store_spec.rb create mode 100644 spec/lib/gitlab/pagination/offset_header_builder_with_controller_spec.rb create mode 100644 spec/lib/gitlab/sidekiq_config/worker_matcher_spec.rb create mode 100644 spec/lib/gitlab/sidekiq_middleware/instrumentation_logger_spec.rb create mode 100644 spec/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/joins_spec.rb delete mode 100644 spec/lib/gitlab/usage_data_counters/aggregated_metrics_spec.rb create mode 100644 spec/lib/gitlab/usage_data_non_sql_metrics_spec.rb create mode 100644 spec/lib/kramdown/kramdown_spec.rb delete mode 100644 spec/lib/quality/test_level_spec.rb create mode 100644 spec/lib/rouge/formatters/html_gitlab_spec.rb create mode 100644 spec/migrations/20210226141517_dedup_issue_metrics_spec.rb create mode 100644 spec/migrations/20210406144743_backfill_total_tuple_count_for_batched_migrations_spec.rb create mode 100644 spec/migrations/20210413132500_reschedule_artifact_expiry_backfill_again_spec.rb create mode 100644 spec/migrations/add_new_trail_plans_spec.rb create mode 100644 spec/migrations/confirm_support_bot_user_spec.rb create mode 100644 spec/migrations/delete_security_findings_without_uuid_spec.rb create mode 100644 spec/migrations/migrate_elastic_index_settings_spec.rb create mode 100644 spec/migrations/move_container_registry_enabled_to_project_features2_spec.rb delete mode 100644 spec/migrations/move_container_registry_enabled_to_project_features_spec.rb create mode 100644 spec/migrations/populate_dismissal_information_for_vulnerabilities_spec.rb create mode 100644 spec/migrations/remove_records_without_group_from_webhooks_table_spec.rb create mode 100644 spec/migrations/schedule_migrate_pages_to_zip_storage_spec.rb delete mode 100644 spec/models/audit_event_archived_spec.rb create mode 100644 spec/models/bulk_imports/stage_spec.rb delete mode 100644 spec/models/ci/test_case_failure_spec.rb delete mode 100644 spec/models/ci/test_case_spec.rb create mode 100644 spec/models/ci/unit_test_failure_spec.rb create mode 100644 spec/models/ci/unit_test_spec.rb create mode 100644 spec/models/clusters/integrations/prometheus_spec.rb create mode 100644 spec/models/concerns/cascading_namespace_setting_attribute_spec.rb create mode 100644 spec/models/concerns/has_timelogs_report_spec.rb create mode 100644 spec/models/concerns/sidebars/container_with_html_options_spec.rb create mode 100644 spec/models/concerns/sidebars/positionable_list_spec.rb create mode 100644 spec/models/concerns/token_authenticatable_strategies/encryption_helper_spec.rb create mode 100644 spec/models/members/last_group_owner_assigner_spec.rb create mode 100644 spec/models/namespace/admin_note_spec.rb create mode 100644 spec/models/packages/debian/file_entry_spec.rb create mode 100644 spec/models/preloaders/labels_preloader_spec.rb create mode 100644 spec/models/preloaders/user_max_access_level_in_projects_preloader_spec.rb create mode 100644 spec/models/sidebars/menu_spec.rb create mode 100644 spec/models/sidebars/panel_spec.rb create mode 100644 spec/models/sidebars/projects/context_spec.rb create mode 100644 spec/models/sidebars/projects/menus/learn_gitlab/menu_spec.rb create mode 100644 spec/models/sidebars/projects/menus/project_overview/menu_items/releases_spec.rb create mode 100644 spec/models/sidebars/projects/menus/project_overview/menu_spec.rb create mode 100644 spec/models/sidebars/projects/menus/repository/menu_spec.rb create mode 100644 spec/models/sidebars/projects/panel_spec.rb create mode 100644 spec/models/users/in_product_marketing_email_spec.rb create mode 100644 spec/models/users/merge_request_interaction_spec.rb create mode 100644 spec/requests/admin/clusters/integrations_controller_spec.rb create mode 100644 spec/requests/api/graphql/ci/job_spec.rb create mode 100644 spec/requests/api/graphql/group/timelogs_spec.rb create mode 100644 spec/requests/api/graphql/mutations/release_asset_links/delete_spec.rb create mode 100644 spec/requests/api/graphql/project/repository/blobs_spec.rb create mode 100644 spec/requests/api/usage_data_non_sql_metrics_spec.rb create mode 100644 spec/requests/api/usage_data_queries_spec.rb create mode 100644 spec/requests/api/users_preferences_spec.rb create mode 100644 spec/requests/customers_dot/proxy_controller_spec.rb create mode 100644 spec/requests/groups/clusters/integrations_controller_spec.rb create mode 100644 spec/requests/projects/clusters/integrations_controller_spec.rb create mode 100644 spec/rubocop/cop/gitlab/delegate_predicate_methods_spec.rb create mode 100644 spec/rubocop/cop/gitlab/feature_available_usage_spec.rb create mode 100644 spec/rubocop/cop/style/regexp_literal_mixed_preserve_spec.rb create mode 100644 spec/rubocop/cop/user_admin_spec.rb create mode 100644 spec/services/authorized_project_update/find_records_due_for_refresh_service_spec.rb create mode 100644 spec/services/boards/destroy_service_spec.rb create mode 100644 spec/services/ci/abort_pipelines_service_spec.rb delete mode 100644 spec/services/ci/abort_project_pipelines_service_spec.rb create mode 100644 spec/services/ci/after_requeue_job_service_spec.rb delete mode 100644 spec/services/ci/cancel_user_pipelines_service_spec.rb delete mode 100644 spec/services/ci/create_job_artifacts_service_spec.rb delete mode 100644 spec/services/ci/destroy_expired_job_artifacts_service_spec.rb create mode 100644 spec/services/ci/disable_user_pipeline_schedules_service_spec.rb create mode 100644 spec/services/ci/drop_pipeline_service_spec.rb create mode 100644 spec/services/ci/job_artifacts/create_service_spec.rb create mode 100644 spec/services/ci/job_artifacts/destroy_all_expired_service_spec.rb create mode 100644 spec/services/ci/job_artifacts/destroy_batch_service_spec.rb delete mode 100644 spec/services/ci/job_artifacts_destroy_batch_service_spec.rb create mode 100644 spec/services/ci/pipeline_artifacts/destroy_all_expired_service_spec.rb delete mode 100644 spec/services/ci/pipeline_artifacts/destroy_expired_artifacts_service_spec.rb create mode 100644 spec/services/ci/pipeline_processing/test_cases/dag_build_test_two_manual_review_test_staging_production.yml delete mode 100644 spec/services/ci/pipeline_processing/test_cases/dag_test_manual_allow_failure_true.yml create mode 100644 spec/services/ci/pipeline_processing/test_cases/dag_test_manual_allow_failure_true_fails.yml create mode 100644 spec/services/ci/pipeline_processing/test_cases/dag_test_manual_allow_failure_true_succeeds.yml create mode 100644 spec/services/clusters/integrations/create_service_spec.rb create mode 100644 spec/services/groups/merge_requests_count_service_spec.rb create mode 100644 spec/services/issues/after_create_service_spec.rb create mode 100644 spec/services/keys/expiry_notification_service_spec.rb create mode 100644 spec/services/merge_requests/handle_assignees_change_service_spec.rb create mode 100644 spec/services/merge_requests/resolve_todos_service_spec.rb create mode 100644 spec/services/merge_requests/update_assignees_service_spec.rb create mode 100644 spec/services/milestones/merge_requests_count_service_spec.rb create mode 100644 spec/services/packages/debian/extract_changes_metadata_service_spec.rb create mode 100644 spec/services/packages/debian/process_changes_service_spec.rb create mode 100644 spec/services/packages/go/create_package_service_spec.rb create mode 100644 spec/services/packages/go/sync_packages_service_spec.rb create mode 100644 spec/services/packages/rubygems/create_dependencies_service_spec.rb create mode 100644 spec/services/packages/rubygems/create_gemspec_service_spec.rb create mode 100644 spec/services/packages/rubygems/metadata_extraction_service_spec.rb create mode 100644 spec/services/packages/rubygems/process_gem_service_spec.rb create mode 100644 spec/services/pages/delete_service_spec.rb delete mode 100644 spec/services/pages/delete_services_spec.rb create mode 100644 spec/services/todos/destroy/destroyed_issuable_service_spec.rb create mode 100644 spec/services/user_preferences/update_service_spec.rb create mode 100644 spec/services/users/update_todo_count_cache_service_spec.rb create mode 100644 spec/support/helpers/reload_helpers.rb create mode 100644 spec/support/helpers/rubygems_helpers.rb delete mode 100644 spec/support/matchers/track_self_describing_event_matcher.rb create mode 100644 spec/support/shared_contexts/lib/gitlab/sidekiq_logging/structured_logger_shared_context.rb create mode 100644 spec/support/shared_contexts/lib/gitlab/sidekiq_middleware/server_metrics_shared_context.rb create mode 100644 spec/support/shared_contexts/requests/api/go_modules_shared_context.rb create mode 100644 spec/support/shared_examples/boards/destroy_service_shared_examples.rb create mode 100644 spec/support/shared_examples/boards/lists/update_service_shared_examples.rb create mode 100644 spec/support/shared_examples/controllers/snippet_shared_examples.rb delete mode 100644 spec/support/shared_examples/controllers/trackable_shared_examples.rb create mode 100644 spec/support/shared_examples/features/cascading_settings_shared_examples.rb create mode 100644 spec/support/shared_examples/features/sidebar_shared_examples.rb create mode 100644 spec/support/shared_examples/graphql/spam_protection_shared_examples.rb create mode 100644 spec/support/shared_examples/helpers/groups_shared_examples.rb create mode 100644 spec/support/shared_examples/lib/gitlab/database/cte_materialized_shared_examples.rb create mode 100644 spec/support/shared_examples/lib/gitlab/sidekiq_middleware/metrics_middleware_with_worker_attribution_shared_examples.rb create mode 100644 spec/support/shared_examples/models/clusters/prometheus_client_shared.rb create mode 100644 spec/support/shared_examples/namespaces/namespace_traversal_examples.rb delete mode 100644 spec/support/shared_examples/namespaces/recursive_traversal_examples.rb create mode 100644 spec/support/shared_examples/querying_shared_examples.rb create mode 100644 spec/support/shared_examples/requests/api/graphql/projects/alert_management/integrations_shared_examples.rb create mode 100644 spec/support/shared_examples/requests/clusters/integrations_controller_shared_examples.rb create mode 100644 spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb create mode 100644 spec/support/shared_examples/services/groups_count_service_shared_examples.rb create mode 100644 spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb create mode 100644 spec/support/shared_examples/workers/in_product_marketing_email_shared_example.rb create mode 100644 spec/support/shared_examples/workers/worker_with_data_consistency_shared_example.rb create mode 100644 spec/tooling/merge_request_spec.rb create mode 100644 spec/tooling/quality/test_level_spec.rb delete mode 100644 spec/views/admin/services/index.html.haml_spec.rb create mode 100644 spec/views/dashboard/projects/index.html.haml_spec.rb create mode 100644 spec/views/groups/settings/_remove.html.haml_spec.rb create mode 100644 spec/views/layouts/_search.html.haml_spec.rb create mode 100644 spec/views/profiles/keys/_form.html.haml_spec.rb create mode 100644 spec/views/profiles/keys/_key.html.haml_spec.rb delete mode 100644 spec/views/projects/pipelines/_stage.html.haml_spec.rb create mode 100644 spec/views/shared/nav/_sidebar.html.haml_spec.rb create mode 100644 spec/workers/bulk_imports/pipeline_worker_spec.rb create mode 100644 spec/workers/ci/drop_pipeline_worker_spec.rb create mode 100644 spec/workers/ci/initial_pipeline_process_worker_spec.rb create mode 100644 spec/workers/ci/merge_requests/add_todo_when_build_fails_worker_spec.rb create mode 100644 spec/workers/concerns/worker_attributes_spec.rb create mode 100644 spec/workers/database/batched_background_migration_worker_spec.rb create mode 100644 spec/workers/merge_requests/assignees_change_worker_spec.rb create mode 100644 spec/workers/merge_requests/create_pipeline_worker_spec.rb create mode 100644 spec/workers/merge_requests/handle_assignees_change_worker_spec.rb create mode 100644 spec/workers/merge_requests/resolve_todos_worker_spec.rb create mode 100644 spec/workers/packages/go/sync_packages_worker_spec.rb create mode 100644 spec/workers/packages/rubygems/extraction_worker_spec.rb create mode 100644 spec/workers/projects/post_creation_worker_spec.rb create mode 100644 spec/workers/ssh_keys/expired_notification_worker_spec.rb create mode 100644 spec/workers/ssh_keys/expiring_soon_notification_worker_spec.rb create mode 100644 spec/workers/todos_destroyer/destroyed_issuable_worker_spec.rb (limited to 'spec') diff --git a/spec/benchmarks/banzai_benchmark.rb b/spec/benchmarks/banzai_benchmark.rb index 4cf079b2130..05c41eed889 100644 --- a/spec/benchmarks/banzai_benchmark.rb +++ b/spec/benchmarks/banzai_benchmark.rb @@ -54,9 +54,10 @@ RSpec.describe 'GitLab Markdown Benchmark', :aggregate_failures do context 'pipelines' do it 'benchmarks several pipelines' do - path = 'images/example.jpg' - gitaly_wiki_file = Gitlab::GitalyClient::WikiFile.new(path: path) - allow(wiki).to receive(:find_file).with(path, load_content: false).and_return(Gitlab::Git::WikiFile.new(gitaly_wiki_file)) + name = 'example.jpg' + path = "images/#{name}" + blob = double(name: name, path: path, mime_type: 'image/jpeg', data: nil) + allow(wiki).to receive(:find_file).with(path, load_content: false).and_return(Gitlab::Git::WikiFile.new(blob)) allow(wiki).to receive(:wiki_base_path) { '/namespace1/gitlabhq/wikis' } puts "\n--> Benchmarking Full, Wiki, and Plain pipelines\n" diff --git a/spec/config/mail_room_spec.rb b/spec/config/mail_room_spec.rb index 289e18be0d7..6265b54931a 100644 --- a/spec/config/mail_room_spec.rb +++ b/spec/config/mail_room_spec.rb @@ -16,7 +16,9 @@ RSpec.describe 'mail_room.yml' do } cmd = "puts ERB.new(File.read(#{absolute_path(mailroom_config_path).inspect})).result" - output, status = Gitlab::Popen.popen(%W(ruby -rerb -e #{cmd}), absolute_path('config'), vars) + result = Gitlab::Popen.popen_with_detail(%W(ruby -rerb -e #{cmd}), absolute_path('config'), vars) + output = result.stdout + status = result.status raise "Error interpreting #{mailroom_config_path}: #{output}" unless status == 0 YAML.load(output) @@ -68,6 +70,39 @@ RSpec.describe 'mail_room.yml' do end end + context 'when both incoming email and service desk email are enabled for Microsoft Graph' do + let(:gitlab_config_path) { 'spec/fixtures/config/mail_room_enabled_ms_graph.yml' } + let(:queues_config_path) { 'spec/fixtures/config/redis_queues_new_format_host.yml' } + let(:gitlab_redis_queues) { Gitlab::Redis::Queues.new(Rails.env) } + + it 'contains the intended configuration' do + expected_mailbox = { + email: 'gitlab-incoming@gmail.com', + name: 'inbox', + idle_timeout: 60, + expunge_deleted: true + } + expected_options = { + redis_url: gitlab_redis_queues.url, + sentinels: gitlab_redis_queues.sentinels + } + expected_inbox_options = { + tenant_id: '12345', + client_id: 'MY-CLIENT-ID', + client_secret: 'MY-CLIENT-SECRET', + poll_interval: 60 + } + + expect(configuration[:mailboxes].length).to eq(2) + expect(configuration[:mailboxes]).to all(include(expected_mailbox)) + expect(configuration[:mailboxes].map { |m| m[:inbox_method] }).to all(eq('microsoft_graph')) + expect(configuration[:mailboxes].map { |m| m[:inbox_options] }).to all(eq(expected_inbox_options)) + expect(configuration[:mailboxes].map { |m| m[:delivery_options] }).to all(include(expected_options)) + expect(configuration[:mailboxes].map { |m| m[:delivery_options] }).to all(include(expected_options)) + expect(configuration[:mailboxes].map { |m| m[:arbitration_options] }).to all(include(expected_options)) + end + end + def clear_queues_raw_config Gitlab::Redis::Queues.remove_instance_variable(:@_raw_config) rescue NameError diff --git a/spec/config/metrics/aggregates/aggregated_metrics_spec.rb b/spec/config/metrics/aggregates/aggregated_metrics_spec.rb new file mode 100644 index 00000000000..9aba86cdaf2 --- /dev/null +++ b/spec/config/metrics/aggregates/aggregated_metrics_spec.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'aggregated metrics' do + RSpec::Matchers.define :be_known_event do + match do |event| + Gitlab::UsageDataCounters::HLLRedisCounter.known_event?(event) + end + + failure_message do |event| + "Event with name: `#{event}` can not be found within `#{Gitlab::UsageDataCounters::HLLRedisCounter::KNOWN_EVENTS_PATH}`" + end + end + + RSpec::Matchers.define :has_known_source do + match do |aggregate| + Gitlab::Usage::Metrics::Aggregates::SOURCES.include?(aggregate[:source]) + end + + failure_message do |aggregate| + "Aggregate with name: `#{aggregate[:name]}` uses not allowed source `#{aggregate[:source]}`" + end + end + + RSpec::Matchers.define :have_known_time_frame do + allowed_time_frames = [ + Gitlab::Utils::UsageData::ALL_TIME_TIME_FRAME_NAME, + Gitlab::Utils::UsageData::TWENTY_EIGHT_DAYS_TIME_FRAME_NAME, + Gitlab::Utils::UsageData::SEVEN_DAYS_TIME_FRAME_NAME + ] + + match do |aggregate| + (aggregate[:time_frame] - allowed_time_frames).empty? + end + + failure_message do |aggregate| + "Aggregate with name: `#{aggregate[:name]}` uses not allowed time_frame`#{aggregate[:time_frame] - allowed_time_frames}`" + end + end + + let_it_be(:known_events) do + Gitlab::UsageDataCounters::HLLRedisCounter.known_events + end + + Gitlab::Usage::Metrics::Aggregates::Aggregate.new(Time.current).send(:aggregated_metrics).tap do |aggregated_metrics| + it 'all events has unique name' do + event_names = aggregated_metrics&.map { |event| event[:name] } + + expect(event_names).to eq(event_names&.uniq) + end + + it 'all aggregated metrics has known source' do + expect(aggregated_metrics).to all has_known_source + end + + it 'all aggregated metrics has known source' do + expect(aggregated_metrics).to all have_known_time_frame + end + + aggregated_metrics&.select { |agg| agg[:source] == Gitlab::Usage::Metrics::Aggregates::REDIS_SOURCE }&.each do |aggregate| + context "for #{aggregate[:name]} aggregate of #{aggregate[:events].join(' ')}" do + let_it_be(:events_records) { known_events.select { |event| aggregate[:events].include?(event[:name]) } } + + it "does not include 'all' time frame for Redis sourced aggregate" do + expect(aggregate[:time_frame]).not_to include(Gitlab::Utils::UsageData::ALL_TIME_TIME_FRAME_NAME) + end + + it "only refers to known events" do + expect(aggregate[:events]).to all be_known_event + end + + it "has expected structure" do + expect(aggregate.keys).to include(*%w[name operator events]) + end + + it "uses allowed aggregation operators" do + expect(Gitlab::Usage::Metrics::Aggregates::ALLOWED_METRICS_AGGREGATIONS).to include aggregate[:operator] + end + + it "uses events from the same Redis slot" do + event_slots = events_records.map { |event| event[:redis_slot] }.uniq + + expect(event_slots).to contain_exactly(be_present) + end + + it "uses events with the same aggregation period" do + event_slots = events_records.map { |event| event[:aggregation] }.uniq + + expect(event_slots).to contain_exactly(be_present) + end + 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 2b562e2dd64..6258dd30438 100644 --- a/spec/controllers/admin/application_settings_controller_spec.rb +++ b/spec/controllers/admin/application_settings_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Admin::ApplicationSettingsController do +RSpec.describe Admin::ApplicationSettingsController, :do_not_mock_admin_mode_setting do include StubENV include UsageDataHelpers @@ -164,6 +164,13 @@ RSpec.describe Admin::ApplicationSettingsController do expect(ApplicationSetting.current.default_branch_name).to eq("example_branch_name") end + it "updates admin_mode setting" do + put :update, params: { application_setting: { admin_mode: true } } + + expect(response).to redirect_to(general_admin_application_settings_path) + expect(ApplicationSetting.current.admin_mode).to be(true) + end + context "personal access token prefix settings" do let(:application_settings) { ApplicationSetting.current } diff --git a/spec/controllers/admin/clusters_controller_spec.rb b/spec/controllers/admin/clusters_controller_spec.rb index 85aa77d8473..bd0c2965906 100644 --- a/spec/controllers/admin/clusters_controller_spec.rb +++ b/spec/controllers/admin/clusters_controller_spec.rb @@ -546,20 +546,30 @@ RSpec.describe Admin::ClustersController do describe 'GET #show' do let(:cluster) { create(:cluster, :provided_by_gcp, :instance) } - def get_show + def get_show(tab: nil) get :show, params: { - id: cluster + id: cluster, + tab: tab } end describe 'functionality' do + render_views + it 'responds successfully' do get_show expect(response).to have_gitlab_http_status(:ok) expect(assigns(:cluster)).to eq(cluster) end + + it 'renders integration tab view' do + get_show(tab: 'integrations') + + expect(response).to render_template('clusters/clusters/_integrations') + expect(response).to have_gitlab_http_status(:ok) + end end describe 'security' do diff --git a/spec/controllers/admin/dev_ops_report_controller_spec.rb b/spec/controllers/admin/dev_ops_report_controller_spec.rb index 913921b9630..142db175a15 100644 --- a/spec/controllers/admin/dev_ops_report_controller_spec.rb +++ b/spec/controllers/admin/dev_ops_report_controller_spec.rb @@ -5,7 +5,13 @@ require 'spec_helper' RSpec.describe Admin::DevOpsReportController do describe 'show_adoption?' do it 'is always false' do - expect(controller.show_adoption?).to be false + expect(controller.show_adoption?).to be_falsey + end + end + + describe 'should_track_devops_score?' do + it 'is always true' do + expect(controller.should_track_devops_score?).to be_truthy end end diff --git a/spec/controllers/admin/groups_controller_spec.rb b/spec/controllers/admin/groups_controller_spec.rb index 38f4ce54e5c..8441a52b454 100644 --- a/spec/controllers/admin/groups_controller_spec.rb +++ b/spec/controllers/admin/groups_controller_spec.rb @@ -37,6 +37,12 @@ RSpec.describe Admin::GroupsController do post :create, params: { group: { path: 'test', name: 'test' } } end.to change { NamespaceSetting.count }.by(1) end + + it 'creates admin_note for group' do + expect do + post :create, params: { group: { path: 'test', name: 'test', admin_note_attributes: { note: 'test' } } } + end.to change { Namespace::AdminNote.count }.by(1) + end end describe 'PUT #members_update' do diff --git a/spec/controllers/admin/impersonations_controller_spec.rb b/spec/controllers/admin/impersonations_controller_spec.rb index 326003acaf8..744c0712d6b 100644 --- a/spec/controllers/admin/impersonations_controller_spec.rb +++ b/spec/controllers/admin/impersonations_controller_spec.rb @@ -42,7 +42,7 @@ RSpec.describe Admin::ImpersonationsController do context "when the impersonator is not admin (anymore)" do before do impersonator.admin = false - impersonator.save + impersonator.save! end it "responds with status 404" do diff --git a/spec/controllers/admin/runners_controller_spec.rb b/spec/controllers/admin/runners_controller_spec.rb index cba25dbff95..45ea8949bf2 100644 --- a/spec/controllers/admin/runners_controller_spec.rb +++ b/spec/controllers/admin/runners_controller_spec.rb @@ -125,7 +125,7 @@ RSpec.describe Admin::RunnersController do describe '#resume' do it 'marks the runner as active and ticks the queue' do - runner.update(active: false) + runner.update!(active: false) expect do post :resume, params: { id: runner.id } @@ -140,7 +140,7 @@ RSpec.describe Admin::RunnersController do describe '#pause' do it 'marks the runner as inactive and ticks the queue' do - runner.update(active: true) + runner.update!(active: true) expect do post :pause, params: { id: runner.id } diff --git a/spec/controllers/admin/services_controller_spec.rb b/spec/controllers/admin/services_controller_spec.rb index 8e78cc75369..d5ec9907b48 100644 --- a/spec/controllers/admin/services_controller_spec.rb +++ b/spec/controllers/admin/services_controller_spec.rb @@ -44,7 +44,7 @@ RSpec.describe Admin::ServicesController do describe "#update" do let(:project) { create(:project) } let!(:service_template) do - RedmineService.create( + RedmineService.create!( project: nil, active: false, template: true, diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 4a729008e67..3d34db6c2c0 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -56,8 +56,6 @@ RSpec.describe ApplicationController do end end - it_behaves_like 'a Trackable Controller' - describe '#add_gon_variables' do before do Gon.clear @@ -900,7 +898,7 @@ RSpec.describe ApplicationController do feature_category :issue_tracking def index - Labkit::Context.with_context do |context| + Gitlab::ApplicationContext.with_raw_context do |context| render json: context.to_h end end diff --git a/spec/controllers/boards/issues_controller_spec.rb b/spec/controllers/boards/issues_controller_spec.rb index a7f3ab0089f..d23f099e382 100644 --- a/spec/controllers/boards/issues_controller_spec.rb +++ b/spec/controllers/boards/issues_controller_spec.rb @@ -404,7 +404,7 @@ RSpec.describe Boards::IssuesController do list_id: list.try(:to_param) } - unless board.try(:parent)&.is_a?(Group) + unless board.try(:parent).is_a?(Group) params[:namespace_id] = project.namespace.to_param params[:project_id] = project end diff --git a/spec/controllers/chaos_controller_spec.rb b/spec/controllers/chaos_controller_spec.rb index cb4f12ff829..26ae4a6b693 100644 --- a/spec/controllers/chaos_controller_spec.rb +++ b/spec/controllers/chaos_controller_spec.rb @@ -109,7 +109,7 @@ RSpec.describe ChaosController do describe '#kill' do it 'calls synchronously' do - expect(Gitlab::Chaos).to receive(:kill).with(no_args) + expect(Gitlab::Chaos).to receive(:kill).with('KILL') get :kill @@ -117,7 +117,7 @@ RSpec.describe ChaosController do end it 'calls asynchronously' do - expect(Chaos::KillWorker).to receive(:perform_async).with(no_args) + expect(Chaos::KillWorker).to receive(:perform_async).with('KILL') get :kill, params: { async: 1 } @@ -125,6 +125,24 @@ RSpec.describe ChaosController do end end + describe '#quit' do + it 'calls synchronously' do + expect(Gitlab::Chaos).to receive(:kill).with('QUIT') + + get :quit + + expect(response).to have_gitlab_http_status(:ok) + end + + it 'calls asynchronously' do + expect(Chaos::KillWorker).to receive(:perform_async).with('QUIT') + + get :quit, params: { async: 1 } + + expect(response).to have_gitlab_http_status(:ok) + end + end + describe '#gc' do let(:gc_stat) { GC.stat.stringify_keys } diff --git a/spec/controllers/concerns/enforces_admin_authentication_spec.rb b/spec/controllers/concerns/enforces_admin_authentication_spec.rb index c6ad1a00484..106b1d53fd2 100644 --- a/spec/controllers/concerns/enforces_admin_authentication_spec.rb +++ b/spec/controllers/concerns/enforces_admin_authentication_spec.rb @@ -19,7 +19,7 @@ RSpec.describe EnforcesAdminAuthentication do end end - context 'feature flag :user_mode_in_session is enabled' do + context 'application setting :admin_mode is enabled' do describe 'authenticate_admin!' do context 'as an admin' do let(:user) { create(:admin) } @@ -61,9 +61,9 @@ RSpec.describe EnforcesAdminAuthentication do end end - context 'feature flag :user_mode_in_session is disabled' do + context 'application setting :admin_mode is disabled' do before do - stub_feature_flags(user_mode_in_session: false) + stub_application_setting(admin_mode: false) end describe 'authenticate_admin!' do diff --git a/spec/controllers/concerns/redis_tracking_spec.rb b/spec/controllers/concerns/redis_tracking_spec.rb index 53b49dd30a6..4077f4f5cce 100644 --- a/spec/controllers/concerns/redis_tracking_spec.rb +++ b/spec/controllers/concerns/redis_tracking_spec.rb @@ -9,8 +9,8 @@ RSpec.describe RedisTracking do include RedisTracking skip_before_action :authenticate_user!, only: :show - track_redis_hll_event :index, :show, name: 'g_compliance_approval_rules', - if: [:custom_condition_one?, :custom_condition_two?] + track_redis_hll_event(:index, :show, name: 'g_compliance_approval_rules', + if: [:custom_condition_one?, :custom_condition_two?]) { |controller| controller.get_custom_id } def index render html: 'index' @@ -24,6 +24,10 @@ RSpec.describe RedisTracking do render html: 'show' end + def get_custom_id + 'some_custom_id' + end + private def custom_condition_one? @@ -92,19 +96,15 @@ RSpec.describe RedisTracking do end end - context 'when user is not logged in and there is a visitor_id' do + context 'when user is not logged in' do let(:visitor_id) { SecureRandom.uuid } - before do - routes.draw { get 'show' => 'anonymous#show' } - end - - it 'tracks the event' do + it 'tracks the event when there is a visitor id' do cookies[:visitor_id] = { value: visitor_id, expires: 24.months } expect_tracking - get :show + get :show, params: { id: 1 } end end @@ -114,5 +114,19 @@ RSpec.describe RedisTracking do get :index end + + it 'tracks the event when there is custom id' do + expect_tracking + + get :show, params: { id: 1 } + end + + it 'does not track the event when there is no custom id' do + expect(controller).to receive(:get_custom_id).and_return(nil) + + expect_no_tracking + + get :show, params: { id: 2 } + end end end diff --git a/spec/controllers/concerns/renders_commits_spec.rb b/spec/controllers/concerns/renders_commits_spec.rb index 7be5f75c19d..7b241fc29af 100644 --- a/spec/controllers/concerns/renders_commits_spec.rb +++ b/spec/controllers/concerns/renders_commits_spec.rb @@ -57,4 +57,16 @@ RSpec.describe RendersCommits do end.not_to exceed_all_query_limit(control_count) end end + + describe '.prepare_commits_for_rendering' do + it 'avoids N+1' do + control = ActiveRecord::QueryRecorder.new do + subject.prepare_commits_for_rendering(merge_request.commits.take(1)) + end + + expect do + subject.prepare_commits_for_rendering(merge_request.commits) + end.not_to exceed_all_query_limit(control.count) + end + end end diff --git a/spec/controllers/dashboard/snippets_controller_spec.rb b/spec/controllers/dashboard/snippets_controller_spec.rb index 016a9f53129..180369447b4 100644 --- a/spec/controllers/dashboard/snippets_controller_spec.rb +++ b/spec/controllers/dashboard/snippets_controller_spec.rb @@ -29,23 +29,6 @@ RSpec.describe Dashboard::SnippetsController do it_behaves_like 'snippets sort order' - context 'when views are rendered' do - render_views - - it 'avoids N+1 database queries' do - # Warming call to load everything non snippet related - get(:index) - - project = create(:project, namespace: user.namespace) - create(:project_snippet, project: project, author: user) - - control_count = ActiveRecord::QueryRecorder.new { get(:index) }.count - - project = create(:project, namespace: user.namespace) - create(:project_snippet, project: project, author: user) - - expect { get(:index) }.not_to exceed_query_limit(control_count) - end - end + it_behaves_like 'snippets views' end end diff --git a/spec/controllers/explore/snippets_controller_spec.rb b/spec/controllers/explore/snippets_controller_spec.rb index f7bd2ba917e..e93e8502dfc 100644 --- a/spec/controllers/explore/snippets_controller_spec.rb +++ b/spec/controllers/explore/snippets_controller_spec.rb @@ -32,5 +32,9 @@ RSpec.describe Explore::SnippetsController do expect(assigns(:snippets)).to all(be_a(PersonalSnippet)) expect(response).to have_gitlab_http_status(:ok) end + + it_behaves_like 'snippets views' do + let_it_be(:user) { create(:user) } + end end end diff --git a/spec/controllers/graphql_controller_spec.rb b/spec/controllers/graphql_controller_spec.rb index f10fbf5ef2c..f2d86b1b166 100644 --- a/spec/controllers/graphql_controller_spec.rb +++ b/spec/controllers/graphql_controller_spec.rb @@ -175,22 +175,44 @@ RSpec.describe GraphqlController do end describe '#append_info_to_payload' do - let(:graphql_query) { graphql_query_for('project', { 'fullPath' => 'foo' }, %w(id name)) } - let(:mock_store) { { graphql_logs: { foo: :bar } } } + let(:query_1) { { query: graphql_query_for('project', { 'fullPath' => 'foo' }, %w(id name), 'getProject_1') } } + let(:query_2) { { query: graphql_query_for('project', { 'fullPath' => 'bar' }, %w(id), 'getProject_2') } } + let(:graphql_queries) { [query_1, query_2] } let(:log_payload) { {} } + let(:expected_logs) do + [ + { + operation_name: 'getProject_1', + complexity: 3, + depth: 2, + used_deprecated_fields: [], + used_fields: ['Project.id', 'Project.name', 'Query.project'], + variables: '{}' + }, + { + operation_name: 'getProject_2', + complexity: 2, + depth: 2, + used_deprecated_fields: [], + used_fields: ['Project.id', 'Query.project'], + variables: '{}' + } + ] + end before do - allow(RequestStore).to receive(:store).and_return(mock_store) + RequestStore.clear! + allow(controller).to receive(:append_info_to_payload).and_wrap_original do |method, *| method.call(log_payload) end end it 'appends metadata for logging' do - post :execute, params: { query: graphql_query, operationName: 'Foo' } + post :execute, params: { _json: graphql_queries } expect(controller).to have_received(:append_info_to_payload) - expect(log_payload.dig(:metadata, :graphql)).to eq({ operation_name: 'Foo', foo: :bar }) + expect(log_payload.dig(:metadata, :graphql)).to match_array(expected_logs) end end end diff --git a/spec/controllers/groups/boards_controller_spec.rb b/spec/controllers/groups/boards_controller_spec.rb index 6201cddecb0..ca4931bdc90 100644 --- a/spec/controllers/groups/boards_controller_spec.rb +++ b/spec/controllers/groups/boards_controller_spec.rb @@ -16,6 +16,15 @@ RSpec.describe Groups::BoardsController do expect { list_boards }.to change(group.boards, :count).by(1) end + it 'pushes swimlanes_buffered_rendering feature flag' do + allow(controller).to receive(:push_frontend_feature_flag).and_call_original + + expect(controller).to receive(:push_frontend_feature_flag) + .with(:swimlanes_buffered_rendering, group, default_enabled: :yaml) + + list_boards + end + context 'when format is HTML' do it 'renders template' do list_boards @@ -98,6 +107,15 @@ RSpec.describe Groups::BoardsController do describe 'GET show' do let!(:board) { create(:board, group: group) } + it 'pushes swimlanes_buffered_rendering feature flag' do + allow(controller).to receive(:push_frontend_feature_flag).and_call_original + + expect(controller).to receive(:push_frontend_feature_flag) + .with(:swimlanes_buffered_rendering, group, default_enabled: :yaml) + + read_board board: board + end + context 'when format is HTML' do it 'renders template' do expect { read_board board: board }.to change(BoardGroupRecentVisit, :count).by(1) diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb index 1334372a1f5..93c560b4753 100644 --- a/spec/controllers/groups/clusters_controller_spec.rb +++ b/spec/controllers/groups/clusters_controller_spec.rb @@ -641,21 +641,31 @@ RSpec.describe Groups::ClustersController do describe 'GET show' do let(:cluster) { create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group]) } - def go + def go(tab: nil) get :show, params: { group_id: group, - id: cluster + id: cluster, + tab: tab } end describe 'functionality' do + render_views + it 'renders view' do go expect(response).to have_gitlab_http_status(:ok) expect(assigns(:cluster)).to eq(cluster) end + + it 'renders integration tab view', :aggregate_failures do + go(tab: 'integrations') + + expect(response).to render_template('clusters/clusters/_integrations') + expect(response).to have_gitlab_http_status(:ok) + end end describe 'security' do diff --git a/spec/controllers/groups/dependency_proxy_auth_controller_spec.rb b/spec/controllers/groups/dependency_proxy_auth_controller_spec.rb index 857e0570621..f67b2022219 100644 --- a/spec/controllers/groups/dependency_proxy_auth_controller_spec.rb +++ b/spec/controllers/groups/dependency_proxy_auth_controller_spec.rb @@ -31,6 +31,7 @@ RSpec.describe Groups::DependencyProxyAuthController do context 'with valid JWT' do let_it_be(:user) { create(:user) } + let(:jwt) { build_jwt(user) } let(:token_header) { "Bearer #{jwt.encoded}" } @@ -65,6 +66,7 @@ RSpec.describe Groups::DependencyProxyAuthController do context 'expired token' do let_it_be(:user) { create(:user) } + let(:jwt) { build_jwt(user, expire_time: Time.zone.now - 1.hour) } let(:token_header) { "Bearer #{jwt.encoded}" } diff --git a/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb b/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb index 83775dcdbdf..9f30a850ca2 100644 --- a/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb +++ b/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb @@ -7,6 +7,7 @@ RSpec.describe Groups::DependencyProxyForContainersController do include DependencyProxyHelpers let_it_be(:user) { create(:user) } + let(:group) { create(:group) } let(:token_response) { { status: :success, token: 'abcd1234' } } let(:jwt) { build_jwt(user) } @@ -102,6 +103,7 @@ RSpec.describe Groups::DependencyProxyForContainersController do describe 'GET #manifest' do let_it_be(:manifest) { create(:dependency_proxy_manifest) } + let(:pull_response) { { status: :success, manifest: manifest } } before do @@ -182,6 +184,7 @@ RSpec.describe Groups::DependencyProxyForContainersController do describe 'GET #blob' do let_it_be(:blob) { create(:dependency_proxy_blob) } + let(:blob_sha) { blob.file_name.sub('.gz', '') } let(:blob_response) { { status: :success, blob: blob } } diff --git a/spec/controllers/groups/group_links_controller_spec.rb b/spec/controllers/groups/group_links_controller_spec.rb index a2f7161ca41..94d3c1ffa0f 100644 --- a/spec/controllers/groups/group_links_controller_spec.rb +++ b/spec/controllers/groups/group_links_controller_spec.rb @@ -9,16 +9,17 @@ RSpec.describe Groups::GroupLinksController do let(:group_member) { create(:user) } let!(:project) { create(:project, group: shared_group) } - around do |example| - travel_to DateTime.new(2019, 4, 1) { example.run } - end - before do + travel_to DateTime.new(2019, 4, 1) sign_in(user) shared_with_group.add_developer(group_member) end + after do + travel_back + end + shared_examples 'placeholder is passed as `id` parameter' do |action| it 'returns a 404' do post( diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb index ff7a7f55863..19655687028 100644 --- a/spec/controllers/groups/group_members_controller_spec.rb +++ b/spec/controllers/groups/group_members_controller_spec.rb @@ -9,8 +9,12 @@ RSpec.describe Groups::GroupMembersController do let(:group) { create(:group, :public) } let(:membership) { create(:group_member, group: group) } - around do |example| - travel_to DateTime.new(2019, 4, 1) { example.run } + before do + travel_to DateTime.new(2019, 4, 1) + end + + after do + travel_back end describe 'GET index' do @@ -288,7 +292,9 @@ RSpec.describe Groups::GroupMembersController do end describe 'DELETE destroy' do - let(:member) { create(:group_member, :developer, group: group) } + let(:sub_group) { create(:group, parent: group) } + let!(:member) { create(:group_member, :developer, group: group) } + let!(:sub_member) { create(:group_member, :developer, group: sub_group, user: member.user) } before do sign_in(user) @@ -324,9 +330,19 @@ RSpec.describe Groups::GroupMembersController do it '[HTML] removes user from members' do delete :destroy, params: { group_id: group, id: member } - expect(response).to set_flash.to 'User was successfully removed from group and any subresources.' + expect(response).to set_flash.to 'User was successfully removed from group.' + expect(response).to redirect_to(group_group_members_path(group)) + expect(group.members).not_to include member + expect(sub_group.members).to include sub_member + end + + it '[HTML] removes user from members including subgroups and projects' do + delete :destroy, params: { group_id: group, id: member, remove_sub_memberships: true } + + expect(response).to set_flash.to 'User was successfully removed from group and any subgroups and projects.' expect(response).to redirect_to(group_group_members_path(group)) expect(group.members).not_to include member + expect(sub_group.members).not_to include sub_member end it '[JS] removes user from members' do diff --git a/spec/controllers/groups/labels_controller_spec.rb b/spec/controllers/groups/labels_controller_spec.rb index b2320615778..90da40cd5f0 100644 --- a/spec/controllers/groups/labels_controller_spec.rb +++ b/spec/controllers/groups/labels_controller_spec.rb @@ -46,6 +46,24 @@ RSpec.describe Groups::LabelsController do it_behaves_like 'disabled when using an external authorization service' end + + context 'with views rendered' do + render_views + + before do + get :index, params: { group_id: group.to_param } + end + + it 'avoids N+1 queries' do + control = ActiveRecord::QueryRecorder.new(skip_cached: false) { get :index, params: { group_id: group.to_param } } + + create_list(:group_label, 3, group: group) + + # some n+1 queries still exist + expect { get :index, params: { group_id: group.to_param } }.not_to exceed_all_query_limit(control.count).with_threshold(10) + expect(assigns(:labels).count).to eq(4) + end + end end describe 'POST #toggle_subscription' do diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb index 05e93da18e7..a3c4c47ab15 100644 --- a/spec/controllers/groups/milestones_controller_spec.rb +++ b/spec/controllers/groups/milestones_controller_spec.rb @@ -322,7 +322,7 @@ RSpec.describe Groups::MilestonesController do end context 'when requesting a redirected path' do - let(:redirect_route) { group.redirect_routes.create(path: 'old-path') } + let(:redirect_route) { group.redirect_routes.create!(path: 'old-path') } it 'redirects to the canonical path' do get :merge_requests, params: { group_id: redirect_route.path, id: title } @@ -350,7 +350,7 @@ RSpec.describe Groups::MilestonesController do end context 'when the old group path is a substring of the scheme or host' do - let(:redirect_route) { group.redirect_routes.create(path: 'http') } + let(:redirect_route) { group.redirect_routes.create!(path: 'http') } it 'does not modify the requested host' do get :merge_requests, params: { group_id: redirect_route.path, id: title } @@ -362,7 +362,7 @@ RSpec.describe Groups::MilestonesController do context 'when the old group path is substring of groups' do # I.e. /groups/oups should not become /grfoo/oups - let(:redirect_route) { group.redirect_routes.create(path: 'oups') } + let(:redirect_route) { group.redirect_routes.create!(path: 'oups') } it 'does not modify the /groups part of the path' do get :merge_requests, params: { group_id: redirect_route.path, id: title } @@ -374,7 +374,7 @@ RSpec.describe Groups::MilestonesController do context 'when the old group path is substring of groups plus the new path' do # I.e. /groups/oups/oup should not become /grfoos - let(:redirect_route) { group.redirect_routes.create(path: 'oups/oup') } + let(:redirect_route) { group.redirect_routes.create!(path: 'oups/oup') } it 'does not modify the /groups part of the path' do get :merge_requests, params: { group_id: redirect_route.path, id: title } @@ -411,7 +411,7 @@ RSpec.describe Groups::MilestonesController do end context 'when requesting a redirected path' do - let(:redirect_route) { group.redirect_routes.create(path: 'old-path') } + let(:redirect_route) { group.redirect_routes.create!(path: 'old-path') } it 'returns not found' do post :create, diff --git a/spec/controllers/groups/registry/repositories_controller_spec.rb b/spec/controllers/groups/registry/repositories_controller_spec.rb index 70125087f30..35c9a80266e 100644 --- a/spec/controllers/groups/registry/repositories_controller_spec.rb +++ b/spec/controllers/groups/registry/repositories_controller_spec.rb @@ -6,6 +6,7 @@ RSpec.describe Groups::Registry::RepositoriesController do let_it_be(:user) { create(:user) } let_it_be(:guest) { create(:user) } let_it_be(:group, reload: true) { create(:group) } + let(:additional_parameters) { {} } subject do diff --git a/spec/controllers/groups/runners_controller_spec.rb b/spec/controllers/groups/runners_controller_spec.rb index 91ff0a53ec7..d6da9a4e8d0 100644 --- a/spec/controllers/groups/runners_controller_spec.rb +++ b/spec/controllers/groups/runners_controller_spec.rb @@ -220,7 +220,7 @@ RSpec.describe Groups::RunnersController do end it 'marks the runner as active, ticks the queue, and redirects' do - runner.update(active: false) + runner.update!(active: false) expect do post :resume, params: params @@ -231,7 +231,7 @@ RSpec.describe Groups::RunnersController do end it 'marks the project runner as active, ticks the queue, and redirects' do - runner_project.update(active: false) + runner_project.update!(active: false) expect do post :resume, params: params_runner_project @@ -248,7 +248,7 @@ RSpec.describe Groups::RunnersController do end it 'responds 404 and does not activate the runner' do - runner.update(active: false) + runner.update!(active: false) expect do post :resume, params: params @@ -259,7 +259,7 @@ RSpec.describe Groups::RunnersController do end it 'responds 404 and does not activate the project runner' do - runner_project.update(active: false) + runner_project.update!(active: false) expect do post :resume, params: params_runner_project @@ -278,7 +278,7 @@ RSpec.describe Groups::RunnersController do end it 'marks the runner as inactive, ticks the queue, and redirects' do - runner.update(active: true) + runner.update!(active: true) expect do post :pause, params: params @@ -289,7 +289,7 @@ RSpec.describe Groups::RunnersController do end it 'marks the project runner as inactive, ticks the queue, and redirects' do - runner_project.update(active: true) + runner_project.update!(active: true) expect do post :pause, params: params_runner_project @@ -306,7 +306,7 @@ RSpec.describe Groups::RunnersController do end it 'responds 404 and does not update the runner or queue' do - runner.update(active: true) + runner.update!(active: true) expect do post :pause, params: params @@ -317,7 +317,7 @@ RSpec.describe Groups::RunnersController do end it 'responds 404 and does not update the project runner or queue' do - runner_project.update(active: true) + runner_project.update!(active: true) expect do post :pause, params: params diff --git a/spec/controllers/groups/settings/applications_controller_spec.rb b/spec/controllers/groups/settings/applications_controller_spec.rb new file mode 100644 index 00000000000..0804a5536e0 --- /dev/null +++ b/spec/controllers/groups/settings/applications_controller_spec.rb @@ -0,0 +1,219 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Groups::Settings::ApplicationsController do + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group) } + let_it_be(:application) { create(:oauth_application, owner_id: group.id, owner_type: 'Namespace') } + + before do + sign_in(user) + end + + describe 'GET #index' do + context 'when user is owner' do + before do + group.add_owner(user) + end + + it 'renders the application form' do + get :index, params: { group_id: group } + + expect(response).to render_template :index + expect(assigns[:scopes]).to be_kind_of(Doorkeeper::OAuth::Scopes) + end + end + + context 'when user is not owner' do + before do + group.add_maintainer(user) + end + + it 'renders a 404' do + get :index, params: { group_id: group } + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + describe 'GET #edit' do + context 'when user is owner' do + before do + group.add_owner(user) + end + + it 'renders the application form' do + get :edit, params: { group_id: group, id: application.id } + + expect(response).to render_template :edit + expect(assigns[:scopes]).to be_kind_of(Doorkeeper::OAuth::Scopes) + end + end + + context 'when user is not owner' do + before do + group.add_maintainer(user) + end + + it 'renders a 404' do + get :edit, params: { group_id: group, id: application.id } + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + describe 'POST #create' do + context 'when user is owner' do + before do + group.add_owner(user) + end + + it 'creates the application' do + create_params = attributes_for(:application, trusted: false, confidential: false, scopes: ['api']) + + expect do + post :create, params: { group_id: group, doorkeeper_application: create_params } + end.to change { Doorkeeper::Application.count }.by(1) + + application = Doorkeeper::Application.last + + expect(response).to redirect_to(group_settings_application_path(group, application)) + expect(application).to have_attributes(create_params.except(:uid, :owner_type)) + end + + it 'renders the application form on errors' do + expect do + post :create, params: { group_id: group, doorkeeper_application: attributes_for(:application).merge(redirect_uri: nil) } + end.not_to change { Doorkeeper::Application.count } + + expect(response).to render_template :index + expect(assigns[:scopes]).to be_kind_of(Doorkeeper::OAuth::Scopes) + end + + context 'when the params are for a confidential application' do + it 'creates a confidential application' do + create_params = attributes_for(:application, confidential: true, scopes: ['read_user']) + + expect do + post :create, params: { group_id: group, doorkeeper_application: create_params } + end.to change { Doorkeeper::Application.count }.by(1) + + application = Doorkeeper::Application.last + + expect(response).to redirect_to(group_settings_application_path(group, application)) + expect(application).to have_attributes(create_params.except(:uid, :owner_type)) + end + end + + context 'when scopes are not present' do + it 'renders the application form on errors' do + create_params = attributes_for(:application, trusted: true, confidential: false) + + expect do + post :create, params: { group_id: group, doorkeeper_application: create_params } + end.not_to change { Doorkeeper::Application.count } + + expect(response).to render_template :index + end + end + end + + context 'when user is not owner' do + before do + group.add_maintainer(user) + end + + it 'renders a 404' do + create_params = attributes_for(:application, trusted: true, confidential: false, scopes: ['api']) + + post :create, params: { group_id: group, doorkeeper_application: create_params } + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + describe 'PATCH #update' do + context 'when user is owner' do + before do + group.add_owner(user) + end + + it 'updates the application' do + doorkeeper_params = { redirect_uri: 'http://example.com/', trusted: true, confidential: false } + + patch :update, params: { group_id: group, id: application.id, doorkeeper_application: doorkeeper_params } + + application.reload + + expect(response).to redirect_to(group_settings_application_path(group, application)) + expect(application) + .to have_attributes(redirect_uri: 'http://example.com/', trusted: false, confidential: false) + end + + it 'renders the application form on errors' do + patch :update, params: { group_id: group, id: application.id, doorkeeper_application: { redirect_uri: nil } } + + expect(response).to render_template :edit + expect(assigns[:scopes]).to be_kind_of(Doorkeeper::OAuth::Scopes) + end + + context 'when updating the application to be confidential' do + it 'successfully sets the application to confidential' do + doorkeeper_params = { confidential: true } + + patch :update, params: { group_id: group, id: application.id, doorkeeper_application: doorkeeper_params } + + application.reload + + expect(response).to redirect_to(group_settings_application_path(group, application)) + expect(application).to be_confidential + end + end + end + + context 'when user is not owner' do + before do + group.add_maintainer(user) + end + + it 'renders a 404' do + doorkeeper_params = { redirect_uri: 'http://example.com/', trusted: true, confidential: false } + + patch :update, params: { group_id: group, id: application.id, doorkeeper_application: doorkeeper_params } + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + describe 'DELETE #destroy' do + context 'when user is owner' do + before do + group.add_owner(user) + end + + it 'deletes the application' do + delete :destroy, params: { group_id: group, id: application.id } + + expect(Doorkeeper::Application.exists?(application.id)).to be_falsy + expect(response).to redirect_to(group_settings_applications_url(group)) + end + end + + context 'when user is not owner' do + before do + group.add_maintainer(user) + end + + it 'renders a 404' do + delete :destroy, params: { group_id: group, id: application.id } + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end +end diff --git a/spec/controllers/groups/uploads_controller_spec.rb b/spec/controllers/groups/uploads_controller_spec.rb index ea6a5ce8841..7dafb813545 100644 --- a/spec/controllers/groups/uploads_controller_spec.rb +++ b/spec/controllers/groups/uploads_controller_spec.rb @@ -23,7 +23,7 @@ RSpec.describe Groups::UploadsController do let!(:upload) { create(:upload, :issuable_upload, :with_file, model: model) } let(:group) { model } let(:old_path) { group.to_param + 'old' } - let!(:redirect_route) { model.redirect_routes.create(path: old_path) } + let!(:redirect_route) { model.redirect_routes.create!(path: old_path) } let(:upload_path) { File.basename(upload.path) } it 'redirects to a file with the proper extension' do diff --git a/spec/controllers/groups/variables_controller_spec.rb b/spec/controllers/groups/variables_controller_spec.rb index a450a4afb02..8c0aa83b9c4 100644 --- a/spec/controllers/groups/variables_controller_spec.rb +++ b/spec/controllers/groups/variables_controller_spec.rb @@ -8,6 +8,7 @@ RSpec.describe Groups::VariablesController do let_it_be(:group) { create(:group) } let_it_be(:user) { create(:user) } let_it_be(:variable) { create(:ci_group_variable, group: group) } + let(:access_level) { :owner } before do diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index cce61c4534b..f47eac7ac25 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -312,6 +312,64 @@ RSpec.describe GroupsController, factory_default: :keep do end end end + + context 'when creating a group with captcha protection' do + before do + sign_in(user) + + stub_application_setting(recaptcha_enabled: true) + end + + after do + # Avoid test ordering issue and ensure `verify_recaptcha` returns true + unless Recaptcha.configuration.skip_verify_env.include?('test') + Recaptcha.configuration.skip_verify_env << 'test' + end + end + + it 'displays an error when the reCAPTCHA is not solved' do + allow(controller).to receive(:verify_recaptcha).and_return(false) + + post :create, params: { group: { name: 'new_group', path: "new_group" } } + + expect(response).to render_template(:new) + expect(flash[:alert]).to eq(_('There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.')) + end + + it 'allows creating a group when the reCAPTCHA is solved' do + expect do + post :create, params: { group: { name: 'new_group', path: "new_group" } } + end.to change { Group.count }.by(1) + + expect(response).to have_gitlab_http_status(:found) + end + + it 'allows creating a sub-group without checking the captcha' do + expect(controller).not_to receive(:verify_recaptcha) + + expect do + post :create, params: { group: { name: 'new_group', path: "new_group", parent_id: group.id } } + end.to change { Group.count }.by(1) + + expect(response).to have_gitlab_http_status(:found) + end + + context 'with feature flag switched off' do + before do + stub_feature_flags(recaptcha_on_top_level_group_creation: false) + end + + it 'allows creating a group without the reCAPTCHA' do + expect(controller).not_to receive(:verify_recaptcha) + + expect do + post :create, params: { group: { name: 'new_group', path: "new_group" } } + end.to change { Group.count }.by(1) + + expect(response).to have_gitlab_http_status(:found) + end + end + end end describe 'GET #index' do @@ -556,6 +614,43 @@ RSpec.describe GroupsController, factory_default: :keep do end end + context "updating :resource_access_token_creation_allowed" do + subject do + put :update, + params: { + id: group.to_param, + group: { resource_access_token_creation_allowed: false } + } + end + + context 'when user is a group owner' do + before do + group.add_owner(user) + sign_in(user) + end + + it "updates the attribute" do + expect { subject } + .to change { group.namespace_settings.reload.resource_access_token_creation_allowed } + .from(true) + .to(false) + + expect(response).to have_gitlab_http_status(:found) + end + end + + context 'when not a group owner' do + before do + group.add_developer(user) + sign_in(user) + end + + it "does not update the attribute" do + expect { subject }.not_to change { group.namespace_settings.reload.resource_access_token_creation_allowed } + end + end + end + describe '#ensure_canonical_path' do before do sign_in(user) @@ -578,7 +673,7 @@ RSpec.describe GroupsController, factory_default: :keep do end context 'when requesting a redirected path' do - let(:redirect_route) { group.redirect_routes.create(path: 'old-path') } + let(:redirect_route) { group.redirect_routes.create!(path: 'old-path') } let(:group_full_path) { redirect_route.path } it 'redirects to the canonical path' do @@ -587,7 +682,7 @@ RSpec.describe GroupsController, factory_default: :keep do end context 'when the old group path is a substring of the scheme or host' do - let(:redirect_route) { group.redirect_routes.create(path: 'http') } + let(:redirect_route) { group.redirect_routes.create!(path: 'http') } it 'does not modify the requested host' do expect(response).to redirect_to(group) @@ -597,7 +692,7 @@ RSpec.describe GroupsController, factory_default: :keep do context 'when the old group path is substring of groups' do # I.e. /groups/oups should not become /grfoo/oups - let(:redirect_route) { group.redirect_routes.create(path: 'oups') } + let(:redirect_route) { group.redirect_routes.create!(path: 'oups') } it 'does not modify the /groups part of the path' do expect(response).to redirect_to(group) @@ -649,7 +744,7 @@ RSpec.describe GroupsController, factory_default: :keep do end context 'when requesting a redirected path' do - let(:redirect_route) { group.redirect_routes.create(path: 'old-path') } + let(:redirect_route) { group.redirect_routes.create!(path: 'old-path') } it 'redirects to the canonical path' do get :issues, params: { id: redirect_route.path } @@ -659,7 +754,7 @@ RSpec.describe GroupsController, factory_default: :keep do end context 'when the old group path is a substring of the scheme or host' do - let(:redirect_route) { group.redirect_routes.create(path: 'http') } + let(:redirect_route) { group.redirect_routes.create!(path: 'http') } it 'does not modify the requested host' do get :issues, params: { id: redirect_route.path } @@ -671,7 +766,7 @@ RSpec.describe GroupsController, factory_default: :keep do context 'when the old group path is substring of groups' do # I.e. /groups/oups should not become /grfoo/oups - let(:redirect_route) { group.redirect_routes.create(path: 'oups') } + let(:redirect_route) { group.redirect_routes.create!(path: 'oups') } it 'does not modify the /groups part of the path' do get :issues, params: { id: redirect_route.path } @@ -683,7 +778,7 @@ RSpec.describe GroupsController, factory_default: :keep do context 'when the old group path is substring of groups plus the new path' do # I.e. /groups/oups/oup should not become /grfoos - let(:redirect_route) { group.redirect_routes.create(path: 'oups/oup') } + let(:redirect_route) { group.redirect_routes.create!(path: 'oups/oup') } it 'does not modify the /groups part of the path' do get :issues, params: { id: redirect_route.path } @@ -711,7 +806,7 @@ RSpec.describe GroupsController, factory_default: :keep do end context 'when requesting a redirected path' do - let(:redirect_route) { group.redirect_routes.create(path: 'old-path') } + let(:redirect_route) { group.redirect_routes.create!(path: 'old-path') } it 'returns not found' do post :update, params: { id: redirect_route.path, group: { path: 'new_path' } } @@ -737,7 +832,7 @@ RSpec.describe GroupsController, factory_default: :keep do end context 'when requesting a redirected path' do - let(:redirect_route) { group.redirect_routes.create(path: 'old-path') } + let(:redirect_route) { group.redirect_routes.create!(path: 'old-path') } it 'returns not found' do delete :destroy, params: { id: redirect_route.path } diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb index a8d38d12f23..5195f482084 100644 --- a/spec/controllers/invites_controller_spec.rb +++ b/spec/controllers/invites_controller_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe InvitesController do let_it_be(:user) { create(:user) } - let(:member) { create(:project_member, :invited, invite_email: user.email) } + let_it_be(:member, reload: true) { create(:project_member, :invited, invite_email: user.email) } let(:raw_invite_token) { member.raw_invite_token } let(:project_members) { member.source.users } let(:md5_member_global_id) { Digest::MD5.hexdigest(member.to_global_id.to_s) } @@ -77,10 +77,83 @@ RSpec.describe InvitesController do context 'when not logged in' do context 'when inviter is a member' do - it 'is redirected to a new session with invite email param' do - request + context 'when instance allows sign up' do + it 'indicates an account can be created in notice' do + request + + expect(flash[:notice]).to include('or create an account') + end + + context 'when user exists with the invited email' do + it 'is redirected to a new session with invite email param' do + request + + expect(response).to redirect_to(new_user_session_path(invite_email: member.invite_email)) + end + end + + context 'when user exists with the invited email as secondary email' do + before do + secondary_email = create(:email, user: user, email: 'foo@example.com') + member.update!(invite_email: secondary_email.email) + end + + it 'is redirected to a new session with invite email param' do + request + + expect(response).to redirect_to(new_user_session_path(invite_email: member.invite_email)) + end + end + + context 'when user does not exist with the invited email' do + before do + member.update!(invite_email: 'bogus_email@example.com') + end + + it 'indicates an account can be created in notice' do + request + + expect(flash[:notice]).to include('create an account or sign in') + end + + it 'is redirected to a new registration with invite email param' do + request + + expect(response).to redirect_to(new_user_registration_path(invite_email: member.invite_email)) + end + end + end + + context 'when instance does not allow sign up' do + before do + stub_application_setting(allow_signup?: false) + end + + it 'does not indicate an account can be created in notice' do + request + + expect(flash[:notice]).not_to include('or create an account') + end + + context 'when user exists with the invited email' do + it 'is redirected to a new session with invite email param' do + request + + expect(response).to redirect_to(new_user_session_path(invite_email: member.invite_email)) + end + end + + context 'when user does not exist with the invited email' do + before do + member.update!(invite_email: 'bogus_email@example.com') + end + + it 'is redirected to a new session with invite email param' do + request - expect(response).to redirect_to(new_user_session_path(invite_email: member.invite_email)) + expect(response).to redirect_to(new_user_session_path(invite_email: member.invite_email)) + end + end end end diff --git a/spec/controllers/oauth/authorizations_controller_spec.rb b/spec/controllers/oauth/authorizations_controller_spec.rb index 2df94a06b3e..21124299b25 100644 --- a/spec/controllers/oauth/authorizations_controller_spec.rb +++ b/spec/controllers/oauth/authorizations_controller_spec.rb @@ -54,7 +54,7 @@ RSpec.describe Oauth::AuthorizationsController do shared_examples "Implicit grant can't be used in confidential application" do context 'when application is confidential' do before do - application.update(confidential: true) + application.update!(confidential: true) params[:response_type] = 'token' end @@ -96,7 +96,7 @@ RSpec.describe Oauth::AuthorizationsController do end it 'deletes session.user_return_to and redirects when skip authorization' do - application.update(trusted: true) + application.update!(trusted: true) request.session['user_return_to'] = 'http://example.com' subject diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb index edd587389cb..4a47a4a2a53 100644 --- a/spec/controllers/omniauth_callbacks_controller_spec.rb +++ b/spec/controllers/omniauth_callbacks_controller_spec.rb @@ -60,7 +60,7 @@ RSpec.describe OmniauthCallbacksController, type: :controller do let(:extern_uid) { 'my-uid' } before do - user.update(failed_attempts: User.maximum_attempts.pred) + user.update!(failed_attempts: User.maximum_attempts.pred) subject.response = ActionDispatch::Response.new end @@ -233,7 +233,7 @@ RSpec.describe OmniauthCallbacksController, type: :controller do before do stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') settings = Gitlab::CurrentSettings.current_application_settings - settings.update(disabled_oauth_sign_in_sources: [provider.to_s]) + settings.update!(disabled_oauth_sign_in_sources: [provider.to_s]) end it 'prevents login via POST' do @@ -299,7 +299,7 @@ RSpec.describe OmniauthCallbacksController, type: :controller do before do stub_omniauth_setting(enabled: true, auto_link_user: true, allow_single_sign_on: ['atlassian_oauth2']) - user.destroy + user.destroy! end it 'denies sign-in if sign-up is enabled, but block_auto_created_users is set' do @@ -381,7 +381,7 @@ RSpec.describe OmniauthCallbacksController, type: :controller do context 'sign up' do before do - user.destroy + user.destroy! end it 'denies login if sign up is enabled, but block_auto_created_users is set' do diff --git a/spec/controllers/profiles/notifications_controller_spec.rb b/spec/controllers/profiles/notifications_controller_spec.rb index 03749366703..1ebf4363ba6 100644 --- a/spec/controllers/profiles/notifications_controller_spec.rb +++ b/spec/controllers/profiles/notifications_controller_spec.rb @@ -5,8 +5,8 @@ require 'spec_helper' RSpec.describe Profiles::NotificationsController do let(:user) do create(:user) do |user| - user.emails.create(email: 'original@example.com', confirmed_at: Time.current) - user.emails.create(email: 'new@example.com', confirmed_at: Time.current) + user.emails.create!(email: 'original@example.com', confirmed_at: Time.current) + user.emails.create!(email: 'new@example.com', confirmed_at: Time.current) user.notification_email = 'original@example.com' user.save! end @@ -21,6 +21,30 @@ RSpec.describe Profiles::NotificationsController do expect(response).to render_template :show end + context 'when personal projects are present', :request_store do + let!(:personal_project_1) { create(:project, namespace: user.namespace) } + + context 'N+1 query check' do + render_views + + it 'does not have an N+1' do + sign_in(user) + + get :show + + control = ActiveRecord::QueryRecorder.new do + get :show + end + + create_list(:project, 2, namespace: user.namespace) + + expect do + get :show + end.not_to exceed_query_limit(control) + end + end + end + context 'with groups that do not have notification preferences' do let_it_be(:group) { create(:group) } let_it_be(:subgroup) { create(:group, parent: group) } @@ -37,18 +61,24 @@ RSpec.describe Profiles::NotificationsController do expect(assigns(:group_notifications).map(&:source_id)).to include(subgroup.id) end - it 'does not have an N+1' do - sign_in(user) + context 'N+1 query check' do + render_views + + it 'does not have an N+1' do + sign_in(user) - control = ActiveRecord::QueryRecorder.new do get :show - end - create_list(:group, 2, parent: group) + control = ActiveRecord::QueryRecorder.new do + get :show + end - expect do - get :show - end.not_to exceed_query_limit(control) + create_list(:group, 2, parent: group) + + expect do + get :show + end.not_to exceed_query_limit(control) + end end end diff --git a/spec/controllers/projects/alerting/notifications_controller_spec.rb b/spec/controllers/projects/alerting/notifications_controller_spec.rb index 3656cfbcc30..fe0c4ce00bf 100644 --- a/spec/controllers/projects/alerting/notifications_controller_spec.rb +++ b/spec/controllers/projects/alerting/notifications_controller_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe Projects::Alerting::NotificationsController do let_it_be(:project) { create(:project) } let_it_be(:environment) { create(:environment, project: project) } + let(:params) { project_params } describe 'POST #create' do @@ -68,6 +69,7 @@ RSpec.describe Projects::Alerting::NotificationsController do context 'with a corresponding integration' do context 'with integration parameters specified' do let_it_be_with_reload(:integration) { create(:alert_management_http_integration, project: project) } + let(:params) { project_params(endpoint_identifier: integration.endpoint_identifier, name: integration.name) } context 'the integration is active' do diff --git a/spec/controllers/projects/artifacts_controller_spec.rb b/spec/controllers/projects/artifacts_controller_spec.rb index 69ab9873b90..754b0ddfb94 100644 --- a/spec/controllers/projects/artifacts_controller_spec.rb +++ b/spec/controllers/projects/artifacts_controller_spec.rb @@ -448,7 +448,7 @@ RSpec.describe Projects::ArtifactsController do context 'with regular branch' do before do - pipeline.update(ref: 'master', + pipeline.update!(ref: 'master', sha: project.commit('master').sha) get :latest_succeeded, params: params_from_ref('master') @@ -459,7 +459,7 @@ RSpec.describe Projects::ArtifactsController do context 'with branch name containing slash' do before do - pipeline.update(ref: 'improve/awesome', + pipeline.update!(ref: 'improve/awesome', sha: project.commit('improve/awesome').sha) get :latest_succeeded, params: params_from_ref('improve/awesome') @@ -470,7 +470,7 @@ RSpec.describe Projects::ArtifactsController do context 'with branch name and path containing slashes' do before do - pipeline.update(ref: 'improve/awesome', + pipeline.update!(ref: 'improve/awesome', sha: project.commit('improve/awesome').sha) get :latest_succeeded, params: params_from_ref('improve/awesome', job.name, 'file/README.md') diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb index cde3a8d4761..48a12a27911 100644 --- a/spec/controllers/projects/boards_controller_spec.rb +++ b/spec/controllers/projects/boards_controller_spec.rb @@ -22,6 +22,15 @@ RSpec.describe Projects::BoardsController do expect(assigns(:boards_endpoint)).to eq project_boards_path(project) end + it 'pushes swimlanes_buffered_rendering feature flag' do + allow(controller).to receive(:push_frontend_feature_flag).and_call_original + + expect(controller).to receive(:push_frontend_feature_flag) + .with(:swimlanes_buffered_rendering, project, default_enabled: :yaml) + + list_boards + end + context 'when format is HTML' do it 'renders template' do list_boards @@ -116,6 +125,15 @@ RSpec.describe Projects::BoardsController do describe 'GET show' do let!(:board) { create(:board, project: project) } + it 'pushes swimlanes_buffered_rendering feature flag' do + allow(controller).to receive(:push_frontend_feature_flag).and_call_original + + expect(controller).to receive(:push_frontend_feature_flag) + .with(:swimlanes_buffered_rendering, project, default_enabled: :yaml) + + read_board board: board + end + it 'sets boards_endpoint instance variable to a boards path' do read_board board: board diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb index dd3440f7660..2a8feb09780 100644 --- a/spec/controllers/projects/clusters_controller_spec.rb +++ b/spec/controllers/projects/clusters_controller_spec.rb @@ -674,22 +674,32 @@ RSpec.describe Projects::ClustersController do describe 'GET show' do let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } - def go + def go(tab: nil) get :show, params: { namespace_id: project.namespace, project_id: project, - id: cluster + id: cluster, + tab: tab } end describe 'functionality' do + render_views + it "renders view" do go expect(response).to have_gitlab_http_status(:ok) expect(assigns(:cluster)).to eq(cluster) end + + it 'renders integration tab view' do + go(tab: 'integrations') + + expect(response).to render_template('clusters/clusters/_integrations') + expect(response).to have_gitlab_http_status(:ok) + end end describe 'security' do diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index 2d7f036be21..a231b54419e 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe Projects::CommitController do + include ProjectForksHelper + let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } @@ -295,6 +297,102 @@ RSpec.describe Projects::CommitController do expect(flash[:alert]).to match('Sorry, we cannot cherry-pick this commit automatically.') end end + + context 'when a project has a fork' do + let(:project) { create(:project, :repository) } + let(:forked_project) { fork_project(project, user, namespace: user.namespace, repository: true) } + let(:target_project) { project } + let(:create_merge_request) { nil } + + def send_request + post(:cherry_pick, + params: { + namespace_id: forked_project.namespace, + project_id: forked_project, + target_project_id: target_project.id, + start_branch: 'feature', + id: forked_project.commit.id, + create_merge_request: create_merge_request + }) + end + + def merge_request_url(source_project, branch) + project_new_merge_request_path( + source_project, + merge_request: { + source_project_id: source_project.id, + target_project_id: project.id, + source_branch: branch, + target_branch: 'feature' + } + ) + end + + before do + forked_project.add_maintainer(user) + end + + it 'successfully cherry picks a commit from fork to upstream project' do + send_request + + expect(response).to redirect_to project_commits_path(project, 'feature') + expect(flash[:notice]).to eq('The commit has been successfully cherry-picked into feature.') + expect(project.commit('feature').message).to include(forked_project.commit.id) + end + + context 'when the cherry pick is performed via merge request' do + let(:create_merge_request) { true } + + it 'successfully cherry picks a commit from fork to a cherry pick branch' do + branch = forked_project.commit.cherry_pick_branch_name + send_request + + expect(response).to redirect_to merge_request_url(project, branch) + expect(flash[:notice]).to start_with("The commit has been successfully cherry-picked into #{branch}") + expect(project.commit(branch).message).to include(forked_project.commit.id) + end + end + + context 'when a user cannot push to upstream project' do + let(:create_merge_request) { true } + + before do + project.add_reporter(user) + end + + it 'cherry picks a commit to the fork' do + branch = forked_project.commit.cherry_pick_branch_name + send_request + + expect(response).to redirect_to merge_request_url(forked_project, branch) + expect(flash[:notice]).to start_with("The commit has been successfully cherry-picked into #{branch}") + expect(project.commit('feature').message).not_to include(forked_project.commit.id) + expect(forked_project.commit(branch).message).to include(forked_project.commit.id) + end + end + + context 'when a user do not have access to the target project' do + let(:target_project) { create(:project, :private) } + + it 'cherry picks a commit to the fork' do + send_request + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'disable pick_into_project feature flag' do + before do + stub_feature_flags(pick_into_project: false) + end + + it 'does not cherry pick a commit from fork to upstream' do + send_request + + expect(project.commit('feature').message).not_to include(forked_project.commit.id) + end + end + end end describe 'GET diff_for_path' do diff --git a/spec/controllers/projects/cycle_analytics/events_controller_spec.rb b/spec/controllers/projects/cycle_analytics/events_controller_spec.rb index f940da7ea35..6bbdda89b14 100644 --- a/spec/controllers/projects/cycle_analytics/events_controller_spec.rb +++ b/spec/controllers/projects/cycle_analytics/events_controller_spec.rb @@ -26,7 +26,7 @@ RSpec.describe Projects::CycleAnalytics::EventsController do let(:issue) { create(:issue, project: project, created_at: 9.days.ago) } before do - issue.update(milestone: milestone) + issue.update!(milestone: milestone) end it 'is not empty' do diff --git a/spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb b/spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb index e0f86876f67..c78b838d0df 100644 --- a/spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb +++ b/spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb @@ -8,6 +8,7 @@ RSpec.describe Projects::DesignManagement::Designs::RawImagesController do let_it_be(:project) { create(:project, :private) } let_it_be(:issue) { create(:issue, project: project) } let_it_be(:viewer) { issue.author } + let(:design_id) { design.id } let(:sha) { design.versions.first.sha } let(:filename) { design.filename } @@ -44,17 +45,6 @@ RSpec.describe Projects::DesignManagement::Designs::RawImagesController do expect(response).to have_gitlab_http_status(:ok) end - context 'when the feature flag attachment_with_filename is disabled' do - it 'serves files with just `attachment` in the disposition header' do - stub_feature_flags(attachment_with_filename: false) - - subject - - expect(response.header['Content-Disposition']).to eq('attachment') - expect(response).to have_gitlab_http_status(:ok) - end - end - it 'serves files with Workhorse' do subject diff --git a/spec/controllers/projects/design_management/designs/resized_image_controller_spec.rb b/spec/controllers/projects/design_management/designs/resized_image_controller_spec.rb index 96ecbaf55b6..56c0ef592ca 100644 --- a/spec/controllers/projects/design_management/designs/resized_image_controller_spec.rb +++ b/spec/controllers/projects/design_management/designs/resized_image_controller_spec.rb @@ -9,6 +9,7 @@ RSpec.describe Projects::DesignManagement::Designs::ResizedImageController do let_it_be(:issue) { create(:issue, project: project) } let_it_be(:viewer) { issue.author } let_it_be(:size) { :v432x230 } + let(:design) { create(:design, :with_smaller_image_versions, issue: issue, versions_count: 2) } let(:design_id) { design.id } let(:sha) { design.versions.first.sha } diff --git a/spec/controllers/projects/discussions_controller_spec.rb b/spec/controllers/projects/discussions_controller_spec.rb index 8a793e29bfa..0c8677ea4b9 100644 --- a/spec/controllers/projects/discussions_controller_spec.rb +++ b/spec/controllers/projects/discussions_controller_spec.rb @@ -85,7 +85,7 @@ RSpec.describe Projects::DiscussionsController do context "when the discussion is not resolvable" do before do - note.update(system: true) + note.update!(system: true) end it "returns status 404" do @@ -168,7 +168,7 @@ RSpec.describe Projects::DiscussionsController do context "when the discussion is not resolvable" do before do - note.update(system: true) + note.update!(system: true) end it "returns status 404" do diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index 83ad36b217f..4cb90edb742 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -9,6 +9,7 @@ RSpec.describe Projects::EnvironmentsController do let_it_be(:project) { create(:project) } let_it_be(:maintainer) { create(:user, name: 'main-dos').tap { |u| project.add_maintainer(u) } } let_it_be(:reporter) { create(:user, name: 'repo-dos').tap { |u| project.add_reporter(u) } } + let(:user) { maintainer } let!(:environment) { create(:environment, name: 'production', project: project) } diff --git a/spec/controllers/projects/feature_flags_controller_spec.rb b/spec/controllers/projects/feature_flags_controller_spec.rb index f69cc0ddfd8..cd7d1ea0e8a 100644 --- a/spec/controllers/projects/feature_flags_controller_spec.rb +++ b/spec/controllers/projects/feature_flags_controller_spec.rb @@ -9,6 +9,7 @@ RSpec.describe Projects::FeatureFlagsController do let_it_be(:project) { create(:project) } let_it_be(:developer) { create(:user) } let_it_be(:reporter) { create(:user) } + let(:user) { developer } before_all do diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb index 7da3d403b53..8ca3009e0c7 100644 --- a/spec/controllers/projects/forks_controller_spec.rb +++ b/spec/controllers/projects/forks_controller_spec.rb @@ -71,7 +71,7 @@ RSpec.describe Projects::ForksController do context 'when fork is internal' do before do - forked_project.update(visibility_level: Project::INTERNAL, group: group) + forked_project.update!(visibility_level: Project::INTERNAL, group: group) end it 'forks counts are correct' do @@ -86,7 +86,7 @@ RSpec.describe Projects::ForksController do context 'when fork is private' do before do - forked_project.update(visibility_level: Project::PRIVATE, group: group) + forked_project.update!(visibility_level: Project::PRIVATE, group: group) end shared_examples 'forks counts' do @@ -153,8 +153,11 @@ RSpec.describe Projects::ForksController do end describe 'GET new' do - subject do + let(:format) { :html } + + subject(:do_request) do get :new, + format: format, params: { namespace_id: project.namespace, project_id: project @@ -166,24 +169,32 @@ RSpec.describe Projects::ForksController do sign_in(user) end - context 'when JSON requested' do - it 'responds with available groups' do - get :new, - format: :json, - params: { - namespace_id: project.namespace, - project_id: project - } + it 'responds with status 200' do + request - expect(json_response['namespaces'].length).to eq(1) - expect(json_response['namespaces'].first['id']).to eq(group.id) - end + expect(response).to have_gitlab_http_status(:ok) end - it 'responds with status 200' do - subject + context 'when JSON is requested' do + let(:format) { :json } - expect(response).to have_gitlab_http_status(:ok) + it 'responds with user namespace + groups' do + do_request + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['namespaces'].length).to eq(2) + expect(json_response['namespaces'][0]['id']).to eq(user.namespace.id) + expect(json_response['namespaces'][1]['id']).to eq(group.id) + end + + it 'responds with group only when fork_project_form feature flag is disabled' do + stub_feature_flags(fork_project_form: false) + do_request + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['namespaces'].length).to eq(1) + expect(json_response['namespaces'][0]['id']).to eq(group.id) + end end end diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb index 084a807e162..d514c486f60 100644 --- a/spec/controllers/projects/group_links_controller_spec.rb +++ b/spec/controllers/projects/group_links_controller_spec.rb @@ -8,15 +8,16 @@ RSpec.describe Projects::GroupLinksController do let_it_be(:project) { create(:project, :private, group: group2) } let_it_be(:user) { create(:user) } - around do |example| - travel_to DateTime.new(2019, 4, 1) { example.run } - end - before do + travel_to DateTime.new(2019, 4, 1) project.add_maintainer(user) sign_in(user) end + after do + travel_back + end + describe '#create' do shared_context 'link project to group' do before do @@ -31,7 +32,7 @@ RSpec.describe Projects::GroupLinksController do context 'when project is not allowed to be shared with a group' do before do - group.update(share_with_group_lock: false) + group.update!(share_with_group_lock: false) end include_context 'link project to group' diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb index 5e09a50aa36..65a80b9e8ec 100644 --- a/spec/controllers/projects/imports_controller_spec.rb +++ b/spec/controllers/projects/imports_controller_spec.rb @@ -47,7 +47,7 @@ RSpec.describe Projects::ImportsController do context 'when import is in progress' do before do - import_state.update(status: :started) + import_state.update!(status: :started) end it 'renders template' do @@ -65,7 +65,7 @@ RSpec.describe Projects::ImportsController do context 'when import failed' do before do - import_state.update(status: :failed) + import_state.update!(status: :failed) end it 'redirects to new_namespace_project_import_path' do @@ -77,7 +77,7 @@ RSpec.describe Projects::ImportsController do context 'when import finished' do before do - import_state.update(status: :finished) + import_state.update!(status: :finished) end context 'when project is a fork' do @@ -126,7 +126,7 @@ RSpec.describe Projects::ImportsController do context 'when import never happened' do before do - import_state.update(status: :none) + import_state.update!(status: :none) end it 'redirects to namespace_project_path' do diff --git a/spec/controllers/projects/incidents_controller_spec.rb b/spec/controllers/projects/incidents_controller_spec.rb index ddd15b9b1dd..460821634b0 100644 --- a/spec/controllers/projects/incidents_controller_spec.rb +++ b/spec/controllers/projects/incidents_controller_spec.rb @@ -69,6 +69,7 @@ RSpec.describe Projects::IncidentsController do end let_it_be(:resource) { create(:incident, project: project) } + let(:user) { developer } it 'renders incident page' do diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 74062038248..3e016a5e8d2 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -8,6 +8,7 @@ RSpec.describe Projects::IssuesController do let_it_be(:project, reload: true) { create(:project) } let_it_be(:user, reload: true) { create(:user) } + let(:issue) { create(:issue, project: project) } let(:spam_action_response_fields) { { 'stub_spam_action_response_fields' => true } } @@ -44,7 +45,7 @@ RSpec.describe Projects::IssuesController do let_it_be(:issue) { create(:issue, project: new_project) } before do - project.route.destroy + project.route.destroy! new_project.redirect_routes.create!(path: project.full_path) new_project.add_developer(user) end @@ -63,23 +64,6 @@ RSpec.describe Projects::IssuesController do expect(response).to have_gitlab_http_status(:moved_permanently) end end - - describe 'the null hypothesis experiment', :experiment do - before do - stub_experiments(null_hypothesis: :candidate) - end - - it 'defines the expected before actions' do - expect(controller).to use_before_action(:run_null_hypothesis_experiment) - end - - it 'assigns the candidate experience and tracks the event' do - expect(experiment(:null_hypothesis)).to track('index').on_any_instance.for(:candidate) - .with_context(project: project) - - get :index, params: { namespace_id: project.namespace, project_id: project } - end - end end context 'internal issue tracker' do @@ -209,6 +193,32 @@ RSpec.describe Projects::IssuesController do expect(response).to have_gitlab_http_status(:ok) expect(json_response['issue_email_participants']).to contain_exactly({ "email" => participants[0].email }, { "email" => participants[1].email }) end + + context 'with the invite_members_in_comment experiment', :experiment do + context 'when user can invite' do + before do + stub_experiments(invite_members_in_comment: :invite_member_link) + project.add_maintainer(user) + end + + it 'assigns the candidate experience and tracks the event' do + expect(experiment(:invite_members_in_comment)).to track(:view, property: project.root_ancestor.id.to_s) + .for(:invite_member_link) + .with_context(namespace: project.root_ancestor) + .on_next_instance + + get :show, params: { namespace_id: project.namespace, project_id: project, id: issue.iid } + end + end + + context 'when user can not invite' do + it 'does not track the event' do + expect(experiment(:invite_members_in_comment)).not_to track(:view) + + get :show, params: { namespace_id: project.namespace, project_id: project, id: issue.iid } + end + end + end end describe 'GET #new' do @@ -342,6 +352,7 @@ RSpec.describe Projects::IssuesController do end let_it_be(:issue) { create(:issue, project: project) } + let(:developer) { user } let(:params) do { @@ -685,7 +696,7 @@ RSpec.describe Projects::IssuesController do issue.update!(last_edited_by: deleted_user, last_edited_at: Time.current) - deleted_user.destroy + deleted_user.destroy! sign_in(user) end @@ -1038,10 +1049,10 @@ RSpec.describe Projects::IssuesController do labels = create_list(:label, 10, project: project).map(&:to_reference) issue = create(:issue, project: project, description: 'Test issue') - control_count = ActiveRecord::QueryRecorder.new { issue.update(description: [issue.description, label].join(' ')) }.count + control_count = ActiveRecord::QueryRecorder.new { issue.update!(description: [issue.description, label].join(' ')) }.count # Follow-up to get rid of this `2 * label.count` requirement: https://gitlab.com/gitlab-org/gitlab-foss/issues/52230 - expect { issue.update(description: [issue.description, labels].join(' ')) } + expect { issue.update!(description: [issue.description, labels].join(' ')) } .not_to exceed_query_limit(control_count + 2 * labels.count) end @@ -1158,6 +1169,7 @@ RSpec.describe Projects::IssuesController do context 'resolving discussions in MergeRequest' do let_it_be(:discussion) { create(:diff_note_on_merge_request).to_discussion } + let(:merge_request) { discussion.noteable } let(:project) { merge_request.source_project } @@ -1420,9 +1432,7 @@ RSpec.describe Projects::IssuesController do expect_next_instance_of(Spam::AkismetService) do |akismet_service| expect(akismet_service).to receive_messages(submit_spam: true) end - expect_next_instance_of(ApplicationSetting) do |setting| - expect(setting).to receive_messages(akismet_enabled: true) - end + stub_application_setting(akismet_enabled: true) end def post_spam @@ -1490,12 +1500,6 @@ RSpec.describe Projects::IssuesController do expect(response).to have_gitlab_http_status(:unprocessable_entity) expect(json_response).to eq({ 'errors' => 'Destroy confirmation not provided for issue' }) end - - it 'delegates the update of the todos count cache to TodoService' do - expect_any_instance_of(TodoService).to receive(:destroy_target).with(issue).once - - delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: issue.iid, destroy_confirm: true } - end end end @@ -1623,6 +1627,7 @@ RSpec.describe Projects::IssuesController do describe 'POST #import_csv' do let_it_be(:project) { create(:project, :public) } + let(:file) { fixture_file_upload('spec/fixtures/csv_comma.csv') } context 'unauthorized' do @@ -1822,6 +1827,7 @@ RSpec.describe Projects::IssuesController do context 'with cross-reference system note', :request_store do let_it_be(:new_issue) { create(:issue) } + let(:cross_reference) { "mentioned in #{new_issue.to_reference(issue.project)}" } before do @@ -1899,7 +1905,7 @@ RSpec.describe Projects::IssuesController do before do sign_in(user) - project.route.destroy + project.route.destroy! new_project.redirect_routes.create!(path: project.full_path) new_project.add_developer(user) end diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 80e1268cb01..a7a36d3a074 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -1275,6 +1275,7 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do let_it_be(:reporter) { create(:user) } let_it_be(:guest) { create(:user) } let_it_be(:project) { create(:project, :private, :repository, namespace: owner.namespace) } + let(:user) { maintainer } let(:pipeline) { create(:ci_pipeline, project: project, source: :webide, config_source: :webide_source, user: user) } let(:job) { create(:ci_build, :running, :with_runner_session, pipeline: pipeline, user: user) } diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb index f452c22a5ca..081927ea73c 100644 --- a/spec/controllers/projects/labels_controller_spec.rb +++ b/spec/controllers/projects/labels_controller_spec.rb @@ -65,7 +65,7 @@ RSpec.describe Projects::LabelsController do end it 'does not include group labels when project does not belong to a group' do - project.update(namespace: create(:namespace)) + project.update!(namespace: create(:namespace)) list_labels @@ -93,6 +93,26 @@ RSpec.describe Projects::LabelsController do end end + context 'with views rendered' do + render_views + + before do + list_labels + end + + it 'avoids N+1 queries' do + control = ActiveRecord::QueryRecorder.new(skip_cached: false) { list_labels } + + create_list(:label, 3, project: project) + create_list(:group_label, 3, group: group) + + # some n+1 queries still exist + # calls to get max project authorization access level + expect { list_labels }.not_to exceed_all_query_limit(control.count).with_threshold(25) + expect(assigns(:labels).count).to eq(10) + end + end + def list_labels get :index, params: { namespace_id: project.namespace.to_param, project_id: project } end @@ -221,7 +241,7 @@ RSpec.describe Projects::LabelsController do end context 'when requesting a redirected path' do - let_it_be(:redirect_route) { project.redirect_routes.create(path: project.full_path + 'old') } + let_it_be(:redirect_route) { project.redirect_routes.create!(path: project.full_path + 'old') } it 'redirects to the canonical path' do get :index, params: { namespace_id: project.namespace, project_id: project.to_param + 'old' } @@ -267,7 +287,7 @@ RSpec.describe Projects::LabelsController do end context 'when requesting a redirected path' do - let_it_be(:redirect_route) { project.redirect_routes.create(path: project.full_path + 'old') } + let_it_be(:redirect_route) { project.redirect_routes.create!(path: project.full_path + 'old') } it 'returns not found' do post :generate, params: { namespace_id: project.namespace, project_id: project.to_param + 'old' } diff --git a/spec/controllers/projects/merge_requests/content_controller_spec.rb b/spec/controllers/projects/merge_requests/content_controller_spec.rb index 67d3ef6f4f0..0eaa528a330 100644 --- a/spec/controllers/projects/merge_requests/content_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/content_controller_spec.rb @@ -11,13 +11,13 @@ RSpec.describe Projects::MergeRequests::ContentController do sign_in(user) end - def do_request(action = :cached_widget) + def do_request(action = :cached_widget, params = {}) get action, params: { namespace_id: project.namespace.to_param, project_id: project, id: merge_request.iid, format: :json - } + }.merge(params) end context 'user has access to the project' do @@ -42,6 +42,10 @@ RSpec.describe Projects::MergeRequests::ContentController do end describe 'GET widget' do + before do + merge_request.mark_as_unchecked! + end + it 'checks whether the MR can be merged' do controller.instance_variable_set(:@merge_request, merge_request) @@ -53,6 +57,17 @@ RSpec.describe Projects::MergeRequests::ContentController do expect(response.headers['Poll-Interval']).to eq('10000') end + context 'when async_mergeability_check param is passed' do + it 'checks mergeability asynchronously' do + expect_next_instance_of(MergeRequests::MergeabilityCheckService) do |service| + expect(service).not_to receive(:execute) + expect(service).to receive(:async_execute).and_call_original + end + + do_request(:widget, { async_mergeability_check: true }) + end + end + context 'merged merge request' do let(:merge_request) do create(:merged_merge_request, :with_test_reports, target_project: project, source_project: project) diff --git a/spec/controllers/projects/merge_requests/creations_controller_spec.rb b/spec/controllers/projects/merge_requests/creations_controller_spec.rb index 091a44130a1..df2023b7356 100644 --- a/spec/controllers/projects/merge_requests/creations_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/creations_controller_spec.rb @@ -213,6 +213,38 @@ RSpec.describe Projects::MergeRequests::CreationsController do expect(assigns(:commit)).to be_nil expect(response).to have_gitlab_http_status(:ok) end + + context 'no target_project_id provided' do + before do + project.add_maintainer(user) + end + + it 'selects itself as a target project' do + get :branch_to, + params: { + namespace_id: project.namespace, + project_id: project, + ref: 'master' + } + + expect(assigns(:target_project)).to eq(project) + expect(response).to have_gitlab_http_status(:ok) + end + + context 'project is a fork' do + it 'calls to project defaults to selects a correct target project' do + get :branch_to, + params: { + namespace_id: fork_project.namespace, + project_id: fork_project, + ref: 'master' + } + + expect(assigns(:target_project)).to eq(project) + expect(response).to have_gitlab_http_status(:ok) + end + end + end end describe 'POST create' do diff --git a/spec/controllers/projects/merge_requests/drafts_controller_spec.rb b/spec/controllers/projects/merge_requests/drafts_controller_spec.rb index af39d4dec72..580211893dc 100644 --- a/spec/controllers/projects/merge_requests/drafts_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/drafts_controller_spec.rb @@ -297,7 +297,7 @@ RSpec.describe Projects::MergeRequests::DraftsController do expect { post :publish, params: params }.to change { Note.count }.by(0).and change { DraftNote.count }.by(0) end - it 'publishes a draft note with quick actions and applies them' do + it 'publishes a draft note with quick actions and applies them', :sidekiq_inline do project.add_developer(user2) create(:draft_note, merge_request: merge_request, author: user, note: "/assign #{user2.to_reference}") diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 93d5e7eff6c..337a4a19b2e 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -8,6 +8,7 @@ RSpec.describe Projects::MergeRequestsController do let_it_be_with_refind(:project) { create(:project, :repository) } let_it_be_with_reload(:project_public_with_private_builds) { create(:project, :repository, :public, :builds_private) } + let(:user) { project.owner } let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } @@ -40,6 +41,32 @@ RSpec.describe Projects::MergeRequestsController do get :show, params: params.merge(extra_params) end + context 'with the invite_members_in_comment experiment', :experiment do + context 'when user can invite' do + before do + stub_experiments(invite_members_in_comment: :invite_member_link) + project.add_maintainer(user) + end + + it 'assigns the candidate experience and tracks the event' do + expect(experiment(:invite_members_in_comment)).to track(:view, property: project.root_ancestor.id.to_s) + .for(:invite_member_link) + .with_context(namespace: project.root_ancestor) + .on_next_instance + + go + end + end + + context 'when user can not invite' do + it 'does not track the event' do + expect(experiment(:invite_members_in_comment)).not_to track(:view) + + go + end + end + end + context 'with view param' do before do go(view: 'parallel') @@ -55,13 +82,19 @@ RSpec.describe Projects::MergeRequestsController do merge_request.mark_as_unchecked! end - it 'checks mergeability asynchronously' do - expect_next_instance_of(MergeRequests::MergeabilityCheckService) do |service| - expect(service).not_to receive(:execute) - expect(service).to receive(:async_execute) + context 'check_mergeability_async_in_widget feature flag is disabled' do + before do + stub_feature_flags(check_mergeability_async_in_widget: false) end - go + it 'checks mergeability asynchronously' do + expect_next_instance_of(MergeRequests::MergeabilityCheckService) do |service| + expect(service).not_to receive(:execute) + expect(service).to receive(:async_execute) + end + + go + end end end @@ -695,12 +728,6 @@ RSpec.describe Projects::MergeRequestsController do expect(response).to have_gitlab_http_status(:unprocessable_entity) expect(json_response).to eq({ 'errors' => 'Destroy confirmation not provided for merge request' }) end - - it 'delegates the update of the todos count cache to TodoService' do - expect_any_instance_of(TodoService).to receive(:destroy_target).with(merge_request).once - - delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid, destroy_confirm: true } - end end end diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb index b93f1b41a7e..b62353784b3 100644 --- a/spec/controllers/projects/milestones_controller_spec.rb +++ b/spec/controllers/projects/milestones_controller_spec.rb @@ -105,7 +105,7 @@ RSpec.describe Projects::MilestonesController do context 'with a single group ancestor' do before do - project.update(namespace: group) + project.update!(namespace: group) get :index, params: { namespace_id: project.namespace.id, project_id: project.id }, format: :json end @@ -122,7 +122,7 @@ RSpec.describe Projects::MilestonesController do let!(:subgroup_milestone) { create(:milestone, group: subgroup) } before do - project.update(namespace: subgroup) + project.update!(namespace: subgroup) get :index, params: { namespace_id: project.namespace.id, project_id: project.id }, format: :json end @@ -158,7 +158,7 @@ RSpec.describe Projects::MilestonesController do let(:group) { create(:group) } before do - project.update(namespace: group) + project.update!(namespace: group) end context 'when user does not have permission to promote milestone' do @@ -234,7 +234,7 @@ RSpec.describe Projects::MilestonesController do end it 'renders 404' do - project.update(namespace: user.namespace) + project.update!(namespace: user.namespace) post :promote, params: { namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid } @@ -253,7 +253,7 @@ RSpec.describe Projects::MilestonesController do before do project.add_guest(guest_user) sign_in(guest_user) - issue.update(assignee_ids: issue_assignee.id) + issue.update!(assignee_ids: issue_assignee.id) end context "when issue is not confidential" do @@ -269,7 +269,7 @@ RSpec.describe Projects::MilestonesController do context "when issue is confidential" do before do - issue.update(confidential: true) + issue.update!(confidential: true) end it 'shows no milestone participants' do diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb index add249e2c74..d92862f0ca3 100644 --- a/spec/controllers/projects/notes_controller_spec.rb +++ b/spec/controllers/projects/notes_controller_spec.rb @@ -334,7 +334,7 @@ RSpec.describe Projects::NotesController do before do project.update_attribute(:visibility_level, project_visibility) - project.project_feature.update(merge_requests_access_level: merge_requests_access_level) + project.project_feature.update!(merge_requests_access_level: merge_requests_access_level) sign_in(user) end @@ -917,7 +917,7 @@ RSpec.describe Projects::NotesController do context "when the note is not resolvable" do before do - note.update(system: true) + note.update!(system: true) end it "returns status 404" do @@ -980,7 +980,7 @@ RSpec.describe Projects::NotesController do context "when the note is not resolvable" do before do - note.update(system: true) + note.update!(system: true) end it "returns status 404" do diff --git a/spec/controllers/projects/performance_monitoring/dashboards_controller_spec.rb b/spec/controllers/projects/performance_monitoring/dashboards_controller_spec.rb index 8a344a72120..923581d9367 100644 --- a/spec/controllers/projects/performance_monitoring/dashboards_controller_spec.rb +++ b/spec/controllers/projects/performance_monitoring/dashboards_controller_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe Projects::PerformanceMonitoring::DashboardsController do let_it_be(:user) { create(:user) } let_it_be(:namespace) { create(:namespace) } + let!(:project) { create(:project, :repository, name: 'dashboard-project', namespace: namespace) } let(:repository) { project.repository } let(:branch) { double(name: branch_name) } diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index e1405660ccb..753223c5a4f 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -7,13 +7,14 @@ RSpec.describe Projects::PipelinesController do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :public, :repository) } + let(:feature) { ProjectFeature::ENABLED } before do allow(Sidekiq.logger).to receive(:info) stub_not_protect_default_branch project.add_developer(user) - project.project_feature.update(builds_access_level: feature) + project.project_feature.update!(builds_access_level: feature) sign_in(user) end @@ -272,6 +273,23 @@ RSpec.describe Projects::PipelinesController do end end + describe 'GET #index' do + context 'pipeline_empty_state_templates experiment' do + before do + stub_application_setting(auto_devops_enabled: false) + end + + it 'tracks the view', :experiment do + expect(experiment(:pipeline_empty_state_templates)) + .to track(:view, value: project.namespace_id) + .with_context(actor: user) + .on_next_instance + + get :index, params: { namespace_id: project.namespace, project_id: project } + end + end + end + describe 'GET show.json' do let(:pipeline) { create(:ci_pipeline, project: project) } @@ -628,44 +646,6 @@ RSpec.describe Projects::PipelinesController do end end - describe 'GET stages_ajax.json' do - let(:pipeline) { create(:ci_pipeline, project: project) } - - context 'when accessing existing stage' do - before do - create(:ci_build, pipeline: pipeline, stage: 'build') - - get_stage_ajax('build') - end - - it 'returns html source for stage dropdown' do - expect(response).to have_gitlab_http_status(:ok) - expect(response).to render_template('projects/pipelines/_stage') - expect(json_response).to include('html') - end - end - - context 'when accessing unknown stage' do - before do - get_stage_ajax('test') - end - - it 'responds with not found' do - expect(response).to have_gitlab_http_status(:not_found) - end - end - - def get_stage_ajax(name) - get :stage_ajax, params: { - namespace_id: project.namespace, - project_id: project, - id: pipeline.id, - stage: name - }, - format: :json - end - end - describe 'GET status.json' do let(:pipeline) { create(:ci_pipeline, project: project) } let(:status) { pipeline.detailed_status(double('user')) } @@ -702,7 +682,7 @@ RSpec.describe Projects::PipelinesController do before do project.add_developer(user) - project.project_feature.update(builds_access_level: feature) + project.project_feature.update!(builds_access_level: feature) end context 'with a valid .gitlab-ci.yml file' do @@ -721,7 +701,7 @@ RSpec.describe Projects::PipelinesController do pipeline = project.ci_pipelines.last expected_redirect_path = Gitlab::Routing.url_helpers.project_pipeline_path(project, pipeline) - expect(pipeline).to be_pending + expect(pipeline).to be_created expect(response).to redirect_to(expected_redirect_path) end end @@ -777,7 +757,7 @@ RSpec.describe Projects::PipelinesController do before do project.add_developer(user) - project.project_feature.update(builds_access_level: feature) + project.project_feature.update!(builds_access_level: feature) end context 'with a valid .gitlab-ci.yml file' do diff --git a/spec/controllers/projects/pipelines_settings_controller_spec.rb b/spec/controllers/projects/pipelines_settings_controller_spec.rb index ad631b7c3da..39fb153e802 100644 --- a/spec/controllers/projects/pipelines_settings_controller_spec.rb +++ b/spec/controllers/projects/pipelines_settings_controller_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe Projects::PipelinesSettingsController do let_it_be(:user) { create(:user) } let_it_be(:project_auto_devops) { create(:project_auto_devops) } + let(:project) { project_auto_devops.project } before do diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index 53a7c2ca069..46a0fc8edb0 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -7,8 +7,12 @@ RSpec.describe Projects::ProjectMembersController do let(:group) { create(:group, :public) } let(:project) { create(:project, :public) } - around do |example| - travel_to DateTime.new(2019, 4, 1) { example.run } + before do + travel_to DateTime.new(2019, 4, 1) + end + + after do + travel_back end describe 'GET index' do diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb index b1c3c1c0276..5dee36ee7c2 100644 --- a/spec/controllers/projects/raw_controller_spec.rb +++ b/spec/controllers/projects/raw_controller_spec.rb @@ -6,6 +6,7 @@ RSpec.describe Projects::RawController do include RepoHelpers let_it_be(:project) { create(:project, :public, :repository) } + let(:inline) { nil } describe 'GET #show' do diff --git a/spec/controllers/projects/registry/repositories_controller_spec.rb b/spec/controllers/projects/registry/repositories_controller_spec.rb index 9b803edd463..0685e5a2055 100644 --- a/spec/controllers/projects/registry/repositories_controller_spec.rb +++ b/spec/controllers/projects/registry/repositories_controller_spec.rb @@ -16,19 +16,19 @@ RSpec.describe Projects::Registry::RepositoriesController do project.add_developer(user) end - shared_examples 'with name parameter' do - let_it_be(:repo) { create(:container_repository, project: project, name: 'my_searched_image') } - let_it_be(:another_repo) { create(:container_repository, project: project, name: 'bar') } - - it 'returns the searched repo' do - go_to_index(format: :json, params: { name: 'my_searched_image' }) + shared_examples 'renders 200 for html and 404 for json' do + it 'successfully renders container repositories', :snowplow do + go_to_index expect(response).to have_gitlab_http_status(:ok) - expect(json_response.length).to eq 1 - expect(json_response.first).to include( - 'id' => repo.id, - 'name' => repo.name - ) + # event tracked in GraphQL API: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44926 + expect_no_snowplow_event + end + + it 'returns 404 for request in json format' do + go_to_index(format: :json) + + expect(response).to have_gitlab_http_status(:not_found) end end @@ -50,33 +50,12 @@ RSpec.describe Projects::Registry::RepositoriesController do tags: %w[rc1 latest]) end - it 'successfully renders container repositories', :snowplow do - go_to_index - - expect_no_snowplow_event - expect(response).to have_gitlab_http_status(:ok) - end - - it 'tracks the event', :snowplow do - go_to_index(format: :json) - - expect_snowplow_event(category: anything, action: 'list_repositories') - end - it 'creates a root container repository' do expect { go_to_index }.to change { ContainerRepository.all.count }.by(1) expect(ContainerRepository.first).to be_root_repository end - it 'json has a list of projects' do - go_to_index(format: :json) - - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('registry/repositories') - expect(response).to include_pagination_headers - end - - it_behaves_like 'with name parameter' + it_behaves_like 'renders 200 for html and 404 for json' end context 'when there are no tags for this repository' do @@ -84,22 +63,11 @@ RSpec.describe Projects::Registry::RepositoriesController do stub_container_registry_tags(repository: :any, tags: []) end - it 'successfully renders container repositories' do - go_to_index - - expect(response).to have_gitlab_http_status(:ok) - end - it 'does not ensure root container repository' do expect { go_to_index }.not_to change { ContainerRepository.all.count } end - it 'responds with json if asked' do - go_to_index(format: :json) - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to be_kind_of(Array) - end + it_behaves_like 'renders 200 for html and 404 for json' end end end diff --git a/spec/controllers/projects/registry/tags_controller_spec.rb b/spec/controllers/projects/registry/tags_controller_spec.rb index 5bff89b4308..c03a280d2cd 100644 --- a/spec/controllers/projects/registry/tags_controller_spec.rb +++ b/spec/controllers/projects/registry/tags_controller_spec.rb @@ -42,7 +42,7 @@ RSpec.describe Projects::Registry::TagsController do it 'tracks the event', :snowplow do get_tags - expect_snowplow_event(category: anything, action: 'list_tags') + expect_snowplow_event(category: 'Projects::Registry::TagsController', action: 'list_tags') end end @@ -107,11 +107,12 @@ RSpec.describe Projects::Registry::TagsController do destroy_tag('test.') end - it 'tracks the event' do + it 'tracks the event', :snowplow do expect_delete_tags(%w[test.]) - expect(controller).to receive(:track_event).with(:delete_tag) destroy_tag('test.') + + expect_snowplow_event(category: 'Projects::Registry::TagsController', action: 'delete_tag') end end end diff --git a/spec/controllers/projects/releases/evidences_controller_spec.rb b/spec/controllers/projects/releases/evidences_controller_spec.rb index 0ec4cdf2a31..68433969d69 100644 --- a/spec/controllers/projects/releases/evidences_controller_spec.rb +++ b/spec/controllers/projects/releases/evidences_controller_spec.rb @@ -7,6 +7,7 @@ RSpec.describe Projects::Releases::EvidencesController do let_it_be(:private_project) { create(:project, :repository, :private) } let_it_be(:developer) { create(:user) } let_it_be(:reporter) { create(:user) } + let(:user) { developer } before do @@ -62,7 +63,7 @@ RSpec.describe Projects::Releases::EvidencesController do context 'when the release was created before evidence existed' do before do - evidence.destroy + evidence.destroy! end it_behaves_like 'not found' diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb index fc7ab88bbe0..a1e36ec5c4c 100644 --- a/spec/controllers/projects/releases_controller_spec.rb +++ b/spec/controllers/projects/releases_controller_spec.rb @@ -11,6 +11,7 @@ RSpec.describe Projects::ReleasesController do let_it_be(:reporter) { create(:user) } let_it_be(:guest) { create(:user) } let_it_be(:user) { developer } + let!(:release_1) { create(:release, project: project, released_at: Time.zone.parse('2018-10-18')) } let!(:release_2) { create(:release, project: project, released_at: Time.zone.parse('2019-10-19')) } diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb index e6327a72a68..cb2579b800a 100644 --- a/spec/controllers/projects/repositories_controller_spec.rb +++ b/spec/controllers/projects/repositories_controller_spec.rb @@ -56,28 +56,6 @@ RSpec.describe Projects::RepositoriesController do expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:") end - it 'handles legacy queries with no ref' do - get :archive, params: { namespace_id: project.namespace, project_id: project }, format: "zip" - - expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:") - end - - it 'handles legacy queries with the ref specified as ref in params' do - get :archive, params: { namespace_id: project.namespace, project_id: project, ref: 'feature' }, format: 'zip' - - expect(response).to have_gitlab_http_status(:ok) - expect(assigns(:ref)).to eq('feature') - expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:") - end - - it 'handles legacy queries with the ref specified as id in params' do - get :archive, params: { namespace_id: project.namespace, project_id: project, id: 'feature' }, format: 'zip' - - expect(response).to have_gitlab_http_status(:ok) - expect(assigns(:ref)).to eq('feature') - expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:") - end - it 'prioritizes the id param over the ref param when both are specified' do get :archive, params: { namespace_id: project.namespace, project_id: project, id: 'feature', ref: 'feature_conflict' }, format: 'zip' diff --git a/spec/controllers/projects/runners_controller_spec.rb b/spec/controllers/projects/runners_controller_spec.rb index d63d88f8283..3021ad42c9f 100644 --- a/spec/controllers/projects/runners_controller_spec.rb +++ b/spec/controllers/projects/runners_controller_spec.rb @@ -46,7 +46,7 @@ RSpec.describe Projects::RunnersController do describe '#resume' do it 'marks the runner as active and ticks the queue' do - runner.update(active: false) + runner.update!(active: false) expect do post :resume, params: params @@ -61,7 +61,7 @@ RSpec.describe Projects::RunnersController do describe '#pause' do it 'marks the runner as inactive and ticks the queue' do - runner.update(active: true) + runner.update!(active: true) expect do post :pause, params: params diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb index 8f928cf3382..488a34b74df 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe Projects::ServicesController do include JiraServiceHelper + include AfterNextHelpers let(:project) { create(:project, :repository) } let(:user) { create(:user) } @@ -13,7 +14,6 @@ RSpec.describe Projects::ServicesController do before do sign_in(user) project.add_maintainer(user) - allow(Gitlab::UrlBlocker).to receive(:validate!).and_return([URI.parse('http://example.com'), nil]) end describe '#test' do @@ -114,7 +114,7 @@ RSpec.describe Projects::ServicesController do end context 'failure' do - it 'returns success status code and the error message' do + it 'returns an error response when the integration test fails' do stub_request(:get, 'http://example.com/rest/api/2/serverInfo') .to_return(status: 404) @@ -128,6 +128,36 @@ RSpec.describe Projects::ServicesController do 'test_failed' => true ) end + + context 'with the Slack integration' do + let_it_be(:service) { build(:slack_service) } + + it 'returns an error response when the URL is blocked' do + put :test, params: project_params(service: { webhook: 'http://127.0.0.1' }) + + expect(response).to be_successful + expect(json_response).to eq( + 'error' => true, + 'message' => 'Connection failed. Please check your settings.', + 'service_response' => "URL 'http://127.0.0.1' is blocked: Requests to localhost are not allowed", + 'test_failed' => true + ) + end + + it 'returns an error response when a network exception is raised' do + expect_next(SlackService).to receive(:test).and_raise(Errno::ECONNREFUSED) + + put :test, params: project_params + + expect(response).to be_successful + expect(json_response).to eq( + 'error' => true, + 'message' => 'Connection failed. Please check your settings.', + 'service_response' => 'Connection refused', + 'test_failed' => true + ) + end + end end end diff --git a/spec/controllers/projects/settings/access_tokens_controller_spec.rb b/spec/controllers/projects/settings/access_tokens_controller_spec.rb index ff52b2a765a..2a7e3d0b322 100644 --- a/spec/controllers/projects/settings/access_tokens_controller_spec.rb +++ b/spec/controllers/projects/settings/access_tokens_controller_spec.rb @@ -4,7 +4,8 @@ require('spec_helper') RSpec.describe Projects::Settings::AccessTokensController 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(:bot_user) { create(:user, :project_bot) } before_all do @@ -40,6 +41,26 @@ RSpec.describe Projects::Settings::AccessTokensController do it_behaves_like 'feature unavailable' it_behaves_like 'project access tokens available #create' + + context 'when project access token creation is disabled' do + before do + group.namespace_settings.update_column(:resource_access_token_creation_allowed, false) + end + + it { is_expected.to have_gitlab_http_status(:not_found) } + + it 'does not create the token' do + expect { subject }.not_to change { PersonalAccessToken.count } + end + + it 'does not add the project bot as a member' do + expect { subject }.not_to change { Member.count } + end + + it 'does not create the project bot user' do + expect { subject }.not_to change { User.count } + end + end end describe '#revoke', :sidekiq_inline do diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb index 7a6e11d53d4..d953249c139 100644 --- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb @@ -5,6 +5,7 @@ require('spec_helper') RSpec.describe Projects::Settings::CiCdController do let_it_be(:user) { create(:user) } let_it_be(:project_auto_devops) { create(:project_auto_devops) } + let(:project) { project_auto_devops.project } before do diff --git a/spec/controllers/projects/settings/operations_controller_spec.rb b/spec/controllers/projects/settings/operations_controller_spec.rb index 46f69eaf96a..d2934ec4e97 100644 --- a/spec/controllers/projects/settings/operations_controller_spec.rb +++ b/spec/controllers/projects/settings/operations_controller_spec.rb @@ -493,6 +493,7 @@ RSpec.describe Projects::Settings::OperationsController do describe 'PATCH #update' do let_it_be(:external_url) { 'https://gitlab.com' } + let(:params) do { tracing_setting_attributes: { diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb index 793ffbbfad9..1a6c0974f08 100644 --- a/spec/controllers/projects/snippets_controller_spec.rb +++ b/spec/controllers/projects/snippets_controller_spec.rb @@ -46,6 +46,10 @@ RSpec.describe Projects::SnippetsController do let(:params) { base_params } end + it_behaves_like 'snippets views' do + let(:params) { base_params } + end + context 'when the project snippet is private' do let_it_be(:project_snippet) { create(:project_snippet, :private, project: project, author: user) } diff --git a/spec/controllers/projects/starrers_controller_spec.rb b/spec/controllers/projects/starrers_controller_spec.rb index 66888fa3024..8d03600cd58 100644 --- a/spec/controllers/projects/starrers_controller_spec.rb +++ b/spec/controllers/projects/starrers_controller_spec.rb @@ -170,7 +170,7 @@ RSpec.describe Projects::StarrersController do context 'when project is private' do before do - project.update(visibility_level: Project::PRIVATE) + project.update!(visibility_level: Project::PRIVATE) end it 'starrers are not visible for non logged in users' do diff --git a/spec/controllers/projects/static_site_editor_controller_spec.rb b/spec/controllers/projects/static_site_editor_controller_spec.rb index b563f3b667f..73b0e3bba69 100644 --- a/spec/controllers/projects/static_site_editor_controller_spec.rb +++ b/spec/controllers/projects/static_site_editor_controller_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe Projects::StaticSiteEditorController do let_it_be(:project) { create(:project, :public, :repository) } let_it_be(:user) { create(:user) } + let(:data) { { key: 'value' } } describe 'GET index' do diff --git a/spec/controllers/projects/todos_controller_spec.rb b/spec/controllers/projects/todos_controller_spec.rb index 0e35f401bc8..9a73417ffdb 100644 --- a/spec/controllers/projects/todos_controller_spec.rb +++ b/spec/controllers/projects/todos_controller_spec.rb @@ -5,6 +5,7 @@ require('spec_helper') RSpec.describe Projects::TodosController do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } + let(:issue) { create(:issue, project: project) } let(:merge_request) { create(:merge_request, source_project: project) } let(:design) { create(:design, project: project, issue: issue) } diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb index dda58f06a37..c008c7253d8 100644 --- a/spec/controllers/projects/uploads_controller_spec.rb +++ b/spec/controllers/projects/uploads_controller_spec.rb @@ -29,7 +29,7 @@ RSpec.describe Projects::UploadsController do let!(:upload) { create(:upload, :issuable_upload, :with_file, model: model) } let(:project) { model } let(:upload_path) { File.basename(upload.path) } - let!(:redirect_route) { project.redirect_routes.create(path: project.full_path + 'old') } + let!(:redirect_route) { project.redirect_routes.create!(path: project.full_path + 'old') } it 'redirects to a file with the proper extension' do get :show, params: { namespace_id: project.namespace, project_id: project.to_param + 'old', filename: File.basename(upload.path), secret: upload.secret } diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 554487db8f2..ffe2d393b1e 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -10,6 +10,7 @@ RSpec.describe ProjectsController do let_it_be(:project, reload: true) { create(:project, service_desk_enabled: false) } let_it_be(:public_project) { create(:project, :public) } let_it_be(:user) { create(:user) } + let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') } let(:txt) { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') } @@ -159,7 +160,7 @@ RSpec.describe ProjectsController do before do setting = user.notification_settings_for(public_project) setting.level = :watch - setting.save + setting.save! end it "shows current notification setting" do @@ -221,24 +222,23 @@ RSpec.describe ProjectsController do allow(controller).to receive(:record_experiment_user) end - context 'when user can push to default branch' do + context 'when user can push to default branch', :experiment do let(:user) { empty_project.owner } - it 'creates an "view_project_show" experiment tracking event', :snowplow do - allow_next_instance_of(ApplicationExperiment) do |e| - allow(e).to receive(:should_track?).and_return(true) - end + it 'creates an "view_project_show" experiment tracking event' do + expect(experiment(:empty_repo_upload)).to track( + :view_project_show, + property: 'empty' + ).on_next_instance get :show, params: { namespace_id: empty_project.namespace, id: empty_project } - - expect_snowplow_event(category: 'empty_repo_upload', action: 'view_project_show', context: [{ schema: 'iglu:com.gitlab/gitlab_experiment/jsonschema/0-3-0', data: anything }], property: 'empty') end end User.project_views.keys.each do |project_view| context "with #{project_view} view set" do before do - user.update(project_view: project_view) + user.update!(project_view: project_view) get :show, params: { namespace_id: empty_project.namespace, id: empty_project } end @@ -261,7 +261,7 @@ RSpec.describe ProjectsController do User.project_views.keys.each do |project_view| context "with #{project_view} view set" do before do - user.update(project_view: project_view) + user.update!(project_view: project_view) get :show, params: { namespace_id: empty_project.namespace, id: empty_project } end @@ -444,7 +444,13 @@ RSpec.describe ProjectsController do :created, property: 'blank', value: 1 - ).on_any_instance.with_context(actor: user) + ).with_context(actor: user).on_next_instance + + post :create, params: { project: project_params } + end + + it 'tracks a created event for the new_repo experiment', :experiment do + expect(experiment(:new_repo, :candidate)).to track(:project_created).on_next_instance post :create, params: { project: project_params } end @@ -549,6 +555,7 @@ RSpec.describe ProjectsController do describe '#housekeeping' do let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, group: group) } + let(:housekeeping) { Repositories::HousekeepingService.new(project) } context 'when authenticated as owner' do @@ -1098,6 +1105,7 @@ RSpec.describe ProjectsController do context 'state filter on references' do let_it_be(:issue) { create(:issue, :closed, project: public_project) } + let(:merge_request) { create(:merge_request, :closed, target_project: public_project) } it 'renders JSON body with state filter for issues' do diff --git a/spec/controllers/registrations/welcome_controller_spec.rb b/spec/controllers/registrations/welcome_controller_spec.rb index d32c936b8c9..008259a8bfa 100644 --- a/spec/controllers/registrations/welcome_controller_spec.rb +++ b/spec/controllers/registrations/welcome_controller_spec.rb @@ -60,8 +60,10 @@ RSpec.describe Registrations::WelcomeController do end describe '#update' do + let(:email_opted_in) { '0' } + subject(:update) do - patch :update, params: { user: { role: 'software_developer', setup_for_company: 'false' } } + patch :update, params: { user: { role: 'software_developer', setup_for_company: 'false', email_opted_in: email_opted_in } } end context 'without a signed in user' do @@ -74,6 +76,24 @@ RSpec.describe Registrations::WelcomeController do end it { is_expected.to redirect_to(dashboard_projects_path)} + + context 'when the user opted in' do + let(:email_opted_in) { '1' } + + it 'sets the email_opted_in field' do + subject + + expect(controller.current_user.email_opted_in).to eq(true) + end + end + + context 'when the user opted out' do + it 'sets the email_opted_in field' do + subject + + expect(controller.current_user.email_opted_in).to eq(false) + end + end end end end diff --git a/spec/controllers/root_controller_spec.rb b/spec/controllers/root_controller_spec.rb index 49841aa61d7..01ff646274a 100644 --- a/spec/controllers/root_controller_spec.rb +++ b/spec/controllers/root_controller_spec.rb @@ -134,26 +134,6 @@ RSpec.describe RootController do expect(response).to render_template 'dashboard/projects/index' end - - context 'when customize_homepage is enabled' do - it 'renders the default dashboard' do - get :index - - expect(assigns[:customize_homepage]).to be true - end - end - - context 'when customize_homepage is not enabled' do - before do - stub_feature_flags(customize_homepage: false) - end - - it 'renders the default dashboard' do - get :index - - expect(assigns[:customize_homepage]).to be false - end - end end end end diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index c31ba6fe156..abdafa2880a 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -86,7 +86,7 @@ RSpec.describe SessionsController do post(:create, params: { user: { login: 'invalid', password: 'invalid' } }) expect(response) - .to set_flash.now[:alert].to(/Invalid Login or password/) + .to set_flash.now[:alert].to(/Invalid login or password/) end end @@ -348,7 +348,7 @@ RSpec.describe SessionsController do otp_user_id: user.id ) - expect(response).to set_flash.now[:alert].to(/Invalid Login or password/) + expect(response).to set_flash.now[:alert].to(/Invalid login or password/) end end @@ -524,7 +524,7 @@ RSpec.describe SessionsController do it 'sets the username and caller_id in the context' do expect(controller).to receive(:destroy).and_wrap_original do |m, *args| - expect(Labkit::Context.current.to_h) + expect(Gitlab::ApplicationContext.current) .to include('meta.user' => user.username, 'meta.caller_id' => 'SessionsController#destroy') @@ -538,9 +538,9 @@ RSpec.describe SessionsController do context 'when not signed in' do it 'sets the caller_id in the context' do expect(controller).to receive(:new).and_wrap_original do |m, *args| - expect(Labkit::Context.current.to_h) + expect(Gitlab::ApplicationContext.current) .to include('meta.caller_id' => 'SessionsController#new') - expect(Labkit::Context.current.to_h) + expect(Gitlab::ApplicationContext.current) .not_to include('meta.user') m.call(*args) @@ -557,9 +557,9 @@ RSpec.describe SessionsController do it 'sets the caller_id in the context' do allow_any_instance_of(User).to receive(:lock_access!).and_wrap_original do |m, *args| - expect(Labkit::Context.current.to_h) + expect(Gitlab::ApplicationContext.current) .to include('meta.caller_id' => 'SessionsController#create') - expect(Labkit::Context.current.to_h) + expect(Gitlab::ApplicationContext.current) .not_to include('meta.user') m.call(*args) diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb index d292ba60a12..908d5741709 100644 --- a/spec/db/schema_spec.rb +++ b/spec/db/schema_spec.rb @@ -19,7 +19,6 @@ RSpec.describe 'Database schema' do approver_groups: %w[target_id], approvers: %w[target_id user_id], audit_events: %w[author_id entity_id target_id], - audit_events_archived: %w[author_id entity_id target_id], award_emoji: %w[awardable_id user_id], aws_roles: %w[role_external_id], boards: %w[milestone_id iteration_id], @@ -86,9 +85,7 @@ RSpec.describe 'Database schema' do users: %w[color_scheme_id created_by_id theme_id email_opted_in_source_id], users_star_projects: %w[user_id], vulnerability_identifiers: %w[external_id], - vulnerability_scanners: %w[external_id], - web_hooks: %w[group_id], - web_hook_logs_part_0c5294f417: %w[web_hook_id] + vulnerability_scanners: %w[external_id] }.with_indifferent_access.freeze context 'for table' do @@ -115,7 +112,7 @@ RSpec.describe 'Database schema' do # postgres and mysql both automatically create an index on the primary # key. Also, the rails connection.indexes() method does not return # automatically generated indexes (like the primary key index). - first_indexed_column = first_indexed_column.push(primary_key_column) + first_indexed_column.push(primary_key_column) expect(first_indexed_column.uniq).to include(*foreign_keys_columns) end diff --git a/spec/deprecation_toolkit_env.rb b/spec/deprecation_toolkit_env.rb index f4ead6d5f01..fb70a8844a1 100644 --- a/spec/deprecation_toolkit_env.rb +++ b/spec/deprecation_toolkit_env.rb @@ -55,12 +55,10 @@ module DeprecationToolkitEnv # one by one def self.allowed_kwarg_warning_paths %w[ - activerecord-6.0.3.4/lib/active_record/migration.rb - devise-4.7.3/lib/devise/test/controller_helpers.rb - activesupport-6.0.3.4/lib/active_support/cache.rb - batch-loader-1.4.0/lib/batch_loader/graphql.rb + activerecord-6.0.3.6/lib/active_record/migration.rb + activesupport-6.0.3.6/lib/active_support/cache.rb carrierwave-1.3.1/lib/carrierwave/sanitized_file.rb - activerecord-6.0.3.4/lib/active_record/relation.rb + activerecord-6.0.3.6/lib/active_record/relation.rb selenium-webdriver-3.142.7/lib/selenium/webdriver/firefox/driver.rb asciidoctor-2.0.12/lib/asciidoctor/extensions.rb ] diff --git a/spec/experiments/application_experiment_spec.rb b/spec/experiments/application_experiment_spec.rb index 2481ee5a806..424a3af20a3 100644 --- a/spec/experiments/application_experiment_spec.rb +++ b/spec/experiments/application_experiment_spec.rb @@ -114,7 +114,7 @@ RSpec.describe ApplicationExperiment, :experiment do data: { data: '_data_' } }, { - schema: 'iglu:com.gitlab/gitlab_experiment/jsonschema/0-3-0', + schema: 'iglu:com.gitlab/gitlab_experiment/jsonschema/1-0-0', data: { experiment: 'namespaced/stub', key: '86208ac54ca798e11f127e8b23ec396a', variant: 'control' } } ] diff --git a/spec/experiments/members/invite_email_experiment_spec.rb b/spec/experiments/members/invite_email_experiment_spec.rb index 539230e39b9..a9a269347e0 100644 --- a/spec/experiments/members/invite_email_experiment_spec.rb +++ b/spec/experiments/members/invite_email_experiment_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Members::InviteEmailExperiment do +RSpec.describe Members::InviteEmailExperiment, :clean_gitlab_redis_shared_state do subject(:invite_email) { experiment('members/invite_email', **context) } let(:context) { { actor: double('Member', created_by: double('User', avatar_url: '_avatar_url_')) } } @@ -23,7 +23,7 @@ RSpec.describe Members::InviteEmailExperiment do end end - describe "variant resolution", :clean_gitlab_redis_shared_state do + describe "variant resolution" do it "proves out round robin in variant selection", :aggregate_failures do instance_1 = described_class.new('members/invite_email', **context) allow(instance_1).to receive(:enabled?).and_return(true) @@ -45,4 +45,69 @@ RSpec.describe Members::InviteEmailExperiment do expect(instance_3.variant.name).to eq('avatar') end end + + describe Members::RoundRobin do + subject(:round_robin) { Members::RoundRobin.new('_key_', %i[variant1 variant2]) } + + describe "execute" do + context "when there are 2 variants" do + it "proves out round robin in selection", :aggregate_failures do + expect(round_robin.execute).to eq :variant2 + expect(round_robin.execute).to eq :variant1 + expect(round_robin.execute).to eq :variant2 + end + end + + context "when there are more than 2 variants" do + subject(:round_robin) { Members::RoundRobin.new('_key_', %i[variant1 variant2 variant3]) } + + it "proves out round robin in selection", :aggregate_failures do + expect(round_robin.execute).to eq :variant2 + expect(round_robin.execute).to eq :variant3 + expect(round_robin.execute).to eq :variant1 + + expect(round_robin.execute).to eq :variant2 + expect(round_robin.execute).to eq :variant3 + expect(round_robin.execute).to eq :variant1 + end + end + + context "when writing to cache fails" do + subject(:round_robin) { Members::RoundRobin.new('_key_', []) } + + it "raises an error and logs" do + allow(Gitlab::Redis::SharedState).to receive(:with).and_raise(Members::RoundRobin::CacheError) + expect(Gitlab::AppLogger).to receive(:warn) + + expect { round_robin.execute }.to raise_error(Members::RoundRobin::CacheError) + end + end + end + + describe "#counter_expires_in" do + it 'displays the expiration time in seconds' do + round_robin.execute + + expect(round_robin.counter_expires_in).to be_between(0, described_class::COUNTER_EXPIRE_TIME) + end + end + + describe '#value' do + it 'get the count' do + expect(round_robin.counter_value).to eq(0) + + round_robin.execute + + expect(round_robin.counter_value).to eq(1) + end + end + + describe '#reset!' do + it 'resets the count down to zero' do + 3.times { round_robin.execute } + + expect { round_robin.reset! }.to change { round_robin.counter_value }.from(3).to(0) + end + end + end end diff --git a/spec/experiments/new_project_readme_experiment_spec.rb b/spec/experiments/new_project_readme_experiment_spec.rb index 17e28cf6e7f..87446394bff 100644 --- a/spec/experiments/new_project_readme_experiment_spec.rb +++ b/spec/experiments/new_project_readme_experiment_spec.rb @@ -7,10 +7,6 @@ RSpec.describe NewProjectReadmeExperiment, :experiment do let(:actor) { User.new(id: 42, created_at: Time.current) } - before do - stub_experiments(new_project_readme: :control) - end - describe "exclusions" do let(:threshold) { described_class::MAX_ACCOUNT_AGE } diff --git a/spec/experiments/strategy/round_robin_spec.rb b/spec/experiments/strategy/round_robin_spec.rb deleted file mode 100644 index f837a4701b2..00000000000 --- a/spec/experiments/strategy/round_robin_spec.rb +++ /dev/null @@ -1,68 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Strategy::RoundRobin, :clean_gitlab_redis_shared_state do - subject(:round_robin) { described_class.new('_key_', %i[variant1 variant2]) } - - describe "execute" do - context "when there are 2 variants" do - it "proves out round robin in selection", :aggregate_failures do - expect(round_robin.execute).to eq :variant2 - expect(round_robin.execute).to eq :variant1 - expect(round_robin.execute).to eq :variant2 - end - end - - context "when there are more than 2 variants" do - subject(:round_robin) { described_class.new('_key_', %i[variant1 variant2 variant3]) } - - it "proves out round robin in selection", :aggregate_failures do - expect(round_robin.execute).to eq :variant2 - expect(round_robin.execute).to eq :variant3 - expect(round_robin.execute).to eq :variant1 - - expect(round_robin.execute).to eq :variant2 - expect(round_robin.execute).to eq :variant3 - expect(round_robin.execute).to eq :variant1 - end - end - - context "when writing to cache fails" do - subject(:round_robin) { described_class.new('_key_', []) } - - it "raises an error and logs" do - allow(Gitlab::Redis::SharedState).to receive(:with).and_raise(Strategy::RoundRobin::CacheError) - expect(Gitlab::AppLogger).to receive(:warn) - - expect { round_robin.execute }.to raise_error(Strategy::RoundRobin::CacheError) - end - end - end - - describe "#counter_expires_in" do - it 'displays the expiration time in seconds' do - round_robin.execute - - expect(round_robin.counter_expires_in).to be_between(0, described_class::COUNTER_EXPIRE_TIME) - end - end - - describe '#value' do - it 'get the count' do - expect(round_robin.counter_value).to eq(0) - - round_robin.execute - - expect(round_robin.counter_value).to eq(1) - end - end - - describe '#reset!' do - it 'resets the count down to zero' do - 3.times { round_robin.execute } - - expect { round_robin.reset! }.to change { round_robin.counter_value }.from(3).to(0) - end - end -end diff --git a/spec/factories/atlassian_identities.rb b/spec/factories/atlassian_identities.rb index 698cf4ae7ad..80420e335a9 100644 --- a/spec/factories/atlassian_identities.rb +++ b/spec/factories/atlassian_identities.rb @@ -3,7 +3,7 @@ FactoryBot.define do factory :atlassian_identity, class: 'Atlassian::Identity' do extern_uid { generate(:username) } - user { create(:user) } + user { association(:user) } expires_at { 2.weeks.from_now } token { SecureRandom.alphanumeric(1254) } refresh_token { SecureRandom.alphanumeric(45) } diff --git a/spec/factories/bulk_import/trackers.rb b/spec/factories/bulk_import/trackers.rb index 03af5b41e0f..94340b0f389 100644 --- a/spec/factories/bulk_import/trackers.rb +++ b/spec/factories/bulk_import/trackers.rb @@ -5,7 +5,19 @@ FactoryBot.define do association :entity, factory: :bulk_import_entity stage { 0 } - relation { :relation } has_next_page { false } + sequence(:pipeline_name) { |n| "pipeline_name_#{n}" } + + trait :started do + status { 1 } + + sequence(:jid) { |n| "bulk_import_entity_#{n}" } + end + + trait :finished do + status { 2 } + + sequence(:jid) { |n| "bulk_import_entity_#{n}" } + end end end diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index 886be520668..b06d581d2c0 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -30,6 +30,21 @@ FactoryBot.define do yaml_variables { nil } end + trait :unique_name do + name { generate(:job_name) } + end + + trait :dependent do + transient do + sequence(:needed_name) { |n| "dependency #{n}" } + needed { association(:ci_build, name: needed_name, pipeline: pipeline) } + end + + after(:create) do |build, evaluator| + build.needs << create(:ci_build_need, build: build, name: evaluator.needed.name) + end + end + trait :started do started_at { 'Di 29. Okt 09:51:28 CET 2013' } end diff --git a/spec/factories/ci/pipeline_artifacts.rb b/spec/factories/ci/pipeline_artifacts.rb index 05ff7afed7c..3250c7abb4b 100644 --- a/spec/factories/ci/pipeline_artifacts.rb +++ b/spec/factories/ci/pipeline_artifacts.rb @@ -13,6 +13,22 @@ FactoryBot.define do Rails.root.join('spec/fixtures/pipeline_artifacts/code_coverage.json'), 'application/json') end + trait :checksummed do + verification_checksum { 'abc' } + end + + trait :checksum_failure do + verification_failure { 'Could not calculate the checksum' } + end + + trait :expired do + expire_at { Date.yesterday } + end + + trait :remote_store do + file_store { ::ObjectStorage::Store::REMOTE} + end + trait :with_coverage_report do file_type { :code_coverage } diff --git a/spec/factories/ci/reports/codequality_degradations.rb b/spec/factories/ci/reports/codequality_degradations.rb index d82157b457a..8b53f2bf46e 100644 --- a/spec/factories/ci/reports/codequality_degradations.rb +++ b/spec/factories/ci/reports/codequality_degradations.rb @@ -95,4 +95,47 @@ FactoryBot.define do }.with_indifferent_access end end + + # TODO: Use this in all other specs and remove the previous numbered factories + # https://gitlab.com/gitlab-org/gitlab/-/issues/325886 + factory :codequality_degradation, class: Hash do + skip_create + + # Feel free to add in more configurable properties here + # as the need arises + fingerprint { SecureRandom.hex } + severity { "major" } + + Gitlab::Ci::Reports::CodequalityReports::SEVERITY_PRIORITIES.keys.each do |s| + trait s.to_sym do + severity { s } + end + end + + initialize_with do + { + "categories": [ + "Complexity" + ], + "check_name": "argument_count", + "content": { + "body": "" + }, + "description": "Avoid parameter lists longer than 5 parameters. [12/5]", + "fingerprint": fingerprint, + "location": { + "path": "file_a.rb", + "lines": { + "begin": 10, + "end": 10 + } + }, + "other_locations": [], + "remediation_points": 900000, + "severity": severity, + "type": "issue", + "engine_name": "structure" + }.with_indifferent_access + end + end end diff --git a/spec/factories/ci/test_case.rb b/spec/factories/ci/test_case.rb deleted file mode 100644 index 601a3fae970..00000000000 --- a/spec/factories/ci/test_case.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -FactoryBot.define do - factory :ci_test_case, class: 'Ci::TestCase' do - project - key_hash { Digest::SHA256.hexdigest(SecureRandom.hex) } - end -end diff --git a/spec/factories/ci/test_case_failure.rb b/spec/factories/ci/test_case_failure.rb deleted file mode 100644 index 11fb002804b..00000000000 --- a/spec/factories/ci/test_case_failure.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -FactoryBot.define do - factory :ci_test_case_failure, class: 'Ci::TestCaseFailure' do - build factory: :ci_build - test_case factory: :ci_test_case - failed_at { Time.current } - end -end diff --git a/spec/factories/ci/unit_test.rb b/spec/factories/ci/unit_test.rb new file mode 100644 index 00000000000..480724f260a --- /dev/null +++ b/spec/factories/ci/unit_test.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :ci_unit_test, class: 'Ci::UnitTest' do + project + suite_name { 'rspec' } + name { 'Math#add returns sum' } + key_hash { Digest::SHA256.hexdigest(SecureRandom.hex) } + end +end diff --git a/spec/factories/ci/unit_test_failure.rb b/spec/factories/ci/unit_test_failure.rb new file mode 100644 index 00000000000..07cd3419754 --- /dev/null +++ b/spec/factories/ci/unit_test_failure.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :ci_unit_test_failure, class: 'Ci::UnitTestFailure' do + build factory: :ci_build + unit_test factory: :ci_unit_test + failed_at { Time.current } + end +end diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb index 01df5cc677d..1ff1292c36e 100644 --- a/spec/factories/clusters/applications/helm.rb +++ b/spec/factories/clusters/applications/helm.rb @@ -4,18 +4,26 @@ FactoryBot.define do factory :clusters_applications_helm, class: 'Clusters::Applications::Helm' do cluster factory: %i(cluster provided_by_gcp) - before(:create) do - allow(Gitlab::Kubernetes::Helm::V2::Certificate).to receive(:generate_root) - .and_return( - double( - key_string: File.read(Rails.root.join('spec/fixtures/clusters/sample_key.key')), - cert_string: File.read(Rails.root.join('spec/fixtures/clusters/sample_cert.pem')) + transient do + helm_installed { true } + end + + before(:create) do |_record, evaluator| + if evaluator.helm_installed + allow(Gitlab::Kubernetes::Helm::V2::Certificate).to receive(:generate_root) + .and_return( + double( + key_string: File.read(Rails.root.join('spec/fixtures/clusters/sample_key.key')), + cert_string: File.read(Rails.root.join('spec/fixtures/clusters/sample_cert.pem')) + ) ) - ) + end end - after(:create) do - allow(Gitlab::Kubernetes::Helm::V2::Certificate).to receive(:generate_root).and_call_original + after(:create) do |_record, evaluator| + if evaluator.helm_installed + allow(Gitlab::Kubernetes::Helm::V2::Certificate).to receive(:generate_root).and_call_original + end end trait :not_installable do @@ -69,19 +77,28 @@ FactoryBot.define do status { 10 } end + trait :externally_installed do + status { 11 } + end + trait :timed_out do installing updated_at { ClusterWaitForAppInstallationWorker::TIMEOUT.ago } end + # Common trait used by the apps below + trait :no_helm_installed do + cluster factory: %i(cluster provided_by_gcp) + + transient do + helm_installed { false } + end + end + factory :clusters_applications_ingress, class: 'Clusters::Applications::Ingress' do modsecurity_enabled { false } cluster factory: %i(cluster with_installed_helm provided_by_gcp) - trait :no_helm_installed do - cluster factory: %i(cluster provided_by_gcp) - end - trait :modsecurity_blocking do modsecurity_enabled { true } modsecurity_mode { :blocking } @@ -104,62 +121,34 @@ FactoryBot.define do factory :clusters_applications_cert_manager, class: 'Clusters::Applications::CertManager' do email { 'admin@example.com' } cluster factory: %i(cluster with_installed_helm provided_by_gcp) - - trait :no_helm_installed do - cluster factory: %i(cluster provided_by_gcp) - end end factory :clusters_applications_elastic_stack, class: 'Clusters::Applications::ElasticStack' 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 factory :clusters_applications_crossplane, class: 'Clusters::Applications::Crossplane' do stack { 'gcp' } cluster factory: %i(cluster with_installed_helm provided_by_gcp) - - trait :no_helm_installed do - cluster factory: %i(cluster provided_by_gcp) - end end factory :clusters_applications_prometheus, class: 'Clusters::Applications::Prometheus' 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 factory :clusters_applications_runner, class: 'Clusters::Applications::Runner' do runner factory: %i(ci_runner) cluster factory: %i(cluster with_installed_helm provided_by_gcp) - - trait :no_helm_installed do - cluster factory: %i(cluster provided_by_gcp) - end end factory :clusters_applications_knative, class: 'Clusters::Applications::Knative' do hostname { 'example.com' } cluster factory: %i(cluster with_installed_helm provided_by_gcp) - - trait :no_helm_installed do - cluster factory: %i(cluster provided_by_gcp) - end end factory :clusters_applications_jupyter, class: 'Clusters::Applications::Jupyter' do oauth_application factory: :oauth_application cluster factory: %i(cluster with_installed_helm provided_by_gcp project) - - trait :no_helm_installed do - cluster factory: %i(cluster provided_by_gcp) - end end factory :clusters_applications_fluentd, class: 'Clusters::Applications::Fluentd' do @@ -167,18 +156,10 @@ FactoryBot.define do waf_log_enabled { true } cilium_log_enabled { true } cluster factory: %i(cluster with_installed_helm provided_by_gcp) - - trait :no_helm_installed 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/integrations/prometheus.rb b/spec/factories/clusters/integrations/prometheus.rb new file mode 100644 index 00000000000..1f0bb1ed512 --- /dev/null +++ b/spec/factories/clusters/integrations/prometheus.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :clusters_integrations_prometheus, class: 'Clusters::Integrations::Prometheus' do + cluster factory: %i(cluster provided_by_gcp) + enabled { true } + + trait :disabled do + enabled { false } + end + end +end diff --git a/spec/factories/draft_note.rb b/spec/factories/draft_note.rb index 67a3377a39f..cde8831f169 100644 --- a/spec/factories/draft_note.rb +++ b/spec/factories/draft_note.rb @@ -9,17 +9,30 @@ FactoryBot.define do transient do line_number { 14 } diff_refs { merge_request.try(:diff_refs) } + path { "files/ruby/popen.rb" } end position do Gitlab::Diff::Position.new( - old_path: "files/ruby/popen.rb", - new_path: "files/ruby/popen.rb", + old_path: path, + new_path: path, old_line: nil, new_line: line_number, diff_refs: diff_refs ) end + + factory :draft_note_on_image_diff do + transient do + path { "files/images/any_image.png" } + end + + position do + association(:image_diff_position, + file: path, + diff_refs: diff_refs) + end + end end factory :draft_note_on_discussion, traits: [:on_discussion] diff --git a/spec/factories/events.rb b/spec/factories/events.rb index 6c9f1ba0137..c9e4ada3ffa 100644 --- a/spec/factories/events.rb +++ b/spec/factories/events.rb @@ -27,17 +27,20 @@ FactoryBot.define do factory :wiki_page_event do action { :created } + # rubocop: disable FactoryBot/InlineAssociation + # A persistent project is needed to have a wiki page being created properly. project { @overrides[:wiki_page]&.container || create(:project, :wiki_repo) } - target { create(:wiki_page_meta, :for_wiki_page, wiki_page: wiki_page) } + # rubocop: enable FactoryBot/InlineAssociation + target { association(:wiki_page_meta, :for_wiki_page, wiki_page: wiki_page) } transient do - wiki_page { create(:wiki_page, container: project) } + wiki_page { association(:wiki_page, container: project) } end end trait :has_design do transient do - design { create(:design, issue: create(:issue, project: project)) } + design { association(:design, issue: association(:issue, project: project)) } end end @@ -45,7 +48,7 @@ FactoryBot.define do has_design transient do - note { create(:note, author: author, project: project, noteable: design) } + note { association(:note, author: author, project: project, noteable: design) } end action { :commented } diff --git a/spec/factories/git_wiki_commit_details.rb b/spec/factories/git_wiki_commit_details.rb index b35f102fd4d..fb3f2954b12 100644 --- a/spec/factories/git_wiki_commit_details.rb +++ b/spec/factories/git_wiki_commit_details.rb @@ -5,7 +5,7 @@ FactoryBot.define do skip_create transient do - author { create(:user) } + author { association(:user) } end sequence(:message) { |n| "Commit message #{n}" } diff --git a/spec/factories/gitaly/commit.rb b/spec/factories/gitaly/commit.rb index 2ed201e9aac..4e8220e449a 100644 --- a/spec/factories/gitaly/commit.rb +++ b/spec/factories/gitaly/commit.rb @@ -14,7 +14,7 @@ FactoryBot.define do subject { "My commit" } body { subject + "\nMy body" } - author { build(:gitaly_commit_author) } - committer { build(:gitaly_commit_author) } + author { association(:gitaly_commit_author) } + committer { association(:gitaly_commit_author) } end end diff --git a/spec/factories/gitlab/database/background_migration/batched_migrations.rb b/spec/factories/gitlab/database/background_migration/batched_migrations.rb index b45f6ff037b..49cbdc5a8fb 100644 --- a/spec/factories/gitlab/database/background_migration/batched_migrations.rb +++ b/spec/factories/gitlab/database/background_migration/batched_migrations.rb @@ -9,5 +9,6 @@ FactoryBot.define do job_class_name { 'CopyColumnUsingBackgroundMigrationJob' } table_name { :events } column_name { :id } + total_tuple_count { 10_000 } end end diff --git a/spec/factories/group_group_links.rb b/spec/factories/group_group_links.rb index 6f98886faff..2a582d8525b 100644 --- a/spec/factories/group_group_links.rb +++ b/spec/factories/group_group_links.rb @@ -2,8 +2,8 @@ FactoryBot.define do factory :group_group_link do - shared_group { create(:group) } - shared_with_group { create(:group) } + shared_group { association(:group) } + shared_with_group { association(:group) } group_access { Gitlab::Access::DEVELOPER } trait(:guest) { group_access { Gitlab::Access::GUEST } } diff --git a/spec/factories/import_export_uploads.rb b/spec/factories/import_export_uploads.rb index 8521411e0e8..e1dd0c10ff2 100644 --- a/spec/factories/import_export_uploads.rb +++ b/spec/factories/import_export_uploads.rb @@ -2,6 +2,6 @@ FactoryBot.define do factory :import_export_upload do - project { create(:project) } + project { association(:project) } end end diff --git a/spec/factories/packages.rb b/spec/factories/packages.rb index 882bac1daa9..9edee735af9 100644 --- a/spec/factories/packages.rb +++ b/spec/factories/packages.rb @@ -16,6 +16,10 @@ FactoryBot.define do status { :processing } end + trait :error do + status { :error } + end + factory :maven_package do maven_metadatum @@ -36,8 +40,8 @@ FactoryBot.define do package_type { :rubygems } after :create do |package| - create :package_file, :gem, package: package - create :package_file, :gemspec, package: package + create :package_file, package.processing? ? :unprocessed_gem : :gem, package: package + create :package_file, :gemspec, package: package unless package.processing? end trait(:with_metadatum) do diff --git a/spec/factories/packages/package_file.rb b/spec/factories/packages/package_file.rb index 6d8b119040e..74400975670 100644 --- a/spec/factories/packages/package_file.rb +++ b/spec/factories/packages/package_file.rb @@ -125,6 +125,9 @@ FactoryBot.define do trait(:source) do file_name { 'sample_1.2.3~alpha2.tar.xz' } + file_md5 { 'd79b34f58f61ff4ad696d9bd0b8daa68' } + file_sha1 { '5f8bba5574eb01ac3b1f5e2988e8c29307788236' } + file_sha256 { 'b5a599e88e7cbdda3bde808160a21ba1dd1ec76b2ec8d4912aae769648d68362' } transient do file_metadatum_trait { :source } @@ -133,6 +136,9 @@ FactoryBot.define do trait(:dsc) do file_name { 'sample_1.2.3~alpha2.dsc' } + file_md5 { '3b0817804f669e16cdefac583ad88f0e' } + file_sha1 { '32ecbd674f0bfd310df68484d87752490685a8d6' } + file_sha256 { '844f79825b7e8aaa191e514b58a81f9ac1e58e2180134b0c9512fa66d896d7ba' } transient do file_metadatum_trait { :dsc } @@ -141,6 +147,9 @@ FactoryBot.define do trait(:deb) do file_name { 'libsample0_1.2.3~alpha2_amd64.deb' } + file_md5 { 'fb0842b21adc44207996296fe14439dd' } + file_sha1 { '5248b95600e85bfe7f63c0dfce330a75f5777366' } + file_sha256 { '1c383a525bfcba619c7305ccd106d61db501a6bbaf0003bf8d0c429fbdb7fcc1' } transient do file_metadatum_trait { :deb } @@ -149,6 +158,9 @@ FactoryBot.define do trait(:deb2) do file_name { 'sample-dev_1.2.3~binary_amd64.deb' } + file_md5 { 'd2afbd28e4d74430d22f9504e18bfdf5' } + file_sha1 { 'f81e4f66c8c6bb899653a3340c157965ee69634f' } + file_sha256 { '9fbeee2191ce4dab5288fad5ecac1bd369f58fef9a992a880eadf0caf25f086d' } transient do file_metadatum_trait { :deb } @@ -157,6 +169,9 @@ FactoryBot.define do trait(:udeb) do file_name { 'sample-udeb_1.2.3~alpha2_amd64.udeb' } + file_md5 { '72b1dd7d98229e2fb0355feda1d3a165' } + file_sha1 { 'e42e8f2fe04ed1bb73b44a187674480d0e49dcba' } + file_sha256 { '2b0c152b3ab4cc07663350424de972c2b7621d69fe6df2e0b94308a191e4632f' } transient do file_metadatum_trait { :udeb } @@ -165,6 +180,9 @@ FactoryBot.define do trait(:buildinfo) do file_name { 'sample_1.2.3~alpha2_amd64.buildinfo' } + file_md5 { '4e085dd67c120ca967ec314f65770a42' } + file_sha1 { '0d47e899f3cc67a2253a4629456ff927e0db5c60' } + file_sha256 { 'f9900d3c94e94b329232668dcbef3dba2d96c07147b15b6dc0533452e4dd8a43' } transient do file_metadatum_trait { :buildinfo } @@ -229,6 +247,14 @@ FactoryBot.define do size { 4.kilobytes } end + trait(:unprocessed_gem) do + package + file_fixture { 'spec/fixtures/packages/rubygems/package.gem' } + file_name { 'package.gem' } + file_sha1 { '5fe852b2a6abd96c22c11fa1ff2fb19d9ce58b57' } + size { 4.kilobytes } + end + trait(:gemspec) do package file_fixture { 'spec/fixtures/packages/rubygems/package.gemspec' } diff --git a/spec/factories/sequences.rb b/spec/factories/sequences.rb index f9952cd9966..b276e6f8cfc 100644 --- a/spec/factories/sequences.rb +++ b/spec/factories/sequences.rb @@ -19,4 +19,5 @@ FactoryBot.define do sequence(:wip_title) { |n| "WIP: #{n}" } sequence(:jira_title) { |n| "[PROJ-#{n}]: fix bug" } sequence(:jira_branch) { |n| "feature/PROJ-#{n}" } + sequence(:job_name) { |n| "job #{n}" } end diff --git a/spec/factories/services.rb b/spec/factories/services.rb index 18d3b2d99b7..25ef75880bb 100644 --- a/spec/factories/services.rb +++ b/spec/factories/services.rb @@ -56,20 +56,24 @@ FactoryBot.define do api_url { nil } username { 'jira_username' } password { 'jira_password' } + jira_issue_transition_automatic { false } jira_issue_transition_id { '56-1' } issues_enabled { false } project_key { nil } vulnerabilities_enabled { false } vulnerabilities_issuetype { nil } + deployment_type { 'cloud' } end before(:create) 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, + url: evaluator.url, api_url: evaluator.api_url, + jira_issue_transition_automatic: evaluator.jira_issue_transition_automatic, + jira_issue_transition_id: evaluator.jira_issue_transition_id, username: evaluator.username, password: evaluator.password, issues_enabled: evaluator.issues_enabled, project_key: evaluator.project_key, vulnerabilities_enabled: evaluator.vulnerabilities_enabled, - vulnerabilities_issuetype: evaluator.vulnerabilities_issuetype + vulnerabilities_issuetype: evaluator.vulnerabilities_issuetype, deployment_type: evaluator.deployment_type ) end end diff --git a/spec/factories/subscriptions.rb b/spec/factories/subscriptions.rb index 2b652cd57bf..8ba96d9fa29 100644 --- a/spec/factories/subscriptions.rb +++ b/spec/factories/subscriptions.rb @@ -5,5 +5,11 @@ FactoryBot.define do project user { project.creator } subscribable factory: :issue + + trait :group_label do + project { nil } + user { association(:user) } + subscribable factory: :group_label + end end end diff --git a/spec/factories/timelogs.rb b/spec/factories/timelogs.rb index 5d34acc635d..204cb808c8e 100644 --- a/spec/factories/timelogs.rb +++ b/spec/factories/timelogs.rb @@ -1,11 +1,22 @@ # frozen_string_literal: true -# Read about factories at https://github.com/thoughtbot/factory_bot - FactoryBot.define do factory :timelog do time_spent { 3600 } - issue - user { issue.project.creator } + for_issue + + factory :issue_timelog, traits: [:for_issue] + factory :merge_request_timelog, traits: [:for_merge_request] + + trait :for_issue do + issue + user { issue.author } + end + + trait :for_merge_request do + merge_request + issue { nil } + user { merge_request.author } + end end end diff --git a/spec/factories/users/in_product_marketing_email.rb b/spec/factories/users/in_product_marketing_email.rb new file mode 100644 index 00000000000..c86c469ff31 --- /dev/null +++ b/spec/factories/users/in_product_marketing_email.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :in_product_marketing_email, class: 'Users::InProductMarketingEmail' do + user + + track { 'create' } + series { 0 } + end +end diff --git a/spec/factories_spec.rb b/spec/factories_spec.rb index 56d643d0cc9..787e0540fda 100644 --- a/spec/factories_spec.rb +++ b/spec/factories_spec.rb @@ -32,7 +32,8 @@ RSpec.describe 'factories' do [:project_member, :blocked], [:project, :remote_mirror], [:remote_mirror, :ssh], - [:user_preference, :only_comments] + [:user_preference, :only_comments], + [:ci_pipeline_artifact, :remote_store] ] end @@ -43,7 +44,7 @@ RSpec.describe 'factories' do end it 'does not raise error when created' do - expect { create(factory.name) }.not_to raise_error + expect { create(factory.name) }.not_to raise_error # rubocop:disable Rails/SaveBang end factory.definition.defined_traits.map(&:name).each do |trait_name| @@ -65,14 +66,23 @@ RSpec.describe 'factories' do # associations must be unique and cannot be reused, or the factory default # is being mutated. skip_factory_defaults = %i[ + evidence + exported_protected_branch fork_network_member group_member import_state + milestone_release namespace project_broken_repo prometheus_alert prometheus_alert_event prometheus_metric + protected_branch + protected_branch_merge_access_level + protected_branch_push_access_level + protected_tag + release + release_link self_managed_prometheus_alert_event users_star_project wiki_page diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb index 192182adddc..3a02ce89aa9 100644 --- a/spec/features/admin/admin_abuse_reports_spec.rb +++ b/spec/features/admin/admin_abuse_reports_spec.rb @@ -56,7 +56,7 @@ RSpec.describe "Admin::AbuseReports", :js do describe 'filtering by user' do let!(:user2) { create(:user) } - let!(:abuse_report) { create(:abuse_report, user: user) } + let!(:abuse_report) { create(:abuse_report, user: user) } let!(:abuse_report_2) { create(:abuse_report, user: user2) } it 'shows only single user report' do diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb index cd136af8d69..61e7efbc56c 100644 --- a/spec/features/admin/admin_appearance_spec.rb +++ b/spec/features/admin/admin_appearance_spec.rb @@ -66,7 +66,7 @@ RSpec.describe 'Admin Appearance' do context 'when system header and footer messages are not empty' do before do - appearance.update(header_message: 'Foo', footer_message: 'Bar') + appearance.update!(header_message: 'Foo', footer_message: 'Bar') end it 'shows custom system header and footer fields' do diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb index bbdf2f7f4a9..e7634f4e020 100644 --- a/spec/features/admin/admin_groups_spec.rb +++ b/spec/features/admin/admin_groups_spec.rb @@ -35,6 +35,7 @@ RSpec.describe 'Admin Groups' do expect(page).to have_field('group_path') expect(page).to have_field('group_visibility_level_0') expect(page).to have_field('description') + expect(page).to have_field('group_admin_note_attributes_note') end end @@ -47,10 +48,12 @@ RSpec.describe 'Admin Groups' do path_component = 'gitlab' group_name = 'GitLab group name' group_description = 'Description of group for GitLab' + group_admin_note = 'A note about this group by an admin' fill_in 'group_path', with: path_component fill_in 'group_name', with: group_name fill_in 'group_description', with: group_description + fill_in 'group_admin_note_attributes_note', with: group_admin_note click_button "Create group" expect(current_path).to eq admin_group_path(Group.find_by(path: path_component)) @@ -61,6 +64,8 @@ RSpec.describe 'Admin Groups' do expect(li_texts).to match group_name expect(li_texts).to match path_component expect(li_texts).to match group_description + p_texts = content.all('p').collect(&:text).join('/n') + expect(p_texts).to match group_admin_note end it 'shows the visibility level radio populated with the default value' do @@ -116,6 +121,16 @@ RSpec.describe 'Admin Groups' do expect(page).to have_link(group.name, href: group_path(group)) end + + it 'has a note if one is available' do + group = create(:group, :private) + note_text = 'A group administrator note' + group.update!(admin_note_attributes: { note: note_text }) + + visit admin_group_path(group) + + expect(page).to have_text(note_text) + end end describe 'group edit' do @@ -145,6 +160,36 @@ RSpec.describe 'Admin Groups' do expect(name_field.value).to eq original_name end + + it 'adding an admin note to group without one' do + group = create(:group, :private) + expect(group.admin_note).to be_nil + + visit admin_group_edit_path(group) + admin_note_text = 'A note by an administrator' + + fill_in 'group_admin_note_attributes_note', with: admin_note_text + click_button 'Save changes' + + expect(page).to have_content(admin_note_text) + end + + it 'editing an existing group admin note' do + admin_note_text = 'A note by an administrator' + new_admin_note_text = 'A new note by an administrator' + group = create(:group, :private) + group.create_admin_note(note: admin_note_text) + + visit admin_group_edit_path(group) + + admin_note_field = find('#group_admin_note_attributes_note') + expect(admin_note_field.value).to eq(admin_note_text) + + fill_in 'group_admin_note_attributes_note', with: new_admin_note_text + click_button 'Save changes' + + expect(page).to have_content(new_admin_note_text) + end end describe 'add user into a group', :js do diff --git a/spec/features/admin/admin_labels_spec.rb b/spec/features/admin/admin_labels_spec.rb index 815a73b1450..43fb1f31a0f 100644 --- a/spec/features/admin/admin_labels_spec.rb +++ b/spec/features/admin/admin_labels_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' RSpec.describe 'admin issues labels' do - let!(:bug_label) { Label.create(title: 'bug', template: true) } - let!(:feature_label) { Label.create(title: 'feature', template: true) } + let!(:bug_label) { Label.create!(title: 'bug', template: true) } + let!(:feature_label) { Label.create!(title: 'feature', template: true) } before do admin = create(:admin) @@ -36,7 +36,7 @@ RSpec.describe 'admin issues labels' do it 'deletes all labels', :js do page.within '.labels' do - page.all('.remove-row').each do |remove| + page.all('.js-remove-row').each do |remove| accept_confirm { remove.click } wait_for_requests end diff --git a/spec/features/admin/admin_mode/login_spec.rb b/spec/features/admin/admin_mode/login_spec.rb index f1dee075925..5b2dfdb2941 100644 --- a/spec/features/admin/admin_mode/login_spec.rb +++ b/spec/features/admin/admin_mode/login_spec.rb @@ -86,7 +86,7 @@ RSpec.describe 'Admin Mode Login' do expect(codes.size).to eq 10 # Ensure the generated codes get saved - user.save + user.save! end context 'with valid code' do diff --git a/spec/features/admin/admin_mode/logout_spec.rb b/spec/features/admin/admin_mode/logout_spec.rb index b7fa59bbfb7..8cfac5d8b99 100644 --- a/spec/features/admin/admin_mode/logout_spec.rb +++ b/spec/features/admin/admin_mode/logout_spec.rb @@ -9,6 +9,8 @@ RSpec.describe 'Admin Mode Logout', :js do let(:user) { create(:admin) } before do + stub_feature_flags(combined_menu: false) + gitlab_sign_in(user) gitlab_enable_admin_mode_sign_in(user) visit admin_root_path diff --git a/spec/features/admin/admin_mode_spec.rb b/spec/features/admin/admin_mode_spec.rb index 8169b3a20db..633de20c82d 100644 --- a/spec/features/admin/admin_mode_spec.rb +++ b/spec/features/admin/admin_mode_spec.rb @@ -9,10 +9,12 @@ RSpec.describe 'Admin mode' do let(:admin) { create(:admin) } before do + stub_feature_flags(combined_menu: false) + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') end - context 'feature flag :user_mode_in_session is enabled', :request_store do + context 'application setting :admin_mode is enabled', :request_store do before do sign_in(admin) end @@ -155,9 +157,9 @@ RSpec.describe 'Admin mode' do end end - context 'feature flag :user_mode_in_session is disabled' do + context 'application setting :admin_mode is disabled' do before do - stub_feature_flags(user_mode_in_session: false) + stub_application_setting(admin_mode: false) sign_in(admin) end diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index 4f135b81bdf..4e0dcbdf075 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -201,21 +201,21 @@ RSpec.describe "Admin Runners" do visit admin_runners_path - within '.runners-content .gl-responsive-table-row:nth-child(2)' do + within '[data-testid="runners-table"] .gl-responsive-table-row:nth-child(2)' do expect(page).to have_content 'runner-2' end - within '.runners-content .gl-responsive-table-row:nth-child(3)' do + within '[data-testid="runners-table"] .gl-responsive-table-row:nth-child(3)' do expect(page).to have_content 'runner-1' end sorting_by 'Last Contact' - within '.runners-content .gl-responsive-table-row:nth-child(2)' do + within '[data-testid="runners-table"] .gl-responsive-table-row:nth-child(2)' do expect(page).to have_content 'runner-1' end - within '.runners-content .gl-responsive-table-row:nth-child(3)' do + within '[data-testid="runners-table"] .gl-responsive-table-row:nth-child(3)' do expect(page).to have_content 'runner-2' end end @@ -285,8 +285,16 @@ RSpec.describe "Admin Runners" do end describe 'runner page breadcrumbs' do - it 'contains the current runner’s short sha' do - expect(page.find('h2')).to have_content(runner.short_sha) + it 'contains the current runner token' do + page.within '[data-testid="breadcrumb-links"]' do + expect(page.find('h2')).to have_content(runner.short_sha) + end + end + end + + describe 'runner page title', :js do + it 'contains the runner id' do + expect(find('.page-title')).to have_content("Runner ##{runner.id}") end end @@ -313,11 +321,11 @@ RSpec.describe "Admin Runners" do describe 'enable/create' do shared_examples 'assignable runner' do it 'enables a runner for a project' do - within '.unassigned-projects' do + within '[data-testid="unassigned-projects"]' do click_on 'Enable' end - assigned_project = page.find('.assigned-projects') + assigned_project = page.find('[data-testid="assigned-projects"]') expect(assigned_project).to have_content(@project2.path) end @@ -347,7 +355,7 @@ RSpec.describe "Admin Runners" do let(:runner) { create(:ci_runner, :instance) } before do - @project1.destroy + @project1.destroy! visit admin_runner_path(runner) end @@ -363,11 +371,11 @@ RSpec.describe "Admin Runners" do end it 'enables specific runner for project' do - within '.assigned-projects' do + within '[data-testid="assigned-projects"]' do click_on 'Disable' end - new_runner_project = page.find('.unassigned-projects') + new_runner_project = page.find('[data-testid="unassigned-projects"]') expect(new_runner_project).to have_content(@project1.path) end diff --git a/spec/features/admin/admin_search_settings_spec.rb b/spec/features/admin/admin_search_settings_spec.rb index a78d17a6651..cd61a1db6f3 100644 --- a/spec/features/admin/admin_search_settings_spec.rb +++ b/spec/features/admin/admin_search_settings_spec.rb @@ -20,8 +20,10 @@ RSpec.describe 'Admin searches application settings', :js do end context 'in ci/cd settings page' do - let(:visit_path) { ci_cd_admin_application_settings_path } + before do + visit(ci_cd_admin_application_settings_path) + end - it_behaves_like 'can search settings with feature flag check', 'Variables', 'Package Registry' + it_behaves_like 'can search settings', 'Variables', 'Package Registry' 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 be781730924..3433cc01b8e 100644 --- a/spec/features/admin/admin_sees_project_statistics_spec.rb +++ b/spec/features/admin/admin_sees_project_statistics_spec.rb @@ -21,7 +21,7 @@ RSpec.describe "Admin > Admin sees project statistics" do end context 'when project has no statistics' do - let(:project) { create(:project, :repository) { |project| project.statistics.destroy } } + let(:project) { create(:project, :repository) { |project| project.statistics.destroy! } } it "shows 'Storage: Unknown'" do expect(page).to have_content("Storage: Unknown") diff --git a/spec/features/admin/admin_sees_projects_statistics_spec.rb b/spec/features/admin/admin_sees_projects_statistics_spec.rb index 2e96814d1e9..d340eb47f34 100644 --- a/spec/features/admin/admin_sees_projects_statistics_spec.rb +++ b/spec/features/admin/admin_sees_projects_statistics_spec.rb @@ -7,7 +7,7 @@ RSpec.describe "Admin > Admin sees projects statistics" do before do create(:project, :repository) - create(:project, :repository) { |project| project.statistics.destroy } + create(:project, :repository) { |project| project.statistics.destroy! } sign_in(current_user) gitlab_enable_admin_mode_sign_in(current_user) diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 249621f5835..9a2e2eb2f6f 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -9,7 +9,7 @@ RSpec.describe 'Admin updates settings' do let(:admin) { create(:admin) } - context 'feature flag :user_mode_in_session is enabled', :request_store do + context 'application setting :admin_mode is enabled', :request_store do before do stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') sign_in(admin) @@ -129,7 +129,7 @@ RSpec.describe 'Admin updates settings' do context 'Change Sign-up restrictions' do context 'Require Admin approval for new signup setting' do - it 'changes the setting' do + it 'changes the setting', :js do page.within('.as-signup') do check 'Require admin approval for new sign-ups' click_button 'Save changes' @@ -249,6 +249,14 @@ RSpec.describe 'Admin updates settings' do expect(page).to have_content "Application settings saved successfully" expect(current_settings.hide_third_party_offers).to be true end + end + + context 'when the Slack Notifications Service template is active' do + before do + create(:service, :template, type: 'SlackService', active: true) + + visit general_admin_application_settings_path + end it 'change Slack Notifications Service template settings', :js do first(:link, 'Service Templates').click @@ -588,7 +596,7 @@ RSpec.describe 'Admin updates settings' do context 'Nav bar' do it 'shows default help links in nav' do - default_support_url = 'https://about.gitlab.com/getting-help/' + default_support_url = "https://#{ApplicationHelper.promo_host}/getting-help/" visit root_dashboard_path @@ -615,9 +623,9 @@ RSpec.describe 'Admin updates settings' do end end - context 'feature flag :user_mode_in_session is disabled' do + context 'application setting :admin_mode is disabled' do before do - stub_feature_flags(user_mode_in_session: false) + stub_application_setting(admin_mode: false) stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') diff --git a/spec/features/admin/admin_users_impersonation_tokens_spec.rb b/spec/features/admin/admin_users_impersonation_tokens_spec.rb index cae190e76b0..dc528dd92d4 100644 --- a/spec/features/admin/admin_users_impersonation_tokens_spec.rb +++ b/spec/features/admin/admin_users_impersonation_tokens_spec.rb @@ -75,7 +75,7 @@ RSpec.describe 'Admin > Users > Impersonation Tokens', :js do end it "removes expired tokens from 'active' section" do - impersonation_token.update(expires_at: 5.days.ago) + impersonation_token.update!(expires_at: 5.days.ago) visit admin_user_impersonation_tokens_path(user_id: user.username) diff --git a/spec/features/admin/services/admin_activates_prometheus_spec.rb b/spec/features/admin/services/admin_activates_prometheus_spec.rb deleted file mode 100644 index a225de365c8..00000000000 --- a/spec/features/admin/services/admin_activates_prometheus_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Admin activates Prometheus', :js do - let(:admin) { create(:user, :admin) } - - before do - sign_in(admin) - gitlab_enable_admin_mode_sign_in(admin) - - visit(admin_application_settings_services_path) - - click_link('Prometheus') - end - - it 'activates service' do - check('Active') - fill_in('API URL', with: 'http://prometheus.example.com') - click_button('Save changes') - - expect(page).to have_content('Application settings saved successfully') - 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 index 563bca8b32f..1fd8c8316e3 100644 --- a/spec/features/admin/services/admin_visits_service_templates_spec.rb +++ b/spec/features/admin/services/admin_visits_service_templates_spec.rb @@ -9,23 +9,45 @@ RSpec.describe 'Admin visits service templates' do before do sign_in(admin) gitlab_enable_admin_mode_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)) + context 'without an active service template' do + before do + visit(admin_application_settings_services_path) + end + + it 'does not show service template content' do + expect(page).not_to have_content('Service template allows you to set default values for integrations') end end - context 'with instance-level integration' do - let_it_be(:slack_instance_integration) { create(:slack_service, instance: true, project: nil) } + context 'with an active service template' do + before do + create(:slack_service, :template, active: true) + visit(admin_application_settings_services_path) + end + + it 'shows service template content' do + expect(page).to have_content('Service template allows you to set default values for integrations') + 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 + before do + create(:slack_service, instance: true, project: nil) + visit(admin_application_settings_services_path) + end - 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)) + 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 end diff --git a/spec/features/alerts_settings/user_views_alerts_settings_spec.rb b/spec/features/alerts_settings/user_views_alerts_settings_spec.rb index 60f2f776595..6675abd6b42 100644 --- a/spec/features/alerts_settings/user_views_alerts_settings_spec.rb +++ b/spec/features/alerts_settings/user_views_alerts_settings_spec.rb @@ -25,7 +25,7 @@ RSpec.describe 'Alert integrations settings form', :js do it 'shows the alerts setting form title' do page.within('#js-alert-management-settings') do - expect(find('h4')).to have_content('Alerts') + expect(find('h4')).to have_content('Alert integrations') end end diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb deleted file mode 100644 index 8d0fa3e023b..00000000000 --- a/spec/features/boards/add_issues_modal_spec.rb +++ /dev/null @@ -1,270 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Issue Boards add issue modal', :js do - let(:project) { create(:project, :public) } - let(:board) { create(:board, project: project) } - let(:user) { create(:user) } - let!(:planning) { create(:label, project: project, name: 'Planning') } - let!(:label) { create(:label, project: project) } - let!(:list1) { create(:list, board: board, label: planning, position: 0) } - let!(:list2) { create(:list, board: board, label: label, position: 1) } - let!(:issue) { create(:issue, project: project, title: 'abc', description: 'def') } - let!(:issue2) { create(:issue, project: project, title: 'hij', description: 'klm') } - - before do - project.add_maintainer(user) - - sign_in(user) - - visit project_board_path(project, board) - wait_for_requests - end - - it 'resets filtered search state' do - visit project_board_path(project, board, search: 'testing') - - wait_for_requests - - click_button('Add issues') - - page.within('.add-issues-modal') do - expect(find('.form-control').value).to eq('') - expect(page).to have_selector('.clear-search', visible: false) - expect(find('.form-control')[:placeholder]).to eq('Search or filter results...') - end - end - - context 'modal interaction' do - before do - stub_feature_flags(add_issues_button: true) - end - - it 'opens modal' do - click_button('Add issues') - - expect(page).to have_selector('.add-issues-modal') - end - - it 'closes modal' do - click_button('Add issues') - - page.within('.add-issues-modal') do - find('.close').click - end - - expect(page).not_to have_selector('.add-issues-modal') - end - - it 'closes modal if cancel button clicked' do - click_button('Add issues') - - page.within('.add-issues-modal') do - click_button 'Cancel' - end - - expect(page).not_to have_selector('.add-issues-modal') - end - - it 'does not show tooltip on add issues button' do - button = page.find('.filter-dropdown-container button', text: 'Add issues') - - expect(button[:title]).not_to eq("Please add a list to your board first") - end - end - - context 'issues list' do - before do - stub_feature_flags(add_issues_button: true) - click_button('Add issues') - - wait_for_requests - end - - it 'loads issues' do - page.within('.add-issues-modal') do - page.within('.gl-tabs') do - expect(page).to have_content('2') - end - - expect(page).to have_selector('.board-card', count: 2) - end - end - - it 'shows selected issues tab and empty state message' do - page.within('.add-issues-modal') do - click_link 'Selected issues' - - expect(page).not_to have_selector('.board-card') - expect(page).to have_content("Go back to Open issues and select some issues to add to your board.") - end - end - - context 'list dropdown' do - it 'resets after deleting list' do - page.within('.add-issues-modal') do - expect(find('.add-issues-footer')).to have_button(planning.title) - - click_button 'Cancel' - end - - page.within(find('.board:nth-child(2)')) do - find('button[title="List settings"]').click - end - - page.within(find('.js-board-settings-sidebar')) do - accept_confirm { find('[data-testid="remove-list"]').click } - end - - click_button('Add issues') - - wait_for_requests - - page.within('.add-issues-modal') do - expect(find('.add-issues-footer')).not_to have_button(planning.title) - expect(find('.add-issues-footer')).to have_button(label.title) - end - end - end - - context 'search' do - it 'returns issues' do - page.within('.add-issues-modal') do - find('.form-control').native.send_keys(issue.title) - find('.form-control').native.send_keys(:enter) - - wait_for_requests - - expect(page).to have_selector('.board-card', count: 1) - end - end - - it 'returns no issues' do - page.within('.add-issues-modal') do - find('.form-control').native.send_keys('testing search') - find('.form-control').native.send_keys(:enter) - - wait_for_requests - - expect(page).not_to have_selector('.board-card') - expect(page).not_to have_content("You haven't added any issues to your project yet") - end - end - end - - context 'selecting issues' do - it 'selects single issue' do - page.within('.add-issues-modal') do - first('.board-card .board-card-number').click - - page.within('.gl-tabs') do - expect(page).to have_content('Selected issues 1') - end - end - end - - it 'changes button text' do - page.within('.add-issues-modal') do - first('.board-card .board-card-number').click - - expect(first('.add-issues-footer .btn')).to have_content('Add 1 issue') - end - end - - it 'changes button text with plural' do - page.within('.add-issues-modal') do - all('.board-card .js-board-card-number-container').each do |el| - el.click - end - - expect(first('.add-issues-footer .btn')).to have_content('Add 2 issues') - end - end - - it 'shows only selected issues on selected tab' do - page.within('.add-issues-modal') do - first('.board-card .board-card-number').click - - click_link 'Selected issues' - - expect(page).to have_selector('.board-card', count: 1) - end - end - - it 'selects all issues' do - page.within('.add-issues-modal') do - click_button 'Select all' - - expect(page).to have_selector('.is-active', count: 2) - end - end - - it 'deselects all issues' do - page.within('.add-issues-modal') do - click_button 'Select all' - - expect(page).to have_selector('.is-active', count: 2) - - click_button 'Deselect all' - - expect(page).not_to have_selector('.is-active') - end - end - - it "selects all that aren't already selected" do - page.within('.add-issues-modal') do - first('.board-card .board-card-number').click - - expect(page).to have_selector('.is-active', count: 1) - - click_button 'Select all' - - expect(page).to have_selector('.is-active', count: 2) - end - end - - it 'unselects from selected tab' do - page.within('.add-issues-modal') do - first('.board-card .board-card-number').click - - click_link 'Selected issues' - - first('.board-card .board-card-number').click - - expect(page).not_to have_selector('.is-active') - end - end - end - - context 'adding issues' do - it 'adds to board' do - page.within('.add-issues-modal') do - first('.board-card .board-card-number').click - - click_button 'Add 1 issue' - end - - page.within(find('.board:nth-child(2)')) do - expect(page).to have_selector('.board-card') - end - end - - it 'adds to second list' do - page.within('.add-issues-modal') do - first('.board-card .board-card-number').click - - click_button planning.title - - click_link label.title - - click_button 'Add 1 issue' - end - - page.within(find('.board:nth-child(3)')) do - expect(page).to have_selector('.board-card') - end - end - end - end -end diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 2392f9d2f8a..ab544022bff 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Issue Boards', :js do +RSpec.describe 'Project issue boards', :js do include DragTo include MobileHelpers @@ -23,7 +23,7 @@ RSpec.describe 'Issue Boards', :js do context 'no lists' do before do - visit project_board_path(project, board) + visit_project_board_path_without_query_limit(project, board) end it 'creates default lists' do @@ -52,6 +52,7 @@ RSpec.describe 'Issue Boards', :js do let_it_be(:a_plus) { create(:label, project: project, name: 'A+') } let_it_be(:list1) { create(:list, board: board, label: planning, position: 0) } let_it_be(:list2) { create(:list, board: board, label: development, position: 1) } + let_it_be(:backlog_list) { create(:backlog_list, board: board) } let_it_be(:confidential_issue) { create(:labeled_issue, :confidential, project: project, author: user, labels: [planning], relative_position: 9) } let_it_be(:issue1) { create(:labeled_issue, project: project, title: 'aaa', description: '111', assignees: [user], labels: [planning], relative_position: 8) } @@ -68,7 +69,7 @@ RSpec.describe 'Issue Boards', :js do before do stub_feature_flags(board_new_list: false) - visit project_board_path(project, board) + visit_project_board_path_without_query_limit(project, board) wait_for_requests @@ -121,7 +122,8 @@ RSpec.describe 'Issue Boards', :js do context 'with the NOT queries feature flag disabled' do before do stub_feature_flags(not_issuable_queries: false) - visit project_board_path(project, board) + + visit_project_board_path_without_query_limit(project, board) end it 'does not have the != option' do @@ -141,7 +143,8 @@ RSpec.describe 'Issue Boards', :js do context 'with the NOT queries feature flag enabled' do before do stub_feature_flags(not_issuable_queries: true) - visit project_board_path(project, board) + + visit_project_board_path_without_query_limit(project, board) end it 'does not have the != option' do @@ -171,8 +174,7 @@ RSpec.describe 'Issue Boards', :js do it 'infinite scrolls list' do create_list(:labeled_issue, 50, project: project, labels: [planning]) - visit project_board_path(project, board) - wait_for_requests + visit_project_board_path_without_query_limit(project, board) page.within(find('.board:nth-child(2)')) do expect(page.find('.board-header')).to have_content('58') @@ -180,15 +182,19 @@ RSpec.describe 'Issue Boards', :js do expect(page).to have_content('Showing 20 of 58 issues') find('.board .board-list') - evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight") - wait_for_requests + + inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do + evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight") + end expect(page).to have_selector('.board-card', count: 40) expect(page).to have_content('Showing 40 of 58 issues') find('.board .board-list') - evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight") - wait_for_requests + + inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do + evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight") + end expect(page).to have_selector('.board-card', count: 58) expect(page).to have_content('Showing all issues') @@ -236,13 +242,13 @@ RSpec.describe 'Issue Boards', :js do wait_for_board_cards(4, 1) expect(find('.board:nth-child(2)')).to have_content(development.title) - expect(find('.board:nth-child(2)')).to have_content(planning.title) + expect(find('.board:nth-child(3)')).to have_content(planning.title) # Make sure list positions are preserved after a reload - visit project_board_path(project, board) + visit_project_board_path_without_query_limit(project, board) expect(find('.board:nth-child(2)')).to have_content(development.title) - expect(find('.board:nth-child(2)')).to have_content(planning.title) + expect(find('.board:nth-child(3)')).to have_content(planning.title) end it 'dragging does not duplicate list' do @@ -254,7 +260,8 @@ RSpec.describe 'Issue Boards', :js do expect(page).to have_selector(selector, text: development.title, count: 1) end - it 'issue moves between lists and does not show the "Development" label since the card is in the "Development" list label' do + # TODO https://gitlab.com/gitlab-org/gitlab/-/issues/323551 + xit 'issue moves between lists and does not show the "Development" label since the card is in the "Development" list label' do drag(list_from_index: 1, from_index: 1, list_to_index: 2) wait_for_board_cards(2, 7) @@ -467,14 +474,16 @@ RSpec.describe 'Issue Boards', :js do end it 'removes filtered labels' do - set_filter("label", testing.title) - click_filter_link(testing.title) - submit_filter + inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do + set_filter("label", testing.title) + click_filter_link(testing.title) + submit_filter - wait_for_board_cards(2, 1) + wait_for_board_cards(2, 1) - find('.clear-search').click - submit_filter + find('.clear-search').click + submit_filter + end wait_for_board_cards(2, 8) end @@ -484,7 +493,9 @@ RSpec.describe 'Issue Boards', :js do set_filter("label", testing.title) click_filter_link(testing.title) - submit_filter + inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do + submit_filter + end wait_for_requests @@ -494,13 +505,18 @@ RSpec.describe 'Issue Boards', :js do expect(page).to have_content('Showing 20 of 51 issues') find('.board .board-list') - evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight") + + inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do + evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight") + end expect(page).to have_selector('.board-card', count: 40) expect(page).to have_content('Showing 40 of 51 issues') find('.board .board-list') - evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight") + inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do + evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight") + end expect(page).to have_selector('.board-card', count: 51) expect(page).to have_content('Showing all issues') @@ -569,7 +585,7 @@ RSpec.describe 'Issue Boards', :js do context 'keyboard shortcuts' do before do - visit project_board_path(project, board) + visit_project_board_path_without_query_limit(project, board) wait_for_requests end @@ -617,15 +633,19 @@ RSpec.describe 'Issue Boards', :js do def drag(selector: '.board-list', list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0, perform_drop: true) # ensure there is enough horizontal space for four boards - resize_window(2000, 800) - - drag_to(selector: selector, - scrollable: '#board-app', - list_from_index: list_from_index, - from_index: from_index, - to_index: to_index, - list_to_index: list_to_index, - perform_drop: perform_drop) + inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do + resize_window(2000, 800) + + drag_to(selector: selector, + scrollable: '#board-app', + list_from_index: list_from_index, + from_index: from_index, + to_index: to_index, + list_to_index: list_to_index, + perform_drop: perform_drop) + end + + wait_for_requests end def wait_for_board_cards(board_number, expected_cards) @@ -666,4 +686,10 @@ RSpec.describe 'Issue Boards', :js do accept_confirm { find('[data-testid="remove-list"]').click } end end + + def visit_project_board_path_without_query_limit(project, board) + inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do + visit project_board_path(project, board) + end + end end diff --git a/spec/features/boards/focus_mode_spec.rb b/spec/features/boards/focus_mode_spec.rb index b1684ad69a6..2bd1e625236 100644 --- a/spec/features/boards/focus_mode_spec.rb +++ b/spec/features/boards/focus_mode_spec.rb @@ -11,7 +11,7 @@ RSpec.describe 'Issue Boards focus mode', :js do wait_for_requests end - it 'shows focus mode button to guest users' do - expect(page).to have_selector('.board-extra-actions .js-focus-mode-btn') + it 'shows focus mode button to anonymous users' do + expect(page).to have_selector('.js-focus-mode-btn') end end diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb deleted file mode 100644 index 5aeb9eb5e50..00000000000 --- a/spec/features/boards/modal_filter_spec.rb +++ /dev/null @@ -1,228 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Issue Boards add issue modal filtering', :js do - let(:project) { create(:project, :public) } - let(:board) { create(:board, project: project) } - let(:planning) { create(:label, project: project, name: 'Planning') } - let!(:list1) { create(:list, board: board, label: planning, position: 0) } - let(:user) { create(:user) } - let(:user2) { create(:user) } - let!(:issue1) { create(:issue, project: project) } - - before do - project.add_maintainer(user) - - sign_in(user) - end - - it 'shows empty state when no results found' do - visit_board - - page.within('.add-issues-modal') do - find('.form-control').native.send_keys('testing empty state') - find('.form-control').native.send_keys(:enter) - - wait_for_requests - - expect(page).to have_content('There are no issues to show.') - end - end - - it 'restores filters when closing' do - visit_board - - set_filter('milestone') - click_filter_link('Upcoming') - submit_filter - - page.within('.add-issues-modal') do - wait_for_requests - - expect(page).to have_selector('.board-card', count: 0) - - click_button 'Cancel' - end - - click_button('Add issues') - - page.within('.add-issues-modal') do - wait_for_requests - - expect(page).to have_selector('.board-card', count: 1) - end - end - - it 'resotres filters after clicking clear button' do - visit_board - - set_filter('milestone') - click_filter_link('Upcoming') - submit_filter - - page.within('.add-issues-modal') do - wait_for_requests - - expect(page).to have_selector('.board-card', count: 0) - - find('.clear-search').click - - wait_for_requests - - expect(page).to have_selector('.board-card', count: 1) - end - end - - context 'author' do - let!(:issue) { create(:issue, project: project, author: user2) } - - before do - project.add_developer(user2) - - visit_board - end - - it 'filters by selected user' do - set_filter('author') - click_filter_link(user2.name) - submit_filter - - page.within('.add-issues-modal') do - wait_for_requests - - expect(page).to have_selector('.js-visual-token', text: user2.name) - expect(page).to have_selector('.board-card', count: 1) - end - end - end - - context 'assignee' do - let!(:issue) { create(:issue, project: project, assignees: [user2]) } - - before do - project.add_developer(user2) - - visit_board - end - - it 'filters by unassigned' do - set_filter('assignee') - click_filter_link('None') - submit_filter - - page.within('.add-issues-modal') do - wait_for_requests - - expect(page).to have_selector('.js-visual-token', text: 'None') - expect(page).to have_selector('.board-card', count: 1) - end - end - - it 'filters by selected user' do - set_filter('assignee') - click_filter_link(user2.name) - submit_filter - - page.within('.add-issues-modal') do - wait_for_requests - - expect(page).to have_selector('.js-visual-token', text: user2.name) - expect(page).to have_selector('.board-card', count: 1) - end - end - end - - context 'milestone' do - let(:milestone) { create(:milestone, project: project) } - let!(:issue) { create(:issue, project: project, milestone: milestone) } - - before do - visit_board - end - - it 'filters by upcoming milestone' do - set_filter('milestone') - click_filter_link('Upcoming') - submit_filter - - page.within('.add-issues-modal') do - wait_for_requests - - expect(page).to have_selector('.js-visual-token', text: 'Upcoming') - expect(page).to have_selector('.board-card', count: 0) - end - end - - it 'filters by selected milestone' do - set_filter('milestone') - click_filter_link(milestone.name) - submit_filter - - page.within('.add-issues-modal') do - wait_for_requests - - expect(page).to have_selector('.js-visual-token', text: milestone.name) - expect(page).to have_selector('.board-card', count: 1) - end - end - end - - context 'label' do - let(:label) { create(:label, project: project) } - let!(:issue) { create(:labeled_issue, project: project, labels: [label]) } - - before do - visit_board - end - - it 'filters by no label' do - set_filter('label') - click_filter_link('None') - submit_filter - - page.within('.add-issues-modal') do - wait_for_requests - - expect(page).to have_selector('.js-visual-token', text: 'None') - expect(page).to have_selector('.board-card', count: 1) - end - end - - it 'filters by label' do - set_filter('label') - click_filter_link(label.title) - submit_filter - - page.within('.add-issues-modal') do - wait_for_requests - - expect(page).to have_selector('.js-visual-token', text: label.title) - expect(page).to have_selector('.board-card', count: 1) - end - end - end - - def visit_board - visit project_board_path(project, board) - wait_for_requests - - click_button('Add issues') - end - - def set_filter(type, text = '') - find('.add-issues-modal .filtered-search').native.send_keys("#{type}:=#{text}") - end - - def submit_filter - find('.add-issues-modal .filtered-search').native.send_keys(:enter) - end - - def click_filter_link(link_text) - page.within('.add-issues-modal .filtered-search-box') do - expect(page).to have_button(link_text) - - click_button(link_text) - end - end -end diff --git a/spec/features/boards/multi_select_spec.rb b/spec/features/boards/multi_select_spec.rb index 162455f75e6..ca322355b8f 100644 --- a/spec/features/boards/multi_select_spec.rb +++ b/spec/features/boards/multi_select_spec.rb @@ -41,6 +41,10 @@ RSpec.describe 'Multi Select Issue', :js do before do project.add_maintainer(user) + # multi-drag disabled with feature flag for now + # https://gitlab.com/gitlab-org/gitlab/-/issues/289797 + stub_feature_flags(graphql_board_lists: false) + sign_in(user) end diff --git a/spec/features/boards/multiple_boards_spec.rb b/spec/features/boards/multiple_boards_spec.rb index 2894d5c7666..219f24f60d7 100644 --- a/spec/features/boards/multiple_boards_spec.rb +++ b/spec/features/boards/multiple_boards_spec.rb @@ -8,6 +8,7 @@ RSpec.describe 'Multiple Issue Boards', :js do let_it_be(:planning) { create(:label, project: project, name: 'Planning') } let_it_be(:board) { create(:board, name: 'board1', project: project) } let_it_be(:board2) { create(:board, name: 'board2', project: project) } + let(:parent) { project } let(:boards_path) { project_boards_path(project) } diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index f434ea0c66f..20ae569322c 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -3,10 +3,12 @@ require 'spec_helper' RSpec.describe 'Issue Boards new issue', :js do - let(:project) { create(:project, :public) } - let(:board) { create(:board, project: project) } - let!(:list) { create(:list, board: board, position: 0) } - let(:user) { create(:user) } + let_it_be(:project) { create(:project, :public) } + let_it_be(:board) { create(:board, project: project) } + let_it_be(:backlog_list) { create(:backlog_list, board: board) } + let_it_be(:label) { create(:label, project: project, name: 'Label 1') } + let_it_be(:list) { create(:list, board: board, label: label, position: 0) } + let_it_be(:user) { create(:user) } context 'authorized user' do before do @@ -15,6 +17,7 @@ RSpec.describe 'Issue Boards new issue', :js do sign_in(user) visit project_board_path(project, board) + wait_for_requests expect(page).to have_selector('.board', count: 3) @@ -57,7 +60,7 @@ RSpec.describe 'Issue Boards new issue', :js do page.within(first('.board-new-issue-form')) do find('.form-control').set('bug') - click_button 'Submit issue' + click_button 'Create issue' end wait_for_requests @@ -70,23 +73,24 @@ RSpec.describe 'Issue Boards new issue', :js do issue = project.issues.find_by_title('bug') expect(page).to have_content(issue.to_reference) - expect(page).to have_link(issue.title, href: issue_path(issue)) + expect(page).to have_link(issue.title, href: /#{issue_path(issue)}/) end end - it 'shows sidebar when creating new issue' do + # TODO https://gitlab.com/gitlab-org/gitlab/-/issues/323446 + xit 'shows sidebar when creating new issue' do page.within(first('.board')) do find('.issue-count-badge-add-button').click end page.within(first('.board-new-issue-form')) do find('.form-control').set('bug') - click_button 'Submit issue' + click_button 'Create issue' end wait_for_requests - expect(page).to have_selector('.issue-boards-sidebar') + expect(page).to have_selector('[data-testid="issue-boards-sidebar"]') end it 'successfuly loads labels to be added to newly created issue' do @@ -96,17 +100,21 @@ RSpec.describe 'Issue Boards new issue', :js do page.within(first('.board-new-issue-form')) do find('.form-control').set('new issue') - click_button 'Submit issue' + click_button 'Create issue' end wait_for_requests - page.within(first('.issue-boards-sidebar')) do - find('.labels .edit-link').click + page.within(first('.board')) do + find('.board-card').click + end + + page.within(first('[data-testid="issue-boards-sidebar"]')) do + find('.labels [data-testid="edit-button"]').click wait_for_requests - expect(page).to have_selector('.labels .dropdown-content li a') + expect(page).to have_selector('.labels-select-contents-list .dropdown-content li a') end end end diff --git a/spec/features/boards/reload_boards_on_browser_back_spec.rb b/spec/features/boards/reload_boards_on_browser_back_spec.rb index 181cbcc9811..36682036d48 100644 --- a/spec/features/boards/reload_boards_on_browser_back_spec.rb +++ b/spec/features/boards/reload_boards_on_browser_back_spec.rb @@ -27,7 +27,7 @@ RSpec.describe 'Ensure Boards do not show stale data on browser back', :js do fill_in 'issue_title', with: 'issue should be shown' - click_button 'Submit issue' + click_button 'Create issue' page.go_back wait_for_requests @@ -43,7 +43,7 @@ RSpec.describe 'Ensure Boards do not show stale data on browser back', :js do issue = project.issues.find_by_title('issue should be shown') expect(page).to have_content(issue.to_reference) - expect(page).to have_link(issue.title, href: issue_path(issue)) + expect(page).to have_link(issue.title, href: /#{issue_path(issue)}/) end end end diff --git a/spec/features/boards/sidebar_assignee_spec.rb b/spec/features/boards/sidebar_assignee_spec.rb new file mode 100644 index 00000000000..e938612163f --- /dev/null +++ b/spec/features/boards/sidebar_assignee_spec.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Project issue boards sidebar assignee', :js do + include BoardHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :public) } + let_it_be(:development) { create(:label, project: project, name: 'Development') } + let_it_be(:regression) { create(:label, project: project, name: 'Regression') } + let_it_be(:stretch) { create(:label, project: project, name: 'Stretch') } + + let!(:issue1) { create(:labeled_issue, project: project, assignees: [user], labels: [development], relative_position: 2) } + let!(:issue2) { create(:labeled_issue, project: project, labels: [development, stretch], relative_position: 1) } + let(:board) { create(:board, project: project) } + let!(:list) { create(:list, board: board, label: development, position: 0) } + let(:card) { find('.board:nth-child(2)').first('.board-card') } + + before do + project.add_maintainer(user) + + sign_in(user) + + visit project_board_path(project, board) + wait_for_requests + end + + context 'assignee' do + it 'updates the issues assignee' do + click_card(card) + + page.within('.assignee') do + click_button('Edit') + + wait_for_requests + + assignee = first('.gl-avatar-labeled').find('.gl-avatar-labeled-label').text + + page.within('.dropdown-menu-user') do + first('.gl-avatar-labeled').click + end + + click_button('Apply') + wait_for_requests + + expect(page).to have_content(assignee) + end + + expect(card).to have_selector('.avatar') + end + + it 'removes the assignee' do + card_two = find('.board:nth-child(2)').find('.board-card:nth-child(2)') + click_card(card_two) + + page.within('.assignee') do + click_button('Edit') + + wait_for_requests + + page.within('.dropdown-menu-user') do + find('[data-testid="unassign"]').click + end + + click_button('Apply') + wait_for_requests + + expect(page).to have_content('None') + end + + expect(card_two).not_to have_selector('.avatar') + end + + it 'assignees to current user' do + click_card(card) + + page.within(find('.assignee')) do + expect(page).to have_content('None') + + click_button 'assign yourself' + + wait_for_requests + + expect(page).to have_content(user.name) + end + + expect(card).to have_selector('.avatar') + end + + it 'updates assignee dropdown' do + click_card(card) + + page.within('.assignee') do + click_button('Edit') + + wait_for_requests + + assignee = first('.gl-avatar-labeled').find('.gl-avatar-labeled-label').text + + page.within('.dropdown-menu-user') do + first('.gl-avatar-labeled').click + end + + click_button('Apply') + wait_for_requests + + expect(page).to have_content(assignee) + end + + page.within(find('.board:nth-child(2)')) do + find('.board-card:nth-child(2)').click + end + + page.within('.assignee') do + click_button('Edit') + + expect(find('.dropdown-menu')).to have_selector('.gl-new-dropdown-item-check-icon') + end + end + end +end diff --git a/spec/features/boards/sidebar_due_date_spec.rb b/spec/features/boards/sidebar_due_date_spec.rb new file mode 100644 index 00000000000..141c574ffec --- /dev/null +++ b/spec/features/boards/sidebar_due_date_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Project issue boards sidebar due date', :js do + include BoardHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :public) } + let_it_be(:issue) { create(:issue, project: project, relative_position: 1) } + let_it_be(:board) { create(:board, project: project) } + let_it_be(:list) { create(:list, board: board, position: 0) } + + let(:card) { find('.board:nth-child(1)').first('.board-card') } + + around do |example| + freeze_time { example.run } + end + + before do + project.add_maintainer(user) + + sign_in(user) + + visit project_board_path(project, board) + wait_for_requests + end + + context 'due date' do + it 'updates due date' do + click_card(card) + + page.within('[data-testid="sidebar-due-date"]') do + today = Date.today.day + + click_button 'Edit' + + click_button today.to_s + + wait_for_requests + + expect(page).to have_content(today.to_s(:medium)) + end + end + end +end diff --git a/spec/features/boards/sidebar_labels_spec.rb b/spec/features/boards/sidebar_labels_spec.rb new file mode 100644 index 00000000000..2f0230c61d8 --- /dev/null +++ b/spec/features/boards/sidebar_labels_spec.rb @@ -0,0 +1,166 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Project issue boards sidebar labels', :js do + include BoardHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :public) } + let_it_be(:development) { create(:label, project: project, name: 'Development') } + let_it_be(:bug) { create(:label, project: project, name: 'Bug') } + let_it_be(:regression) { create(:label, project: project, name: 'Regression') } + let_it_be(:stretch) { create(:label, project: project, name: 'Stretch') } + let_it_be(:issue1) { create(:labeled_issue, project: project, labels: [development], relative_position: 2) } + let_it_be(:issue2) { create(:labeled_issue, project: project, labels: [development, stretch], relative_position: 1) } + let_it_be(:board) { create(:board, project: project) } + let_it_be(:list) { create(:list, board: board, label: development, position: 0) } + + let(:card) { find('.board:nth-child(2)').first('.board-card') } + + before do + project.add_maintainer(user) + + sign_in(user) + + visit project_board_path(project, board) + wait_for_requests + end + + context 'labels' do + # https://gitlab.com/gitlab-org/gitlab/-/issues/322725 + xit 'shows current labels when editing' do + click_card(card) + + page.within('.labels') do + click_link 'Edit' + + wait_for_requests + + page.within('.value') do + expect(page).to have_selector('.gl-label-text', count: 2) + expect(page).to have_content(development.title) + expect(page).to have_content(stretch.title) + end + end + end + + it 'adds a single label' do + click_card(card) + + page.within('.labels') do + click_button 'Edit' + + wait_for_requests + + click_link bug.title + + find('[data-testid="close-icon"]').click + + wait_for_requests + + page.within('.value') do + expect(page).to have_selector('.gl-label-text', count: 3) + expect(page).to have_content(bug.title) + end + end + + # 'Development' label does not show since the card is in a 'Development' list label + expect(card).to have_selector('.gl-label', count: 2) + expect(card).to have_content(bug.title) + end + + it 'adds a multiple labels' do + click_card(card) + + page.within('.labels') do + click_button 'Edit' + + wait_for_requests + + click_link bug.title + + click_link regression.title + + find('[data-testid="close-icon"]').click + + wait_for_requests + + page.within('.value') do + expect(page).to have_selector('.gl-label-text', count: 4) + expect(page).to have_content(bug.title) + expect(page).to have_content(regression.title) + end + end + + # 'Development' label does not show since the card is in a 'Development' list label + expect(card).to have_selector('.gl-label', count: 3) + expect(card).to have_content(bug.title) + expect(card).to have_content(regression.title) + end + + it 'removes a label' do + click_card(card) + + page.within('.labels') do + click_button 'Edit' + + wait_for_requests + + click_link stretch.title + + find('[data-testid="close-icon"]').click + + wait_for_requests + + page.within('.value') do + expect(page).to have_selector('.gl-label-text', count: 1) + expect(page).not_to have_content(stretch.title) + end + end + + # 'Development' label does not show since the card is in a 'Development' list label + expect(card).to have_selector('.gl-label-text', count: 0) + expect(card).not_to have_content(stretch.title) + end + + # https://gitlab.com/gitlab-org/gitlab/-/issues/324290 + xit 'creates project label' do + click_card(card) + + page.within('.labels') do + click_link 'Edit' + wait_for_requests + + click_link 'Create project label' + fill_in 'new_label_name', with: 'test label' + first('.suggest-colors-dropdown a').click + click_button 'Create' + wait_for_requests + + expect(page).to have_link 'test label' + end + expect(page).to have_selector('.board', count: 3) + end + + # https://gitlab.com/gitlab-org/gitlab/-/issues/324290 + xit 'creates project label and list' do + click_card(card) + + page.within('.labels') do + click_link 'Edit' + wait_for_requests + + click_link 'Create project label' + fill_in 'new_label_name', with: 'test label' + first('.suggest-colors-dropdown a').click + first('.js-add-list').click + click_button 'Create' + wait_for_requests + + expect(page).to have_link 'test label' + end + expect(page).to have_selector('.board', count: 4) + end + end +end diff --git a/spec/features/boards/sidebar_milestones_spec.rb b/spec/features/boards/sidebar_milestones_spec.rb new file mode 100644 index 00000000000..54182781a30 --- /dev/null +++ b/spec/features/boards/sidebar_milestones_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Project issue boards sidebar milestones', :js do + include BoardHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :public) } + let_it_be(:milestone) { create(:milestone, project: project) } + let_it_be(:issue1) { create(:issue, project: project, relative_position: 1) } + let_it_be(:issue2) { create(:issue, project: project, milestone: milestone, relative_position: 2) } + let_it_be(:board) { create(:board, project: project) } + let_it_be(:list) { create(:list, board: board, position: 0) } + + let(:card1) { find('.board:nth-child(1) .board-card:nth-of-type(1)') } + let(:card2) { find('.board:nth-child(1) .board-card:nth-of-type(2)') } + + before do + project.add_maintainer(user) + + sign_in(user) + + visit project_board_path(project, board) + wait_for_requests + end + + context 'milestone' do + it 'adds a milestone' do + click_card(card1) + + page.within('[data-testid="sidebar-milestones"]') do + click_button 'Edit' + + wait_for_requests + + click_button milestone.title + + wait_for_requests + + page.within('.value') do + expect(page).to have_content(milestone.title) + end + end + end + + it 'removes a milestone' do + click_card(card2) + + page.within('[data-testid="sidebar-milestones"]') do + click_button 'Edit' + + wait_for_requests + + click_button "No milestone" + + wait_for_requests + + page.within('.value') do + expect(page).not_to have_content(milestone.title) + end + end + end + end +end diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index c79bf2abff1..977147c3c6b 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -2,412 +2,33 @@ require 'spec_helper' -RSpec.describe 'Issue Boards', :js do +RSpec.describe 'Project issue boards sidebar', :js do include BoardHelpers - include FilteredSearchHelpers - let(:user) { create(:user) } - let(:user2) { create(:user) } - let(:project) { create(:project, :public) } - let!(:milestone) { create(:milestone, project: project) } - let!(:development) { create(:label, project: project, name: 'Development') } - let!(:bug) { create(:label, project: project, name: 'Bug') } - let!(:regression) { create(:label, project: project, name: 'Regression') } - let!(:stretch) { create(:label, project: project, name: 'Stretch') } - let!(:issue1) { create(:labeled_issue, project: project, assignees: [user], milestone: milestone, labels: [development], relative_position: 2) } - let!(:issue2) { create(:labeled_issue, project: project, labels: [development, stretch], relative_position: 1) } - let(:board) { create(:board, project: project) } - let!(:list) { create(:list, board: board, label: development, position: 0) } - let(:card) { find('.board:nth-child(2)').first('.board-card') } + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :public) } + let_it_be(:board) { create(:board, project: project) } + let_it_be(:list) { create(:list, board: board, position: 0) } - let(:application_settings) { {} } - - around do |example| - freeze_time { example.run } - end + let_it_be(:issue, reload: true) { create(:issue, project: project, relative_position: 1) } before do project.add_maintainer(user) sign_in(user) - stub_application_setting(application_settings) - visit project_board_path(project, board) - wait_for_requests - end - - it 'shows sidebar when clicking issue' do - click_card(card) - - expect(page).to have_selector('.issue-boards-sidebar') - end - - it 'closes sidebar when clicking issue' do - click_card(card) - - expect(page).to have_selector('.issue-boards-sidebar') - - click_card(card) - - expect(page).not_to have_selector('.issue-boards-sidebar') - end - - it 'closes sidebar when clicking close button' do - click_card(card) - - expect(page).to have_selector('.issue-boards-sidebar') - - find('.gutter-toggle').click - - expect(page).not_to have_selector('.issue-boards-sidebar') - end - - it 'shows issue details when sidebar is open' do - click_card(card) - - page.within('.issue-boards-sidebar') do - expect(page).to have_content(issue2.title) - expect(page).to have_content(issue2.to_reference) - end - end - - context 'assignee' do - it 'updates the issues assignee' do - click_card(card) - - page.within('.assignee') do - click_button('Edit') - - wait_for_requests - - assignee = first('.gl-avatar-labeled').find('.gl-avatar-labeled-label').text - - page.within('.dropdown-menu-user') do - first('.gl-avatar-labeled').click - end - - click_button('Edit') - wait_for_requests - - expect(page).to have_content(assignee) - end - - expect(card).to have_selector('.avatar') - end - - it 'removes the assignee' do - card_two = find('.board:nth-child(2)').find('.board-card:nth-child(2)') - click_card(card_two) - - page.within('.assignee') do - click_button('Edit') - - wait_for_requests - - page.within('.dropdown-menu-user') do - find('[data-testid="unassign"]').click - end - - click_button('Edit') - wait_for_requests - - expect(page).to have_content('None') - end - - expect(card_two).not_to have_selector('.avatar') - end - - it 'assignees to current user' do - click_card(card) - - page.within(find('.assignee')) do - expect(page).to have_content('None') - - click_button 'assign yourself' - - wait_for_requests - - expect(page).to have_content(user.name) - end - - expect(card).to have_selector('.avatar') - end - - it 'updates assignee dropdown' do - click_card(card) - - page.within('.assignee') do - click_button('Edit') - - wait_for_requests - - assignee = first('.gl-avatar-labeled').find('.gl-avatar-labeled-label').text - - page.within('.dropdown-menu-user') do - first('.gl-avatar-labeled').click - end - - click_button('Edit') - wait_for_requests - - expect(page).to have_content(assignee) - end - - page.within(find('.board:nth-child(2)')) do - find('.board-card:nth-child(2)').click - end - - page.within('.assignee') do - click_button('Edit') - - expect(find('.dropdown-menu')).to have_selector('.gl-new-dropdown-item-check-icon') - end - end - end - - context 'milestone' do - it 'adds a milestone' do - click_card(card) - - page.within('.milestone') do - click_link 'Edit' - - wait_for_requests - - click_link milestone.title - - wait_for_requests - - page.within('.value') do - expect(page).to have_content(milestone.title) - end - end - end - - it 'removes a milestone' do - click_card(card) - page.within('.milestone') do - click_link 'Edit' - - wait_for_requests - - click_link "No milestone" - - wait_for_requests - - page.within('.value') do - expect(page).not_to have_content(milestone.title) - end - end - end - end - - context 'time tracking' do - let(:compare_meter_tooltip) { find('.time-tracking .time-tracking-content .compare-meter')['title'] } - - before do - issue2.timelogs.create(time_spent: 14400, user: user) - issue2.update!(time_estimate: 128800) - - click_card(card) - end - - it 'shows time tracking progress bar' do - expect(compare_meter_tooltip).to eq('Time remaining: 3d 7h 46m') - end - - context 'when time_tracking_limit_to_hours is true' do - let(:application_settings) { { time_tracking_limit_to_hours: true } } - - it 'shows time tracking progress bar' do - expect(compare_meter_tooltip).to eq('Time remaining: 31h 46m') - end - end - end - - context 'due date' do - it 'updates due date' do - click_card(card) - - page.within('.due_date') do - click_link 'Edit' - - click_button Date.today.day - - wait_for_requests - - expect(page).to have_content(Date.today.to_s(:medium)) - end - end + wait_for_requests end - context 'labels' do - it 'shows current labels when editing' do - click_card(card) - - page.within('.labels') do - click_link 'Edit' - - wait_for_requests - - page.within('.value') do - expect(page).to have_selector('.gl-label-text', count: 2) - expect(page).to have_content(development.title) - expect(page).to have_content(stretch.title) - end - end - end - - it 'adds a single label' do - click_card(card) - - page.within('.labels') do - click_link 'Edit' - - wait_for_requests - - click_link bug.title - - wait_for_requests - - find('.dropdown-menu-close-icon').click - - page.within('.value') do - expect(page).to have_selector('.gl-label-text', count: 3) - expect(page).to have_content(bug.title) - end - end - - # 'Development' label does not show since the card is in a 'Development' list label - expect(card).to have_selector('.gl-label', count: 2) - expect(card).to have_content(bug.title) - end - - it 'adds a multiple labels' do - click_card(card) + it_behaves_like 'issue boards sidebar' - page.within('.labels') do - click_link 'Edit' - - wait_for_requests - - click_link bug.title - - wait_for_requests - - click_link regression.title - - wait_for_requests - - find('.dropdown-menu-close-icon').click - - page.within('.value') do - expect(page).to have_selector('.gl-label-text', count: 4) - expect(page).to have_content(bug.title) - expect(page).to have_content(regression.title) - end - end - - # 'Development' label does not show since the card is in a 'Development' list label - expect(card).to have_selector('.gl-label', count: 3) - expect(card).to have_content(bug.title) - expect(card).to have_content(regression.title) - end - - it 'removes a label' do - click_card(card) - - page.within('.labels') do - click_link 'Edit' - - wait_for_requests - - within('.dropdown-menu-labels') do - click_link stretch.title - end - - wait_for_requests - - find('.dropdown-menu-close-icon').click - - page.within('.value') do - expect(page).to have_selector('.gl-label-text', count: 1) - expect(page).not_to have_content(stretch.title) - end - end - - # 'Development' label does not show since the card is in a 'Development' list label - expect(card).to have_selector('.gl-label-text', count: 0) - expect(card).not_to have_content(stretch.title) - end - - it 'creates project label' do - click_card(card) - - page.within('.labels') do - click_link 'Edit' - wait_for_requests - - click_link 'Create project label' - fill_in 'new_label_name', with: 'test label' - first('.suggest-colors-dropdown a').click - click_button 'Create' - wait_for_requests - - expect(page).to have_link 'test label' - end - expect(page).to have_selector('.board', count: 3) - end - - it 'creates project label and list' do - click_card(card) - - page.within('.labels') do - click_link 'Edit' - wait_for_requests - - click_link 'Create project label' - fill_in 'new_label_name', with: 'test label' - first('.suggest-colors-dropdown a').click - first('.js-add-list').click - click_button 'Create' - wait_for_requests - - expect(page).to have_link 'test label' - end - expect(page).to have_selector('.board', count: 4) - end + def first_card + find('.board:nth-child(1)').first("[data-testid='board_card']") end - context 'subscription' do - it 'changes issue subscription' do - click_card(card) - wait_for_requests - - page.within('.subscriptions') do - find('[data-testid="subscription-toggle"] button:not(.is-checked)').click - wait_for_requests - - expect(page).to have_css('[data-testid="subscription-toggle"] button.is-checked') - end - end - - it 'has checked subscription toggle when already subscribed' do - create(:subscription, user: user, project: project, subscribable: issue2, subscribed: true) - visit project_board_path(project, board) - wait_for_requests - - click_card(card) - wait_for_requests - - page.within('.subscriptions') do - find('[data-testid="subscription-toggle"] button.is-checked').click - wait_for_requests - - expect(page).to have_css('[data-testid="subscription-toggle"] button:not(.is-checked)') - end - end + def click_first_issue_card + click_card(first_card) end end diff --git a/spec/features/boards/sub_group_project_spec.rb b/spec/features/boards/sub_group_project_spec.rb index cd3d61726f6..bde5f061a67 100644 --- a/spec/features/boards/sub_group_project_spec.rb +++ b/spec/features/boards/sub_group_project_spec.rb @@ -21,7 +21,8 @@ RSpec.describe 'Sub-group project issue boards', :js do wait_for_requests end - it 'creates new label from sidebar' do + # TODO https://gitlab.com/gitlab-org/gitlab/-/issues/324290 + xit 'creates new label from sidebar' do find('.board-card').click page.within '.labels' do diff --git a/spec/features/boards/user_adds_lists_to_board_spec.rb b/spec/features/boards/user_adds_lists_to_board_spec.rb index b9945207bb2..5128fc4004e 100644 --- a/spec/features/boards/user_adds_lists_to_board_spec.rb +++ b/spec/features/boards/user_adds_lists_to_board_spec.rb @@ -71,10 +71,13 @@ RSpec.describe 'User adds lists', :js do def select_label(board_new_list_enabled, label) if board_new_list_enabled - page.within('.board-add-new-list') do - find('label', text: label.title).click - click_button 'Add' - end + click_button 'Select a label' + + find('label', text: label.title).click + + click_button 'Add to board' + + wait_for_all_requests else page.within('.dropdown-menu-issues-board-new') do click_link label.title diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb index ee156bdcab4..0b73226268d 100644 --- a/spec/features/calendar_spec.rb +++ b/spec/features/calendar_spec.rb @@ -42,7 +42,7 @@ RSpec.describe 'Contributions Calendar', :js do "#{contributions} #{'contribution'.pluralize(contributions)}" end - "#{get_cell_color_selector(contributions)}[title='#{contribution_text}
#{date}']" + "#{get_cell_color_selector(contributions)}[title='#{contribution_text}
#{date}']" end def push_code_contribution @@ -64,7 +64,7 @@ RSpec.describe 'Contributions Calendar', :js do author_id: user.id } - Event.create(note_comment_params) + Event.create!(note_comment_params) end def selected_day_activities(visible: true) diff --git a/spec/features/callouts/service_templates_deprecation_spec.rb b/spec/features/callouts/service_templates_deprecation_spec.rb new file mode 100644 index 00000000000..b6403b54e29 --- /dev/null +++ b/spec/features/callouts/service_templates_deprecation_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Service templates deprecation callout' do + let_it_be(:admin) { create(:admin) } + let_it_be(:non_admin) { create(:user) } + let_it_be(:callout_content) { 'Service templates are deprecated and will be removed in GitLab 14.0.' } + + context 'when a non-admin is logged in' do + before do + sign_in(non_admin) + visit root_dashboard_path + end + + it 'does not display callout' do + expect(page).not_to have_content callout_content + end + end + + context 'when an admin is logged in' do + before do + sign_in(admin) + gitlab_enable_admin_mode_sign_in(admin) + + visit root_dashboard_path + end + + context 'with no active service templates' do + it 'does not display callout' do + expect(page).not_to have_content callout_content + end + end + + context 'with active service template' do + before do + create(:service, :template, type: 'MattermostService', active: true) + visit root_dashboard_path + end + + it 'displays callout' do + expect(page).to have_content callout_content + expect(page).to have_link 'See affected service templates', href: admin_application_settings_services_path + end + + context 'when callout is dismissed', :js do + before do + find('[data-testid="close-service-templates-deprecated-callout"]').click + + visit root_dashboard_path + end + + it 'does not display callout' do + expect(page).not_to have_content callout_content + end + end + end + end +end diff --git a/spec/features/clusters/cluster_detail_page_spec.rb b/spec/features/clusters/cluster_detail_page_spec.rb index 6fe6c099d80..84a18a45d35 100644 --- a/spec/features/clusters/cluster_detail_page_spec.rb +++ b/spec/features/clusters/cluster_detail_page_spec.rb @@ -23,7 +23,7 @@ RSpec.describe 'Clusterable > Show page' do it 'allow the user to set domain', :js do visit cluster_path - within '.js-cluster-integration-form' do + within '.js-cluster-details-form' do fill_in('cluster_base_domain', with: 'test.com') click_on 'Save changes' end @@ -39,7 +39,7 @@ RSpec.describe 'Clusterable > Show page' do end it 'shows help text with the domain as an alternative to custom domain', :js do - within '.js-cluster-integration-form' do + within '.js-cluster-details-form' do expect(find(cluster_ingress_help_text_selector).text).to include('192.168.1.100') end end @@ -49,7 +49,7 @@ RSpec.describe 'Clusterable > Show page' do it 'alternative to custom domain is not shown' do visit cluster_path - within '.js-cluster-integration-form' do + within '.js-cluster-details-form' do expect(page).not_to have_selector(cluster_ingress_help_text_selector) end end diff --git a/spec/features/clusters/cluster_health_dashboard_spec.rb b/spec/features/clusters/cluster_health_dashboard_spec.rb index e9e3b48e9c0..862f34768c4 100644 --- a/spec/features/clusters/cluster_health_dashboard_spec.rb +++ b/spec/features/clusters/cluster_health_dashboard_spec.rb @@ -33,7 +33,7 @@ RSpec.describe 'Cluster Health board', :js, :kubeclient, :use_clean_rails_memory click_link 'Health' - expect(page).to have_text('you must first install Prometheus in the Applications tab') + expect(page).to have_text('you must first enable Prometheus in the Integrations tab') end end diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index 1622979812d..2dafaedd262 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -140,7 +140,7 @@ RSpec.describe 'Commits' do context 'when accessing internal project with disallowed access', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/299575' do before do - project.update( + project.update!( visibility_level: Gitlab::VisibilityLevel::INTERNAL, public_builds: false) create(:ci_job_artifact, :archive, file: artifacts_file, job: build) diff --git a/spec/features/dashboard/active_tab_spec.rb b/spec/features/dashboard/active_tab_spec.rb index ee85c136190..3a532cb4161 100644 --- a/spec/features/dashboard/active_tab_spec.rb +++ b/spec/features/dashboard/active_tab_spec.rb @@ -4,6 +4,8 @@ require 'spec_helper' RSpec.describe 'Dashboard Active Tab', :js do before do + stub_feature_flags(combined_menu: false) + sign_in(create(:user)) end diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb index c14a6001a3e..442b8904974 100644 --- a/spec/features/dashboard/datetime_on_tooltips_spec.rb +++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb @@ -13,7 +13,7 @@ RSpec.describe 'Tooltips on .timeago dates', :js do context 'on the activity tab' do before do - Event.create( project: project, author_id: user.id, action: :joined, + Event.create!( project: project, author_id: user.id, action: :joined, updated_at: created_date, created_at: created_date) sign_in user diff --git a/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb b/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb index c2a3b90b6f4..179d9d09905 100644 --- a/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb +++ b/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb @@ -8,6 +8,8 @@ RSpec.describe 'The group dashboard' do let(:user) { create(:user) } before do + stub_feature_flags(combined_menu: false) + sign_in user end diff --git a/spec/features/dashboard/issuables_counter_spec.rb b/spec/features/dashboard/issuables_counter_spec.rb index 3cb7140d253..d4c6b6faa79 100644 --- a/spec/features/dashboard/issuables_counter_spec.rb +++ b/spec/features/dashboard/issuables_counter_spec.rb @@ -10,7 +10,7 @@ RSpec.describe 'Navigation bar counter', :use_clean_rails_memory_store_caching d before do issue.assignees = [user] - merge_request.update(assignees: [user]) + merge_request.update!(assignees: [user]) sign_in(user) end @@ -35,7 +35,7 @@ RSpec.describe 'Navigation bar counter', :use_clean_rails_memory_store_caching d expect_counters('merge_requests', '1') - merge_request.update(assignees: []) + merge_request.update!(assignees: []) user.invalidate_cache_counts diff --git a/spec/features/dashboard/milestones_spec.rb b/spec/features/dashboard/milestones_spec.rb index 308432b7a1b..992ed2f2ce6 100644 --- a/spec/features/dashboard/milestones_spec.rb +++ b/spec/features/dashboard/milestones_spec.rb @@ -30,7 +30,7 @@ RSpec.describe 'Dashboard > Milestones' do expect(current_path).to eq dashboard_milestones_path expect(page).to have_content(milestone.title) expect(page).to have_content(group.name) - expect(first('.milestone')).to have_content('Merge Requests') + expect(first('.milestone')).to have_content('Merge requests') end describe 'new milestones dropdown', :js do diff --git a/spec/features/dashboard/project_member_activity_index_spec.rb b/spec/features/dashboard/project_member_activity_index_spec.rb index 6e6e466294f..c26a1a0b486 100644 --- a/spec/features/dashboard/project_member_activity_index_spec.rb +++ b/spec/features/dashboard/project_member_activity_index_spec.rb @@ -11,7 +11,7 @@ RSpec.describe 'Project member activity', :js do end def visit_activities_and_wait_with_event(event_type) - Event.create(project: project, author_id: user.id, action: event_type) + Event.create!(project: project, author_id: user.id, action: event_type) visit activity_project_path(project) wait_for_requests end diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index d7330b5267b..20c753b1cdb 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -3,9 +3,9 @@ require 'spec_helper' RSpec.describe 'Dashboard Projects' do - let(:user) { create(:user) } - let(:project) { create(:project, :repository, name: 'awesome stuff') } - let(:project2) { create(:project, :public, name: 'Community project') } + let_it_be(:user) { create(:user) } + let_it_be(:project, reload: true) { create(:project, :repository) } + let_it_be(:project2) { create(:project, :public) } before do project.add_developer(user) @@ -18,17 +18,10 @@ RSpec.describe 'Dashboard Projects' do end end - it 'shows the project the user in a member of in the list' do - visit dashboard_projects_path - expect(page).to have_content('awesome stuff') - end - - it 'shows "New project" button' do + it 'shows the customize banner', :js do visit dashboard_projects_path - page.within '#content-body' do - expect(page).to have_link('New project') - end + expect(page).to have_content('Do you want to customize this page?') end context 'when user has access to the project' do @@ -48,7 +41,7 @@ RSpec.describe 'Dashboard Projects' do expect(page).to have_content('Developer') end - project.members.last.update(access_level: 40) + project.members.last.update!(access_level: 40) visit dashboard_projects_path @@ -153,7 +146,7 @@ RSpec.describe 'Dashboard Projects' do end describe 'with a pipeline', :clean_gitlab_redis_shared_state do - let(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.sha, ref: project.default_branch) } + let_it_be(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.sha, ref: project.default_branch) } before do # Since the cache isn't updated when a new pipeline is created @@ -190,7 +183,7 @@ RSpec.describe 'Dashboard Projects' do let(:guest_user) { create(:user) } before do - project.update(public_builds: false) + project.update!(public_builds: false) project.add_guest(guest_user) sign_in(guest_user) end diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb index b2fda28f0ec..e96a60b2ab2 100644 --- a/spec/features/dashboard/shortcuts_spec.rb +++ b/spec/features/dashboard/shortcuts_spec.rb @@ -3,6 +3,10 @@ require 'spec_helper' RSpec.describe 'Dashboard shortcuts', :js do + before do + stub_feature_flags(combined_menu: false) + end + context 'logged in' do let(:user) { create(:user) } let(:project) { create(:project) } @@ -20,7 +24,7 @@ RSpec.describe 'Dashboard shortcuts', :js do find('body').send_keys([:shift, 'M']) - check_page_title('Merge Requests') + check_page_title('Merge requests') find('body').send_keys([:shift, 'T']) diff --git a/spec/features/discussion_comments/merge_request_spec.rb b/spec/features/discussion_comments/merge_request_spec.rb index f60d7da6a30..a90ff3721d3 100644 --- a/spec/features/discussion_comments/merge_request_spec.rb +++ b/spec/features/discussion_comments/merge_request_spec.rb @@ -8,8 +8,6 @@ RSpec.describe 'Thread Comments Merge Request', :js do let(:merge_request) { create(:merge_request, source_project: project) } before do - stub_feature_flags(remove_resolve_note: false) - project.add_maintainer(user) sign_in(user) diff --git a/spec/features/error_pages_spec.rb b/spec/features/error_pages_spec.rb index 77f8aa87237..8dc9e5ade46 100644 --- a/spec/features/error_pages_spec.rb +++ b/spec/features/error_pages_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Error Pages' do +RSpec.describe 'Error Pages', :js do let(:user) { create(:user) } let(:project) { create(:project, :public) } @@ -14,7 +14,12 @@ RSpec.describe 'Error Pages' do it 'shows nav links' do expect(page).to have_link("Home", href: root_path) expect(page).to have_link("Help", href: help_path) - expect(page).to have_link(nil, href: destroy_user_session_path) + end + + it 'allows user to sign out' do + click_link 'Sign out and sign in with a different account' + + expect(page).to have_current_path(new_user_session_path) end end diff --git a/spec/features/error_tracking/user_filters_errors_by_status_spec.rb b/spec/features/error_tracking/user_filters_errors_by_status_spec.rb index a0d93b791d9..6846d8f6ade 100644 --- a/spec/features/error_tracking/user_filters_errors_by_status_spec.rb +++ b/spec/features/error_tracking/user_filters_errors_by_status_spec.rb @@ -7,6 +7,7 @@ RSpec.describe 'When a user filters Sentry errors by status', :js, :use_clean_ra let_it_be(:issues_response_body) { fixture_file('sentry/issues_sample_response.json') } let_it_be(:filtered_errors_by_status_response) { Gitlab::Json.parse(issues_response_body).filter { |error| error['status'] == 'ignored' }.to_json } + let(:issues_api_url) { "#{sentry_api_urls.issues_url}?limit=20&query=is:unresolved" } let(:issues_api_url_filter) { "#{sentry_api_urls.issues_url}?limit=20&query=is:ignored" } let(:auth_token) {{ 'Authorization' => 'Bearer access_token_123' }} diff --git a/spec/features/error_tracking/user_searches_sentry_errors_spec.rb b/spec/features/error_tracking/user_searches_sentry_errors_spec.rb index 025a6261957..c16c9d3fb1f 100644 --- a/spec/features/error_tracking/user_searches_sentry_errors_spec.rb +++ b/spec/features/error_tracking/user_searches_sentry_errors_spec.rb @@ -7,6 +7,7 @@ RSpec.describe 'When a user searches for Sentry errors', :js, :use_clean_rails_m let_it_be(:issues_response_body) { fixture_file('sentry/issues_sample_response.json') } let_it_be(:error_search_response_body) { fixture_file('sentry/error_list_search_response.json') } + let(:issues_api_url) { "#{sentry_api_urls.issues_url}?limit=20&query=is:unresolved" } let(:issues_api_url_search) { "#{sentry_api_urls.issues_url}?limit=20&query=is:unresolved%20NotFound" } diff --git a/spec/features/error_tracking/user_sees_error_index_spec.rb b/spec/features/error_tracking/user_sees_error_index_spec.rb index a4b15432ef3..bc6709c659d 100644 --- a/spec/features/error_tracking/user_sees_error_index_spec.rb +++ b/spec/features/error_tracking/user_sees_error_index_spec.rb @@ -7,6 +7,7 @@ RSpec.describe 'View error index page', :js, :use_clean_rails_memory_store_cachi let_it_be(:issues_response_body) { fixture_file('sentry/issues_sample_response.json') } let_it_be(:issues_response) { Gitlab::Json.parse(issues_response_body) } + let(:issues_api_url) { "#{sentry_api_urls.issues_url}?limit=20&query=is:unresolved" } before do @@ -41,7 +42,7 @@ RSpec.describe 'View error index page', :js, :use_clean_rails_memory_store_cachi context 'with error tracking settings disabled' do before do - project_error_tracking_settings.update(enabled: false) + project_error_tracking_settings.update!(enabled: false) sign_in(project.owner) visit project_error_tracking_index_path(project) diff --git a/spec/features/file_uploads/attachment_spec.rb b/spec/features/file_uploads/attachment_spec.rb new file mode 100644 index 00000000000..9ad404ce869 --- /dev/null +++ b/spec/features/file_uploads/attachment_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Upload an attachment', :api, :js do + include_context 'file upload requests helpers' + + let_it_be(:project) { create(:project) } + let_it_be(:user) { project.owner } + let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } + + let(:api_path) { "/projects/#{project_id}/uploads" } + let(:url) { capybara_url(api(api_path)) } + let(:file) { fixture_file_upload('spec/fixtures/dk.png') } + + subject do + HTTParty.post( + url, + headers: { 'PRIVATE-TOKEN' => personal_access_token.token }, + body: { file: file } + ) + end + + shared_examples 'for an attachment' do + it 'creates files' do + expect { subject } + .to change { Upload.count }.by(1) + end + + it { expect(subject.code).to eq(201) } + end + + context 'with an integer project ID' do + let(:project_id) { project.id } + + it_behaves_like 'handling file uploads', 'for an attachment' + end + + context 'with an encoded project ID' do + let(:project_id) { "#{project.namespace.path}%2F#{project.path}" } + + it_behaves_like 'handling file uploads', 'for an attachment' + end +end diff --git a/spec/features/file_uploads/maven_package_spec.rb b/spec/features/file_uploads/maven_package_spec.rb index e87eec58618..ab9f023bd8f 100644 --- a/spec/features/file_uploads/maven_package_spec.rb +++ b/spec/features/file_uploads/maven_package_spec.rb @@ -6,16 +6,17 @@ RSpec.describe 'Upload a maven package', :api, :js do include_context 'file upload requests helpers' let_it_be(:project) { create(:project) } - let_it_be(:user) { create(:user, :admin) } + let_it_be(:user) { project.owner } let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } - let(:api_path) { "/projects/#{project.id}/packages/maven/com/example/my-app/1.0/my-app-1.0-20180724.124855-1.jar" } + let(:project_id) { project.id } + let(:api_path) { "/projects/#{project_id}/packages/maven/com/example/my-app/1.0/my-app-1.0-20180724.124855-1.jar" } let(:url) { capybara_url(api(api_path, personal_access_token: personal_access_token)) } let(:file) { fixture_file_upload('spec/fixtures/dk.png') } subject { HTTParty.put(url, body: file.read) } - RSpec.shared_examples 'for a maven package' do + shared_examples 'for a maven package' do it 'creates package files' do expect { subject } .to change { Packages::Package.maven.count }.by(1) @@ -25,9 +26,9 @@ RSpec.describe 'Upload a maven package', :api, :js do it { expect(subject.code).to eq(200) } end - RSpec.shared_examples 'for a maven sha1' do + shared_examples 'for a maven sha1' do let(:dummy_package) { double(Packages::Package) } - let(:api_path) { "/projects/#{project.id}/packages/maven/com/example/my-app/1.0/my-app-1.0-20180724.124855-1.jar.sha1" } + let(:api_path) { "/projects/#{project_id}/packages/maven/com/example/my-app/1.0/my-app-1.0-20180724.124855-1.jar.sha1" } before do # The sha verification done by the maven api is between: @@ -42,8 +43,8 @@ RSpec.describe 'Upload a maven package', :api, :js do it { expect(subject.code).to eq(204) } end - RSpec.shared_examples 'for a maven md5' do - let(:api_path) { "/projects/#{project.id}/packages/maven/com/example/my-app/1.0/my-app-1.0-20180724.124855-1.jar.md5" } + shared_examples 'for a maven md5' do + let(:api_path) { "/projects/#{project_id}/packages/maven/com/example/my-app/1.0/my-app-1.0-20180724.124855-1.jar.md5" } let(:file) { StringIO.new('dummy_package') } it { expect(subject.code).to eq(200) } @@ -52,4 +53,10 @@ RSpec.describe 'Upload a maven package', :api, :js do it_behaves_like 'handling file uploads', 'for a maven package' it_behaves_like 'handling file uploads', 'for a maven sha1' it_behaves_like 'handling file uploads', 'for a maven md5' + + context 'with an encoded project ID' do + let(:project_id) { "#{project.namespace.path}%2F#{project.path}" } + + it_behaves_like 'handling file uploads', 'for a maven package' + end end diff --git a/spec/features/file_uploads/nuget_package_spec.rb b/spec/features/file_uploads/nuget_package_spec.rb index 6e05e5d1a6e..871c0274445 100644 --- a/spec/features/file_uploads/nuget_package_spec.rb +++ b/spec/features/file_uploads/nuget_package_spec.rb @@ -6,7 +6,7 @@ RSpec.describe 'Upload a nuget package', :api, :js do include_context 'file upload requests helpers' let_it_be(:project) { create(:project) } - let_it_be(:user) { create(:user, :admin) } + let_it_be(:user) { project.owner } let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } let(:api_path) { "/projects/#{project.id}/packages/nuget/" } @@ -21,7 +21,7 @@ RSpec.describe 'Upload a nuget package', :api, :js do ) end - RSpec.shared_examples 'for a nuget package' do + shared_examples 'for a nuget package' do it 'creates package files' do expect { subject } .to change { Packages::Package.nuget.count }.by(1) diff --git a/spec/features/file_uploads/rubygem_package_spec.rb b/spec/features/file_uploads/rubygem_package_spec.rb new file mode 100644 index 00000000000..4a5891fdfed --- /dev/null +++ b/spec/features/file_uploads/rubygem_package_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Upload a RubyGems package', :api, :js do + include_context 'file upload requests helpers' + + let_it_be(:project) { create(:project) } + let_it_be(:user) { project.owner } + let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } + + let(:api_path) { "/projects/#{project_id}/packages/rubygems/api/v1/gems" } + let(:url) { capybara_url(api(api_path)) } + let(:file) { fixture_file_upload('spec/fixtures/dk.png') } + + subject do + HTTParty.post( + url, + headers: { 'Authorization' => personal_access_token.token }, + body: { file: file } + ) + end + + shared_examples 'for a Rubygems package' do + it 'creates package files' do + expect { subject } + .to change { Packages::Package.rubygems.count }.by(1) + .and change { Packages::PackageFile.count }.by(1) + end + + it { expect(subject.code).to eq(201) } + end + + context 'with an integer project ID' do + let(:project_id) { project.id } + + it_behaves_like 'handling file uploads', 'for a Rubygems package' + end + + context 'with an encoded project ID' do + let(:project_id) { "#{project.namespace.path}%2F#{project.path}" } + + it_behaves_like 'handling file uploads', 'for a Rubygems package' + end +end diff --git a/spec/features/frequently_visited_projects_and_groups_spec.rb b/spec/features/frequently_visited_projects_and_groups_spec.rb index b8797d9c139..6c25afdf6d4 100644 --- a/spec/features/frequently_visited_projects_and_groups_spec.rb +++ b/spec/features/frequently_visited_projects_and_groups_spec.rb @@ -6,6 +6,8 @@ RSpec.describe 'Frequently visited items', :js do let_it_be(:user) { create(:user) } before do + stub_feature_flags(combined_menu: false) + sign_in(user) end diff --git a/spec/features/gitlab_experiments_spec.rb b/spec/features/gitlab_experiments_spec.rb new file mode 100644 index 00000000000..76b418adcea --- /dev/null +++ b/spec/features/gitlab_experiments_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe "Gitlab::Experiment", :js do + # This is part of a set of tests that ensure that tracking remains + # consistent at the front end layer. Since we don't want to actually + # introduce an experiment in real code, we're going to simulate it + # here. + let(:user) { create(:user) } + + before do + admin = create(:admin) + sign_in(admin) + gitlab_enable_admin_mode_sign_in(admin) + stub_experiments(null_hypothesis: :candidate) + end + + describe 'with event tracking' do + it 'publishes the experiments that have been run to the client', :experiment do + allow_next_instance_of(Admin::AbuseReportsController) do |instance| + allow(instance).to receive(:index).and_wrap_original do |original| + instance.experiment(:null_hypothesis, user: instance.current_user) do |e| + e.use { original.call } + e.try { original.call } + end + end + end + + visit admin_abuse_reports_path + + expect(page).to have_content('Abuse Reports') + + published_experiments = page.evaluate_script('window.gon.experiment') + expect(published_experiments).to include({ + 'null_hypothesis' => { + 'experiment' => 'null_hypothesis', + 'key' => anything, + 'variant' => 'candidate' + } + }) + end + end +end diff --git a/spec/features/groups/board_spec.rb b/spec/features/groups/board_spec.rb index aab3f5e68d5..b4c60ff4fa3 100644 --- a/spec/features/groups/board_spec.rb +++ b/spec/features/groups/board_spec.rb @@ -33,7 +33,7 @@ RSpec.describe 'Group Boards' do find('.gl-new-dropdown-item button').click end - click_button 'Submit issue' + click_button 'Create issue' expect(page).to have_content(issue_title) end diff --git a/spec/features/groups/clusters/user_spec.rb b/spec/features/groups/clusters/user_spec.rb index 97f8864aab2..2a7ededa39b 100644 --- a/spec/features/groups/clusters/user_spec.rb +++ b/spec/features/groups/clusters/user_spec.rb @@ -97,7 +97,7 @@ RSpec.describe 'User Cluster', :js do context 'when user disables the cluster' do before do page.find(:css, '.js-cluster-enable-toggle-area .js-project-feature-toggle').click - page.within('.js-cluster-integration-form') { click_button 'Save changes' } + page.within('.js-cluster-details-form') { click_button 'Save changes' } end it 'user sees the successful message' do diff --git a/spec/features/groups/group_page_with_external_authorization_service_spec.rb b/spec/features/groups/group_page_with_external_authorization_service_spec.rb index 8ef1b60d8ca..187d878472e 100644 --- a/spec/features/groups/group_page_with_external_authorization_service_spec.rb +++ b/spec/features/groups/group_page_with_external_authorization_service_spec.rb @@ -19,7 +19,7 @@ RSpec.describe 'The group page' do expect(page).to have_link('Details') expect(page).to have_link('Activity') expect(page).to have_link('Issues') - expect(page).to have_link('Merge Requests') + expect(page).to have_link('Merge requests') expect(page).to have_link('Members') end end @@ -50,7 +50,7 @@ RSpec.describe 'The group page' do expect(page).not_to have_link('Contribution') expect(page).not_to have_link('Issues') - expect(page).not_to have_link('Merge Requests') + expect(page).not_to have_link('Merge requests') expect(page).to have_link('Members') end end diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb index 60cd1ebbbd7..00ad1006037 100644 --- a/spec/features/groups/group_settings_spec.rb +++ b/spec/features/groups/group_settings_spec.rb @@ -175,7 +175,7 @@ RSpec.describe 'Edit group settings' do end def updated_emails_disabled? - group.reload.clear_memoization(:emails_disabled) + group.reload.clear_memoization(:emails_disabled_memoized) group.emails_disabled? end end diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb index 8ecd2beba68..b0d2f90145f 100644 --- a/spec/features/groups/issues_spec.rb +++ b/spec/features/groups/issues_spec.rb @@ -108,7 +108,7 @@ RSpec.describe 'Group issues page' do it 'shows projects only with issues feature enabled', :js do find('.empty-state .js-lazy-loaded') - find('.new-project-item-link').click + find('.empty-state .new-project-item-link').click page.within('.select2-results') do expect(page).to have_content(project.full_name) diff --git a/spec/features/groups/labels/index_spec.rb b/spec/features/groups/labels/index_spec.rb index 3de29231f5c..68f03368989 100644 --- a/spec/features/groups/labels/index_spec.rb +++ b/spec/features/groups/labels/index_spec.rb @@ -24,6 +24,6 @@ RSpec.describe 'Group labels' do end it 'shows an edit label button', :js do - expect(page).to have_selector('.label-action.edit') + expect(page).to have_selector('.edit') end end diff --git a/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb index 38deee547a3..d31a7977f66 100644 --- a/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb +++ b/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb @@ -8,6 +8,7 @@ RSpec.describe 'Groups > Members > Owner adds member with expiration date', :js let_it_be(:user1) { create(:user, name: 'John Doe') } let_it_be(:group) { create(:group) } + let(:new_member) { create(:user, name: 'Mary Jane') } before do diff --git a/spec/features/groups/members/request_access_spec.rb b/spec/features/groups/members/request_access_spec.rb index 307cb63ec8e..827962fee61 100644 --- a/spec/features/groups/members/request_access_spec.rb +++ b/spec/features/groups/members/request_access_spec.rb @@ -15,7 +15,7 @@ RSpec.describe 'Groups > Members > Request access' do end it 'request access feature is disabled' do - group.update(request_access_enabled: false) + group.update!(request_access_enabled: false) visit group_path(group) expect(page).not_to have_content 'Request Access' diff --git a/spec/features/groups/merge_requests_spec.rb b/spec/features/groups/merge_requests_spec.rb index 43d4b6b23e0..f79c93157dc 100644 --- a/spec/features/groups/merge_requests_spec.rb +++ b/spec/features/groups/merge_requests_spec.rb @@ -27,7 +27,7 @@ RSpec.describe 'Group merge requests page' do end it 'ignores archived merge request count badges in navbar' do - expect(first(:link, text: 'Merge Requests').find('.badge').text).to eq("1") + expect(first(:link, text: 'Merge requests').find('.badge').text).to eq("1") end it 'ignores archived merge request count badges in state-filters' do diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb index 8d1008b98a6..1d9ac5ee1e9 100644 --- a/spec/features/groups/milestone_spec.rb +++ b/spec/features/groups/milestone_spec.rb @@ -133,7 +133,7 @@ RSpec.describe 'Group milestones' do href: project_issues_path(project, milestone_title: 'v1.0') ) expect(page).to have_link( - '0 Merge Requests', + '0 Merge requests', href: project_merge_requests_path(project, milestone_title: 'v1.0') ) expect(page).to have_link( @@ -145,7 +145,7 @@ RSpec.describe 'Group milestones' do href: issues_group_path(group, milestone_title: 'GL-113') ) expect(page).to have_link( - '0 Merge Requests', + '0 Merge requests', href: merge_requests_group_path(group, milestone_title: 'GL-113') ) end @@ -179,7 +179,7 @@ RSpec.describe 'Group milestones' do it 'renders the merge requests tab' do within('.js-milestone-tabs') do - click_link('Merge Requests') + click_link('Merge requests') end within('#tab-merge-requests') do diff --git a/spec/features/groups/navbar_spec.rb b/spec/features/groups/navbar_spec.rb index 7025874a4ff..021b1af54d4 100644 --- a/spec/features/groups/navbar_spec.rb +++ b/spec/features/groups/navbar_spec.rb @@ -30,7 +30,7 @@ RSpec.describe 'Group navbar' do ] }, { - nav_item: _('Merge Requests'), + nav_item: _('Merge requests'), nav_sub_items: [] }, (security_and_compliance_nav_item if Gitlab.ee?), @@ -68,7 +68,7 @@ RSpec.describe 'Group navbar' do before do stub_config(registry: { enabled: true }) - insert_container_nav(_('Kubernetes')) + insert_container_nav visit group_path(group) end @@ -80,7 +80,7 @@ RSpec.describe 'Group navbar' do before do stub_config(dependency_proxy: { enabled: true }) - insert_dependency_proxy_nav(_('Dependency Proxy')) + insert_dependency_proxy_nav visit group_path(group) end diff --git a/spec/features/groups/settings/user_searches_in_settings_spec.rb b/spec/features/groups/settings/user_searches_in_settings_spec.rb index 819d0c4faba..6d7a3871bb1 100644 --- a/spec/features/groups/settings/user_searches_in_settings_spec.rb +++ b/spec/features/groups/settings/user_searches_in_settings_spec.rb @@ -5,7 +5,6 @@ require 'spec_helper' RSpec.describe 'User searches group settings', :js do let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group) } - let_it_be(:project) { create(:project, :repository, namespace: group) } before do group.add_owner(user) @@ -13,9 +12,19 @@ RSpec.describe 'User searches group settings', :js do end context 'in general settings page' do - let(:visit_path) { edit_group_path(group) } + before do + visit edit_group_path(group) + end + + it_behaves_like 'can search settings', 'Naming', 'Permissions' + end + + context 'in Integrations page' do + before do + visit group_settings_integrations_path(group) + end - it_behaves_like 'can search settings with feature flag check', 'Naming', 'Permissions' + it_behaves_like 'can highlight results', 'set default configuration' end context 'in Repository page' do @@ -33,4 +42,12 @@ RSpec.describe 'User searches group settings', :js do it_behaves_like 'can search settings', 'Variables', 'Runners' end + + context 'in Packages & Registries page' do + before do + visit group_settings_packages_and_registries_path(group) + end + + it_behaves_like 'can highlight results', 'GitLab Packages' + end end diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index 28b22860f0a..33d2ac50628 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -141,6 +141,30 @@ RSpec.describe 'Group' do end end end + + describe 'showing recaptcha on group creation when it is enabled' do + before do + stub_application_setting(recaptcha_enabled: true) + allow(Gitlab::Recaptcha).to receive(:load_configurations!) + visit new_group_path + end + + it 'renders recaptcha' do + expect(page).to have_css('.recaptcha') + end + end + + describe 'not showing recaptcha on group creation when it is disabled' do + before do + stub_feature_flags(recaptcha_on_top_level_group_creation: false) + stub_application_setting(recaptcha_enabled: true) + visit new_group_path + end + + it 'does not render recaptcha' do + expect(page).not_to have_css('.recaptcha') + end + end end describe 'create a nested group' do @@ -189,6 +213,46 @@ RSpec.describe 'Group' do expect(page).to have_content("Group 'bar' was successfully created.") end end + + context 'when recaptcha is enabled' do + before do + stub_application_setting(recaptcha_enabled: true) + allow(Gitlab::Recaptcha).to receive(:load_configurations!) + end + + context 'when creating subgroup' do + let(:path) { new_group_path(group, parent_id: group.id) } + + it 'does not render recaptcha' do + visit path + + expect(page).not_to have_css('.recaptcha') + end + end + end + + describe 'real-time group url validation', :js do + let_it_be(:subgroup) { create(:group, path: 'sub', parent: group) } + + before do + group.add_owner(user) + visit new_group_path(parent_id: group.id) + end + + it 'shows a message if group url is available' do + fill_in 'Group URL', with: group.path + 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 URL', with: subgroup.path + wait_for_requests + + expect(page).to have_content('Group path is already taken') + end + end end it 'checks permissions to avoid exposing groups by parent_id' do @@ -203,6 +267,7 @@ RSpec.describe 'Group' do describe 'group edit', :js do let_it_be(:group) { create(:group, :public) } + let(:path) { edit_group_path(group) } let(:new_name) { 'new-name' } @@ -248,6 +313,7 @@ RSpec.describe 'Group' do describe 'group page with markdown description' do let_it_be(:group) { create(:group) } + let(:path) { group_path(group) } before do diff --git a/spec/features/ide/clientside_preview_csp_spec.rb b/spec/features/ide/clientside_preview_csp_spec.rb index eadcb9cd008..559edb8bf53 100644 --- a/spec/features/ide/clientside_preview_csp_spec.rb +++ b/spec/features/ide/clientside_preview_csp_spec.rb @@ -7,9 +7,7 @@ RSpec.describe 'IDE Clientside Preview CSP' do shared_context 'disable feature' do before do - allow_next_instance_of(ApplicationSetting) do |instance| - allow(instance).to receive(:web_ide_clientside_preview_enabled?).and_return(false) - end + stub_application_setting(web_ide_clientside_preview_enabled: false) end end @@ -24,10 +22,8 @@ RSpec.describe 'IDE Clientside Preview CSP' do end before do - allow_next_instance_of(ApplicationSetting) do |instance| - allow(instance).to receive(:web_ide_clientside_preview_enabled?).and_return(true) - allow(instance).to receive(:web_ide_clientside_preview_bundler_url).and_return(whitelisted_url) - end + stub_application_setting(web_ide_clientside_preview_enabled: true) + stub_application_setting(web_ide_clientside_preview_bundler_url: whitelisted_url) sign_in(user) end diff --git a/spec/features/import/manifest_import_spec.rb b/spec/features/import/manifest_import_spec.rb index dfd6211a683..520cf850da2 100644 --- a/spec/features/import/manifest_import_spec.rb +++ b/spec/features/import/manifest_import_spec.rb @@ -37,11 +37,20 @@ RSpec.describe 'Import multiple repositories by uploading a manifest file', :js wait_for_requests page.within(second_row) do - expect(page).to have_content 'Done' + expect(page).to have_content 'Complete' expect(page).to have_content("#{group.full_path}/build/blueprint") end end + it 'renders an error if the remote url scheme starts with javascript' do + visit new_import_manifest_path + + attach_file('manifest', Rails.root.join('spec/fixtures/unsafe_javascript.xml')) + click_on 'List available repositories' + + expect(page).to have_content 'Make sure the url does not start with javascript' + end + it 'renders an error if invalid file was provided' do visit new_import_manifest_path diff --git a/spec/features/invites_spec.rb b/spec/features/invites_spec.rb index 2ceffa896eb..e9960802378 100644 --- a/spec/features/invites_spec.rb +++ b/spec/features/invites_spec.rb @@ -50,21 +50,23 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do end it 'renders sign in page with sign in notice' do - expect(current_path).to eq(new_user_session_path) - expect(page).to have_content('To accept this invitation, sign in') + expect(current_path).to eq(new_user_registration_path) + expect(page).to have_content('To accept this invitation, create an account or sign in') end it 'pre-fills the "Username or email" field on the sign in box with the invite_email from the invite' do + click_link 'Sign in' + expect(find_field('Username or email').value).to eq(group_invite.invite_email) end it 'pre-fills the Email field on the sign up box with the invite_email from the invite' do - click_link 'Register now' - expect(find_field('Email').value).to eq(group_invite.invite_email) end it 'sign in, grants access and redirects to group page' do + click_link 'Sign in' + fill_in_sign_in_form(user) expect(current_path).to eq(group_path(group)) @@ -85,20 +87,19 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do end end - context 'when inviting a user' do + context 'when inviting an unregistered user' 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, created_by: owner) } let!(:project_invite) { create(:project_member, :invited, project: project, invite_email: invite_email) } - context 'when user has not signed in yet' do + context 'when registering using invitation email' do before do stub_application_setting(send_user_confirmation_email: send_email_confirmation) visit invite_path(group_invite.raw_invite_token) - click_link 'Register now' end - context 'with admin appoval required enabled' do + context 'with admin approval required enabled' do before do stub_application_setting(require_admin_approval_after_user_signup: true) end diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb index aa61aff3b05..80bf964e2ee 100644 --- a/spec/features/issues/bulk_assignment_labels_spec.rb +++ b/spec/features/issues/bulk_assignment_labels_spec.rb @@ -295,8 +295,8 @@ RSpec.describe 'Issues > Labels bulk assignment' do before do issue1.milestone = milestone issue2.milestone = milestone - issue1.save - issue2.save + issue1.save! + issue2.save! issue1.labels << bug issue2.labels << feature diff --git a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb index a4e9df604a9..34d78880991 100644 --- a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb +++ b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb @@ -18,10 +18,6 @@ RSpec.describe 'Resolving all open threads in a merge request from an issue', :j end end - before do - stub_feature_flags(remove_resolve_note: false) - end - describe 'as a user with access to the project' do before do project.add_maintainer(user) @@ -37,7 +33,7 @@ RSpec.describe 'Resolving all open threads in a merge request from an issue', :j context 'resolving the thread' do before do - click_button 'Resolve thread' + find('button[data-qa-selector="resolve_discussion_button"]').click end it 'hides the link for creating a new issue' do diff --git a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb index 99dc71f0559..ac3471e8401 100644 --- a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb +++ b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb @@ -14,10 +14,6 @@ RSpec.describe 'Resolve an open thread in a merge request by creating an issue', "a[title=\"#{title}\"][href=\"#{url}\"]" end - before do - stub_feature_flags(remove_resolve_note: false) - end - describe 'As a user with access to the project' do before do project.add_maintainer(user) @@ -39,7 +35,7 @@ RSpec.describe 'Resolve an open thread in a merge request by creating an issue', context 'resolving the thread' do before do - click_button 'Resolve thread' + find('button[data-qa-selector="resolve_discussion_button"]').click end it 'hides the link for creating a new issue' do diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb index dac066856c0..5ca20028485 100644 --- a/spec/features/issues/form_spec.rb +++ b/spec/features/issues/form_spec.rb @@ -156,7 +156,7 @@ RSpec.describe 'New/edit issue', :js do expect(page.all('input[name="issue[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s) expect(page.all('input[name="issue[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s) - click_button 'Submit issue' + click_button 'Create issue' page.within '.issuable-sidebar' do page.within '.assignee' do diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index e6ebc37ba59..0cefbae4d37 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -3,16 +3,23 @@ require 'spec_helper' RSpec.describe 'GFM autocomplete', :js do - let_it_be(:user_xss_title) { 'eve User edits issue", :js do end it 'warns about version conflict' do - issue.update(title: "New title") + issue.update!(title: "New title") fill_in 'issue_title', with: 'bug 345' fill_in 'issue_description', with: 'bug description' @@ -142,10 +142,8 @@ RSpec.describe "Issues > User edits issue", :js do it 'can remove label without removing label added via quick action', :aggregate_failures do # Add `syzygy` label with a quick action - note = find('#note-body') - page.within '.timeline-content-form' do - note.native.send_keys('/label ~syzygy') - end + fill_in 'Comment', with: '/label ~syzygy' + click_button 'Comment' wait_for_requests @@ -169,80 +167,165 @@ RSpec.describe "Issues > User edits issue", :js do end describe 'update assignee' do - context 'by authorized user' do - def close_dropdown_menu_if_visible - find('.dropdown-menu-toggle', visible: :all).tap do |toggle| - toggle.click if toggle.visible? - end + context 'when GraphQL assignees widget feature flag is disabled' do + before do + stub_feature_flags(issue_assignees_widget: false) end - it 'allows user to select unassigned' do - visit project_issue_path(project, issue) + context 'by authorized user' do + def close_dropdown_menu_if_visible + find('.dropdown-menu-toggle', visible: :all).tap do |toggle| + toggle.click if toggle.visible? + end + end - page.within('.assignee') do - expect(page).to have_content "#{user.name}" + it 'allows user to select unassigned' do + visit project_issue_path(project, issue) - click_link 'Edit' - click_link 'Unassigned' - first('.title').click - expect(page).to have_content 'None - assign yourself' + page.within('.assignee') do + expect(page).to have_content "#{user.name}" + + click_link 'Edit' + click_link 'Unassigned' + first('.title').click + + expect(page).to have_content 'None - assign yourself' + end end - end - it 'allows user to select an assignee' do - issue2 = create(:issue, project: project, author: user) - visit project_issue_path(project, issue2) + it 'allows user to select an assignee' do + issue2 = create(:issue, project: project, author: user) + visit project_issue_path(project, issue2) - page.within('.assignee') do - expect(page).to have_content "None" + page.within('.assignee') do + expect(page).to have_content "None" + end + + page.within '.assignee' do + click_link 'Edit' + end + + page.within '.dropdown-menu-user' do + click_link user.name + end + + page.within('.assignee') do + expect(page).to have_content user.name + end end - page.within '.assignee' do - click_link 'Edit' + it 'allows user to unselect themselves' do + issue2 = create(:issue, project: project, author: user, assignees: [user]) + + visit project_issue_path(project, issue2) + + page.within '.assignee' do + expect(page).to have_content user.name + + click_link 'Edit' + click_link user.name + + close_dropdown_menu_if_visible + + page.within '.value .assign-yourself' do + expect(page).to have_content "None" + end + end end + end + + context 'by unauthorized user' do + let(:guest) { create(:user) } - page.within '.dropdown-menu-user' do - click_link user.name + before do + project.add_guest(guest) end - page.within('.assignee') do - expect(page).to have_content user.name + it 'shows assignee text' do + sign_out(:user) + sign_in(guest) + + visit project_issue_path(project, issue) + expect(page).to have_content issue.assignees.first.name end end + end - it 'allows user to unselect themselves' do - issue2 = create(:issue, project: project, author: user, assignees: [user]) + context 'when GraphQL assignees widget feature flag is enabled' do + context 'by authorized user' do + it 'allows user to select unassigned' do + visit project_issue_path(project, issue) - visit project_issue_path(project, issue2) + page.within('.assignee') do + expect(page).to have_content "#{user.name}" - page.within '.assignee' do - expect(page).to have_content user.name + click_button('Edit') + wait_for_requests - click_link 'Edit' - click_link user.name + find('[data-testid="unassign"]').click + find('[data-testid="title"]').click + wait_for_requests + + expect(page).to have_content 'None - assign yourself' + end + end - close_dropdown_menu_if_visible + it 'allows user to select an assignee' do + issue2 = create(:issue, project: project, author: user) + visit project_issue_path(project, issue2) - page.within '.value .assign-yourself' do + page.within('.assignee') do expect(page).to have_content "None" + click_button('Edit') + wait_for_requests + end + + page.within '.dropdown-menu-user' do + click_link user.name + end + + page.within('.assignee') do + find('[data-testid="title"]').click + wait_for_requests + + expect(page).to have_content user.name end end - end - end - context 'by unauthorized user' do - let(:guest) { create(:user) } + it 'allows user to unselect themselves' do + issue2 = create(:issue, project: project, author: user, assignees: [user]) - before do - project.add_guest(guest) + visit project_issue_path(project, issue2) + + page.within '.assignee' do + expect(page).to have_content user.name + + click_button('Edit') + wait_for_requests + click_link user.name + + find('[data-testid="title"]').click + wait_for_requests + + expect(page).to have_content "None" + end + end end - it 'shows assignee text' do - sign_out(:user) - sign_in(guest) + context 'by unauthorized user' do + let(:guest) { create(:user) } - visit project_issue_path(project, issue) - expect(page).to have_content issue.assignees.first.name + before do + project.add_guest(guest) + end + + it 'shows assignee text' do + sign_out(:user) + sign_in(guest) + + visit project_issue_path(project, issue) + expect(page).to have_content issue.assignees.first.name + end end end end @@ -309,7 +392,7 @@ RSpec.describe "Issues > User edits issue", :js do before do project.add_guest(guest) issue.milestone = milestone - issue.save + issue.save! end it 'shows milestone text' do @@ -326,24 +409,23 @@ RSpec.describe "Issues > User edits issue", :js do it 'adds due date to issue' do date = Date.today.at_beginning_of_month + 2.days - page.within '.due_date' do - click_link 'Edit' - + page.within '[data-testid="due-date"]' do + click_button 'Edit' page.within '.pika-single' do click_button date.day end wait_for_requests - expect(find('.value').text).to have_content date.strftime('%b %-d, %Y') + expect(find('[data-testid="sidebar-duedate-value"]').text).to have_content date.strftime('%b %-d, %Y') end end it 'removes due date from issue' do date = Date.today.at_beginning_of_month + 2.days - page.within '.due_date' do - click_link 'Edit' + page.within '[data-testid="due-date"]' do + click_button 'Edit' page.within '.pika-single' do click_button date.day @@ -353,7 +435,7 @@ RSpec.describe "Issues > User edits issue", :js do expect(page).to have_no_content 'None' - click_link 'remove due date' + click_button 'remove due date' expect(page).to have_content 'None' end end diff --git a/spec/features/issues/user_filters_issues_spec.rb b/spec/features/issues/user_filters_issues_spec.rb index 1b246181523..5d05df6aaf0 100644 --- a/spec/features/issues/user_filters_issues_spec.rb +++ b/spec/features/issues/user_filters_issues_spec.rb @@ -18,7 +18,7 @@ RSpec.describe 'User filters issues', :js do @issue = Issue.find_by(title: 'foobar') @issue.milestone = create(:milestone, project: project) @issue.assignees = [] - @issue.save + @issue.save! end let(:issue) { @issue } diff --git a/spec/features/issues/user_interacts_with_awards_spec.rb b/spec/features/issues/user_interacts_with_awards_spec.rb index 1c7bc5f239f..e862f7030c0 100644 --- a/spec/features/issues/user_interacts_with_awards_spec.rb +++ b/spec/features/issues/user_interacts_with_awards_spec.rb @@ -5,6 +5,10 @@ require 'spec_helper' RSpec.describe 'User interacts with awards' do let(:user) { create(:user) } + before do + stub_feature_flags(improved_emoji_picker: false) + end + describe 'User interacts with awards in an issue', :js do let(:issue) { create(:issue, project: project)} let(:project) { create(:project) } diff --git a/spec/features/issues/user_invites_from_a_comment_spec.rb b/spec/features/issues/user_invites_from_a_comment_spec.rb new file mode 100644 index 00000000000..82061f6ed79 --- /dev/null +++ b/spec/features/issues/user_invites_from_a_comment_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "User invites from a comment", :js do + let_it_be(:project) { create(:project_empty_repo, :public) } + let_it_be(:issue) { create(:issue, project: project) } + let_it_be(:user) { project.owner } + + before do + sign_in(user) + end + + it "launches the invite modal from invite link on a comment" do + stub_experiments(invite_members_in_comment: :invite_member_link) + + visit project_issue_path(project, issue) + + page.within(".new-note") do + click_button 'Invite Member' + end + + expect(page).to have_content("You're inviting members to the") + end +end diff --git a/spec/features/issues/user_sees_live_update_spec.rb b/spec/features/issues/user_sees_live_update_spec.rb index 79c6978cbc0..7e4880f209e 100644 --- a/spec/features/issues/user_sees_live_update_spec.rb +++ b/spec/features/issues/user_sees_live_update_spec.rb @@ -18,7 +18,7 @@ RSpec.describe 'Issues > User sees live update', :js do expect(page).to have_text("new title") - issue.update(title: "updated title") + issue.update!(title: "updated title") wait_for_requests expect(page).to have_text("updated title") diff --git a/spec/features/issues/user_sees_sidebar_updates_in_realtime_spec.rb b/spec/features/issues/user_sees_sidebar_updates_in_realtime_spec.rb index 7a2b637e48e..6473fe01052 100644 --- a/spec/features/issues/user_sees_sidebar_updates_in_realtime_spec.rb +++ b/spec/features/issues/user_sees_sidebar_updates_in_realtime_spec.rb @@ -19,11 +19,14 @@ RSpec.describe 'Issues > Real-time sidebar', :js do expect(page.find('.assignee')).to have_content 'None' end - gitlab_sign_in(user) + sign_in(user) + visit project_issue_path(project, issue) expect(page.find('.assignee')).to have_content 'None' click_button 'assign yourself' + wait_for_requests + expect(page.find('.assignee')).to have_content user.name using_session :other_session do expect(page.find('.assignee')).to have_content user.name diff --git a/spec/features/issues/user_sorts_issues_spec.rb b/spec/features/issues/user_sorts_issues_spec.rb index f0bb055c6f2..c161e1deb83 100644 --- a/spec/features/issues/user_sorts_issues_spec.rb +++ b/spec/features/issues/user_sorts_issues_spec.rb @@ -77,7 +77,7 @@ RSpec.describe "User sorts issues" do it 'sorts by most recently updated', :js do issue3.updated_at = Time.now + 100 - issue3.save + issue3.save! visit project_issues_path(project, sort: sort_value_recently_updated) expect(first_issue).to include('baz') @@ -85,8 +85,8 @@ RSpec.describe "User sorts issues" do describe 'sorting by due date', :js do before do - issue1.update(due_date: 1.day.from_now) - issue2.update(due_date: 6.days.from_now) + issue1.update!(due_date: 1.day.from_now) + issue2.update!(due_date: 6.days.from_now) end it 'sorts by due date' do @@ -96,7 +96,7 @@ RSpec.describe "User sorts issues" do end it 'sorts by due date by excluding nil due dates' do - issue2.update(due_date: nil) + issue2.update!(due_date: nil) visit project_issues_path(project, sort: sort_value_due_date) @@ -111,7 +111,7 @@ RSpec.describe "User sorts issues" do end it 'sorts by least recently due date by excluding nil due dates' do - issue2.update(due_date: nil) + issue2.update!(due_date: nil) visit project_issues_path(project, label_names: [label.name], sort: sort_value_due_date_later) @@ -122,8 +122,8 @@ RSpec.describe "User sorts issues" do describe 'filtering by due date', :js do before do - issue1.update(due_date: 1.day.from_now) - issue2.update(due_date: 6.days.from_now) + issue1.update!(due_date: 1.day.from_now) + issue2.update!(due_date: 6.days.from_now) end it 'filters by none' do @@ -147,9 +147,9 @@ RSpec.describe "User sorts issues" do end it 'filters by due this week' do - issue1.update(due_date: Date.today.beginning_of_week + 2.days) - issue2.update(due_date: Date.today.end_of_week) - issue3.update(due_date: Date.today - 8.days) + issue1.update!(due_date: Date.today.beginning_of_week + 2.days) + issue2.update!(due_date: Date.today.end_of_week) + issue3.update!(due_date: Date.today - 8.days) visit project_issues_path(project, due_date: Issue::DueThisWeek.name) @@ -161,9 +161,9 @@ RSpec.describe "User sorts issues" do end it 'filters by due this month' do - issue1.update(due_date: Date.today.beginning_of_month + 2.days) - issue2.update(due_date: Date.today.end_of_month) - issue3.update(due_date: Date.today - 50.days) + issue1.update!(due_date: Date.today.beginning_of_month + 2.days) + issue2.update!(due_date: Date.today.end_of_month) + issue3.update!(due_date: Date.today - 50.days) visit project_issues_path(project, due_date: Issue::DueThisMonth.name) @@ -175,9 +175,9 @@ RSpec.describe "User sorts issues" do end it 'filters by overdue' do - issue1.update(due_date: Date.today + 2.days) - issue2.update(due_date: Date.today + 20.days) - issue3.update(due_date: Date.yesterday) + issue1.update!(due_date: Date.today + 2.days) + issue2.update!(due_date: Date.today + 20.days) + issue3.update!(due_date: Date.yesterday) visit project_issues_path(project, due_date: Issue::Overdue.name) @@ -189,9 +189,9 @@ RSpec.describe "User sorts issues" do end it 'filters by due next month and previous two weeks' do - issue1.update(due_date: Date.today - 4.weeks) - issue2.update(due_date: (Date.today + 2.months).beginning_of_month) - issue3.update(due_date: Date.yesterday) + issue1.update!(due_date: Date.today - 4.weeks) + issue2.update!(due_date: (Date.today + 2.months).beginning_of_month) + issue3.update!(due_date: Date.yesterday) visit project_issues_path(project, due_date: Issue::DueNextMonthAndPreviousTwoWeeks.name) @@ -206,9 +206,9 @@ RSpec.describe "User sorts issues" do describe 'sorting by milestone', :js do before do issue1.milestone = newer_due_milestone - issue1.save + issue1.save! issue2.milestone = later_due_milestone - issue2.save + issue2.save! end it 'sorts by milestone' do @@ -224,9 +224,9 @@ RSpec.describe "User sorts issues" do before do issue1.assignees << user2 - issue1.save + issue1.save! issue2.assignees << user2 - issue2.save + issue2.save! end it 'sorts with a filter applied' do diff --git a/spec/features/markdown/markdown_spec.rb b/spec/features/markdown/markdown_spec.rb index e84b300a748..3208ad82c03 100644 --- a/spec/features/markdown/markdown_spec.rb +++ b/spec/features/markdown/markdown_spec.rb @@ -63,8 +63,8 @@ RSpec.describe 'GitLab Markdown', :aggregate_failures do end aggregate_failures 'parses fenced code blocks' do - expect(doc).to have_selector('pre.code.highlight.js-syntax-highlight.c') - expect(doc).to have_selector('pre.code.highlight.js-syntax-highlight.python') + expect(doc).to have_selector('pre.code.highlight.js-syntax-highlight.language-c') + expect(doc).to have_selector('pre.code.highlight.js-syntax-highlight.language-python') end aggregate_failures 'parses mermaid code block' do @@ -288,9 +288,10 @@ RSpec.describe 'GitLab Markdown', :aggregate_failures do @wiki = @feat.wiki @wiki_page = @feat.wiki_page - path = 'images/example.jpg' - gitaly_wiki_file = Gitlab::GitalyClient::WikiFile.new(path: path) - expect(@wiki).to receive(:find_file).with(path, load_content: false).and_return(Gitlab::Git::WikiFile.new(gitaly_wiki_file)) + name = 'example.jpg' + path = "images/#{name}" + blob = double(name: name, path: path, mime_type: 'image/jpeg', data: nil) + expect(@wiki).to receive(:find_file).with(path, load_content: false).and_return(Gitlab::Git::WikiFile.new(blob)) allow(@wiki).to receive(:wiki_base_path) { '/namespace1/gitlabhq/wikis' } @html = markdown(@feat.raw_markdown, { pipeline: :wiki, wiki: @wiki, page_slug: @wiki_page.slug }) diff --git a/spec/features/merge_request/batch_comments_spec.rb b/spec/features/merge_request/batch_comments_spec.rb index 25f2707146d..19680a827bf 100644 --- a/spec/features/merge_request/batch_comments_spec.rb +++ b/spec/features/merge_request/batch_comments_spec.rb @@ -28,7 +28,7 @@ RSpec.describe 'Merge request > Batch comments', :js do end it 'adds draft note' do - write_comment + write_diff_comment expect(find('.draft-note-component')).to have_content('Line is wrong') @@ -38,7 +38,7 @@ RSpec.describe 'Merge request > Batch comments', :js do end it 'publishes review' do - write_comment + write_diff_comment page.within('.review-bar-content') do click_button 'Submit review' @@ -52,7 +52,7 @@ RSpec.describe 'Merge request > Batch comments', :js do end it 'publishes single comment' do - write_comment + write_diff_comment click_button 'Add comment now' @@ -64,7 +64,7 @@ RSpec.describe 'Merge request > Batch comments', :js do end it 'deletes draft note' do - write_comment + write_diff_comment accept_alert { find('.js-note-delete').click } @@ -74,21 +74,70 @@ RSpec.describe 'Merge request > Batch comments', :js do end it 'edits draft note' do - write_comment + write_diff_comment find('.js-note-edit').click # make sure comment form is in view execute_script("window.scrollBy(0, 200)") - page.within('.js-discussion-note-form') do - fill_in('note_note', with: 'Testing update') - click_button('Save comment') + write_comment(text: 'Testing update', button_text: 'Save comment') + + expect(page).to have_selector('.draft-note-component', text: 'Testing update') + end + + context 'with image and file draft note' do + let(:merge_request) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project) } + let!(:draft_on_text) { create(:draft_note_on_text_diff, merge_request: merge_request, author: user, path: 'README.md', note: 'Lorem ipsum on text...') } + let!(:draft_on_image) { create(:draft_note_on_image_diff, merge_request: merge_request, author: user, path: 'files/images/ee_repo_logo.png', note: 'Lorem ipsum on an image...') } + + it 'does not show in overview' do + visit_overview + + expect(page).to have_no_text(draft_on_text.note) + expect(page).to have_no_text(draft_on_image.note) end + end - wait_for_requests + context 'adding single comment to review' do + before do + visit_overview + end - expect(page).to have_selector('.draft-note-component', text: 'Testing update') + it 'at first does not show `Add to review` and `Add comment now` buttons' do + expect(page).to have_no_button('Add to review') + expect(page).to have_no_button('Add comment now') + end + + context 'when review has started' do + before do + visit_diffs + + write_diff_comment + + visit_overview + end + + it 'can add comment to review' do + write_comment(selector: '.js-main-target-form', field: 'note-body', text: 'Its a draft comment', button_text: 'Add to review') + + expect(page).to have_selector('.draft-note-component', text: 'Its a draft comment') + + click_button('Pending comments') + + expect(page).to have_text('2 pending comments') + end + + it 'can add comment right away' do + write_comment(selector: '.js-main-target-form', field: 'note-body', text: 'Its a regular comment', button_text: 'Add comment now') + + expect(page).to have_selector('.note:not(.draft-note)', text: 'Its a regular comment') + + click_button('Pending comments') + + expect(page).to have_text('1 pending comment') + end + end end context 'in parallel diff' do @@ -197,46 +246,51 @@ RSpec.describe 'Merge request > Batch comments', :js do wait_for_requests end - def write_comment(button_text: 'Start a review', text: 'Line is wrong') - click_diff_line(find("[id='#{sample_compare.changes[0][:line_code]}']")) - - page.within('.js-discussion-note-form') do - fill_in('note_note', with: text) - click_button(button_text) - end + def visit_overview + visit project_merge_request_path(merge_request.project, merge_request) wait_for_requests end - def write_parallel_comment(line, button_text: 'Start a review', text: 'Line is wrong') + def write_diff_comment(**params) + click_diff_line(find("[id='#{sample_compare.changes[0][:line_code]}']")) + + write_comment(**params) + end + + def write_parallel_comment(line, **params) find("td[id='#{line}']").hover find(".is-over button").click - page.within("form[data-line-code='#{line}']") do - fill_in('note_note', with: text) + write_comment(selector: "form[data-line-code='#{line}']", **params) + end + + def write_comment(selector: '.js-discussion-note-form', field: 'note_note', button_text: 'Start a review', text: 'Line is wrong') + page.within(selector) do + fill_in(field, with: text) click_button(button_text) end wait_for_requests end -end -def write_reply_to_discussion(button_text: 'Start a review', text: 'Line is wrong', resolve: false, unresolve: false) - page.within(first('.diff-files-holder .discussion-reply-holder')) do - find_field('Reply…', match: :first).click + def write_reply_to_discussion(button_text: 'Start a review', text: 'Line is wrong', resolve: false, unresolve: false) + page.within(first('.diff-files-holder .discussion-reply-holder')) do + find_field('Reply…', match: :first).click - fill_in('note_note', with: text) + fill_in('note_note', with: text) - if resolve - page.check('Resolve thread') - end + if resolve + page.check('Resolve thread') + end + + if unresolve + page.check('Unresolve thread') + end - if unresolve - page.check('Unresolve thread') + click_button(button_text) end - click_button(button_text) + wait_for_requests end - - wait_for_requests end diff --git a/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb b/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb index fd13083c185..d36abf86518 100644 --- a/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb +++ b/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb @@ -28,7 +28,7 @@ RSpec.describe 'create a merge request, allowing commits from members who can me check 'Allow commits from members who can merge to the target branch' - click_button 'Submit merge request' + click_button 'Create merge request' wait_for_requests diff --git a/spec/features/merge_request/user_awards_emoji_spec.rb b/spec/features/merge_request/user_awards_emoji_spec.rb index 62e4209f386..6f46cc20cba 100644 --- a/spec/features/merge_request/user_awards_emoji_spec.rb +++ b/spec/features/merge_request/user_awards_emoji_spec.rb @@ -17,33 +17,28 @@ RSpec.describe 'Merge request > User awards emoji', :js do end it 'adds award to merge request' do - first('.js-emoji-btn').click - expect(page).to have_selector('.js-emoji-btn.active') - expect(first('.js-emoji-btn')).to have_content '1' + first('[data-testid="award-button"]').click + expect(page).to have_selector('[data-testid="award-button"].is-active') + expect(first('[data-testid="award-button"]')).to have_content '1' visit project_merge_request_path(project, merge_request) - expect(first('.js-emoji-btn')).to have_content '1' + expect(first('[data-testid="award-button"]')).to have_content '1' end it 'removes award from merge request' do - first('.js-emoji-btn').click - find('.js-emoji-btn.active').click - expect(first('.js-emoji-btn')).to have_content '0' + first('[data-testid="award-button"]').click + find('[data-testid="award-button"].is-active').click + expect(first('[data-testid="award-button"]')).to have_content '0' visit project_merge_request_path(project, merge_request) - expect(first('.js-emoji-btn')).to have_content '0' - end - - it 'has only one menu on the page' do - first('.js-add-award').click - expect(page).to have_selector('.emoji-menu') - - expect(page).to have_selector('.emoji-menu', count: 1) + expect(first('[data-testid="award-button"]')).to have_content '0' end it 'adds awards to note' do - first('.js-note-emoji').click - first('.emoji-menu .js-emoji-btn').click + page.within('.note-actions') do + first('.note-emoji-button').click + find('gl-emoji[data-name="8ball"]').click + end wait_for_requests diff --git a/spec/features/merge_request/user_creates_merge_request_spec.rb b/spec/features/merge_request/user_creates_merge_request_spec.rb index 37d329d4d5d..119cf31098c 100644 --- a/spec/features/merge_request/user_creates_merge_request_spec.rb +++ b/spec/features/merge_request/user_creates_merge_request_spec.rb @@ -31,7 +31,7 @@ RSpec.describe "User creates a merge request", :js do end fill_in("Title", with: title) - click_button("Submit merge request") + click_button("Create merge request") page.within(".merge-request") do expect(page).to have_content(title) @@ -87,7 +87,7 @@ RSpec.describe "User creates a merge request", :js do click_button("Compare branches and continue") - expect(page).to have_css("h3.page-title", text: "New Merge Request") + expect(page).to have_css("h3.page-title", text: "New merge request") page.within("form#new_merge_request") do fill_in("Title", with: title) @@ -103,7 +103,7 @@ RSpec.describe "User creates a merge request", :js do end find('.js-assignee-search').click - click_button("Submit merge request") + click_button("Create merge request") expect(page).to have_content(title).and have_content("Request to merge #{user.namespace.path}:#{source_branch} into master") end diff --git a/spec/features/merge_request/user_invites_from_a_comment_spec.rb b/spec/features/merge_request/user_invites_from_a_comment_spec.rb new file mode 100644 index 00000000000..79865094fd0 --- /dev/null +++ b/spec/features/merge_request/user_invites_from_a_comment_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "User invites from a comment", :js do + let_it_be(:project) { create(:project, :public, :repository) } + let_it_be(:merge_request) { create(:merge_request, source_project: project) } + let_it_be(:user) { project.owner } + + before do + sign_in(user) + end + + it "launches the invite modal from invite link on a comment" do + stub_experiments(invite_members_in_comment: :invite_member_link) + + visit project_merge_request_path(project, merge_request) + + page.within(".new-note") do + click_button 'Invite Member' + end + + expect(page).to have_content("You're inviting members to the") + end +end diff --git a/spec/features/merge_request/user_posts_notes_spec.rb b/spec/features/merge_request/user_posts_notes_spec.rb index 3099a893dc2..a6dfae72912 100644 --- a/spec/features/merge_request/user_posts_notes_spec.rb +++ b/spec/features/merge_request/user_posts_notes_spec.rb @@ -173,7 +173,7 @@ RSpec.describe 'Merge request > User posts notes', :js do it 'allows using markdown buttons after saving a note and then trying to edit it again' do page.within('.current-note-edit-form') do fill_in 'note[note]', with: 'This is the new content' - find('.btn-success').click + find('.btn-confirm').click end find('.note').hover @@ -182,16 +182,16 @@ RSpec.describe 'Merge request > User posts notes', :js do find('.js-note-edit').click page.within('.current-note-edit-form') do - expect(find('#note_note').value).to include('This is the new content') + expect(find_field('note[note]').value).to include('This is the new content') first('.js-md').click - expect(find('#note_note').value).to include('This is the new content****') + expect(find_field('note[note]').value).to include('This is the new content****') end end it 'appends the edited at time to the note' do page.within('.current-note-edit-form') do fill_in 'note[note]', with: 'Some new content' - find('.btn-success').click + find('.btn-confirm').click end page.within("#note_#{note.id}") do diff --git a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb index caa04059469..9a3f97a0943 100644 --- a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb +++ b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb @@ -15,10 +15,6 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do diff_refs: merge_request.diff_refs) end - before do - stub_feature_flags(remove_resolve_note: false) - end - context 'no threads' do before do project.add_maintainer(user) @@ -67,7 +63,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do it 'allows user to mark thread as resolved' do page.within '.diff-content' do - click_button 'Resolve thread' + find('button[data-qa-selector="resolve_discussion_button"]').click end expect(page).to have_selector('.discussion-body', visible: false) @@ -84,7 +80,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do it 'allows user to unresolve thread' do page.within '.diff-content' do - click_button 'Resolve thread' + find('button[data-qa-selector="resolve_discussion_button"]').click click_button 'Unresolve thread' end @@ -96,7 +92,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do describe 'resolved thread' do before do page.within '.diff-content' do - click_button 'Resolve thread' + find('button[data-qa-selector="resolve_discussion_button"]').click end visit_merge_request @@ -197,7 +193,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do it 'allows user to resolve from reply form without a comment' do page.within '.diff-content' do - click_button 'Resolve thread' + find('button[data-qa-selector="resolve_discussion_button"]').click end page.within '.line-resolve-all-container' do @@ -234,7 +230,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do it 'hides jump to next button when all resolved' do page.within '.diff-content' do - click_button 'Resolve thread' + find('button[data-qa-selector="resolve_discussion_button"]').click end expect(page).to have_selector('.discussion-next-btn', visible: false) @@ -264,7 +260,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do visit_merge_request end - it 'does not mark thread as resolved when resolving single note' do + it 'marks thread as resolved when resolving single note' do page.within("#note_#{note.id}") do first('.line-resolve-btn').click @@ -273,15 +269,13 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do expect(first('.line-resolve-btn')['aria-label']).to eq("Resolved by #{user.name}") end - expect(page).to have_content('Last updated') - page.within '.line-resolve-all-container' do - expect(page).to have_content('1 unresolved thread') + expect(page).to have_content('All threads resolved') end end it 'resolves thread' do - resolve_buttons = page.all('.note .line-resolve-btn', count: 2) + resolve_buttons = page.all('.note .line-resolve-btn', count: 1) resolve_buttons.each do |button| button.click end @@ -332,7 +326,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do it 'allows user to mark all threads as resolved' do page.all('.discussion-reply-holder', count: 2).each do |reply_holder| page.within reply_holder do - click_button 'Resolve thread' + find('button[data-qa-selector="resolve_discussion_button"]').click end end @@ -344,7 +338,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do it 'allows user to quickly scroll to next unresolved thread' do page.within('.discussion-reply-holder', match: :first) do - click_button 'Resolve thread' + find('button[data-qa-selector="resolve_discussion_button"]').click end page.within '.line-resolve-all-container' do @@ -416,7 +410,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do it 'allows user to mark thread as resolved' do page.within '.diff-content' do - click_button 'Resolve thread' + find('button[data-qa-selector="resolve_discussion_button"]').click end page.within '.diff-content .note' do @@ -431,7 +425,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do it 'allows user to unresolve thread' do page.within '.diff-content' do - click_button 'Resolve thread' + find('button[data-qa-selector="resolve_discussion_button"]').click click_button 'Unresolve thread' end @@ -459,7 +453,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do it 'allows user to comment & unresolve thread' do page.within '.diff-content' do - click_button 'Resolve thread' + find('button[data-qa-selector="resolve_discussion_button"]').click find_field('Reply…').click diff --git a/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb b/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb index 95e435a333e..d8b258bac47 100644 --- a/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb +++ b/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb @@ -13,7 +13,7 @@ RSpec.describe 'New merge request breadcrumb' do it 'displays link to project merge requests and new merge request' do page.within '.breadcrumbs' do - expect(find_link('Merge Requests')[:href]).to end_with(project_merge_requests_path(project)) + expect(find_link('Merge requests')[:href]).to end_with(project_merge_requests_path(project)) expect(find_link('New')[:href]).to end_with(project_new_merge_request_path(project)) end end diff --git a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb index ad0e9b48903..733b5a97fea 100644 --- a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb +++ b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb @@ -26,7 +26,6 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request', end before do - stub_feature_flags(new_pipelines_table: false) stub_application_setting(auto_devops_enabled: false) stub_ci_pipeline_yaml_file(YAML.dump(config)) project.add_maintainer(user) @@ -62,7 +61,7 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request', it 'sees branch pipelines and detached merge request pipelines in correct order' do page.within('.ci-table') do - expect(page).to have_selector('.ci-pending', count: 2) + expect(page).to have_selector('.ci-created', count: 2) expect(first('[data-testid="pipeline-url-link"]')).to have_content("##{detached_merge_request_pipeline.id}") end end @@ -154,7 +153,7 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request', context 'when detached merge request pipeline succeeds' do before do - detached_merge_request_pipeline.succeed! + detached_merge_request_pipeline.reload.succeed! wait_for_requests end @@ -168,7 +167,7 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request', context 'when branch pipeline succeeds' do before do click_link 'Overview' - push_pipeline.succeed! + push_pipeline.reload.succeed! wait_for_requests end @@ -197,7 +196,7 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request', it 'sees a branch pipeline in pipeline tab' do page.within('.ci-table') do - expect(page).to have_selector('.ci-pending', count: 1) + expect(page).to have_selector('.ci-created', count: 1) expect(first('[data-testid="pipeline-url-link"]')).to have_content("##{push_pipeline.id}") end end @@ -333,6 +332,31 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request', end end + context 'when the latest pipeline is running in the parent project' do + before do + Ci::CreatePipelineService.new(project, user, ref: 'feature') + .execute(:merge_request_event, merge_request: merge_request) + end + + context 'when the previous pipeline failed in the fork project' do + before do + detached_merge_request_pipeline.reload.drop! + end + + context 'when the parent project enables pipeline must succeed' do + before do + project.update!(only_allow_merge_if_pipeline_succeeds: true) + end + + it 'shows MWPS button' do + visit project_merge_request_path(project, merge_request) + + expect(page).to have_button('Merge when pipeline succeeds') + end + end + end + end + context 'when a user merges a merge request from a forked project to the parent project' do before do click_link("Overview") @@ -351,7 +375,7 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request', context 'when detached merge request pipeline succeeds' do before do - detached_merge_request_pipeline.succeed! + detached_merge_request_pipeline.reload.succeed! wait_for_requests end @@ -364,7 +388,7 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request', context 'when branch pipeline succeeds' do before do - push_pipeline.succeed! + push_pipeline.reload.succeed! wait_for_requests end 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 05fa5459e06..0cb4107c21d 100644 --- a/spec/features/merge_request/user_sees_merge_widget_spec.rb +++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb @@ -32,7 +32,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js do end it 'shows widget status after creating new merge request' do - click_button 'Submit merge request' + click_button 'Create merge request' wait_for_requests diff --git a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb index c0dc2ec3baf..e5592ae9535 100644 --- a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb +++ b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb @@ -23,7 +23,7 @@ RSpec.describe 'Merge request < User sees mini pipeline graph', :js do end it 'displays a mini pipeline graph' do - expect(page).to have_selector('.mr-widget-pipeline-graph') + expect(page).to have_selector('[data-testid="pipeline-mini-graph"]') end context 'as json' do @@ -57,6 +57,10 @@ RSpec.describe 'Merge request < User sees mini pipeline graph', :js do first(dropdown_selector) end + before do + wait_for_requests + end + # Status icon button styles should update as described in # https://gitlab.com/gitlab-org/gitlab-foss/issues/42769 it 'has unique styles for default, :hover, :active, and :focus states' do diff --git a/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb b/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb index ea46ae06329..b8b7fc2009f 100644 --- a/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb +++ b/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb @@ -28,8 +28,8 @@ RSpec.describe 'Merge request > User sees notes from forked project', :js do page.within('.discussion-notes') do find_field('Reply…').click - scroll_to(page.find('#note_note', visible: false)) - find('#note_note').send_keys('A reply comment') + scroll_to(find_field('note[note]', visible: false)) + fill_in 'note[note]', with: 'A reply comment' find('.js-comment-button').click end diff --git a/spec/features/merge_request/user_sees_pipelines_spec.rb b/spec/features/merge_request/user_sees_pipelines_spec.rb index 77d2cb77ae3..a5047c8d550 100644 --- a/spec/features/merge_request/user_sees_pipelines_spec.rb +++ b/spec/features/merge_request/user_sees_pipelines_spec.rb @@ -41,7 +41,7 @@ RSpec.describe 'Merge request > User sees pipelines', :js do context 'with a detached merge request pipeline' do let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) } - it 'displays the Run Pipeline button' do + it 'displays the "Run pipeline" button' do visit project_merge_request_path(project, merge_request) page.within('.merge-request-tabs') do @@ -50,14 +50,14 @@ RSpec.describe 'Merge request > User sees pipelines', :js do wait_for_requests - expect(page.find('[data-testid="run_pipeline_button"]')).to have_text('Run Pipeline') + expect(page.find('[data-testid="run_pipeline_button"]')).to have_text('Run pipeline') end end context 'with a merged results pipeline' do let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) } - it 'displays the Run Pipeline button' do + it 'displays the "Run pipeline" button' do visit project_merge_request_path(project, merge_request) page.within('.merge-request-tabs') do @@ -66,7 +66,7 @@ RSpec.describe 'Merge request > User sees pipelines', :js do wait_for_requests - expect(page.find('[data-testid="run_pipeline_button"]')).to have_text('Run Pipeline') + expect(page.find('[data-testid="run_pipeline_button"]')).to have_text('Run pipeline') end end end @@ -131,7 +131,7 @@ RSpec.describe 'Merge request > User sees pipelines', :js do visit project_merge_request_path(parent_project, merge_request) create_merge_request_pipeline - act_on_security_warning(action: 'Run Pipeline') + act_on_security_warning(action: 'Run pipeline') check_pipeline(expected_project: parent_project) check_head_pipeline(expected_project: parent_project) @@ -175,7 +175,7 @@ RSpec.describe 'Merge request > User sees pipelines', :js do def create_merge_request_pipeline page.within('.merge-request-tabs') { click_link('Pipelines') } - click_button('Run Pipeline') + click_button('Run pipeline') end def check_pipeline(expected_project:) diff --git a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb index 9850ca3f173..275a87ca391 100644 --- a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb +++ b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb @@ -60,7 +60,7 @@ RSpec.describe 'Merge request > User selects branches for new MR', :js do expect(page).to have_content "wm.png" fill_in "merge_request_title", with: "Orphaned MR test" - click_button "Submit merge request" + click_button "Create merge request" click_button "Check out branch" @@ -200,7 +200,7 @@ RSpec.describe 'Merge request > User selects branches for new MR', :js do click_button "Compare branches" - expect(page).to have_button("Submit merge request") + expect(page).to have_button("Create merge request") end end end diff --git a/spec/features/merge_request/user_squashes_merge_request_spec.rb b/spec/features/merge_request/user_squashes_merge_request_spec.rb index 84964bd0637..15f59c0d7bc 100644 --- a/spec/features/merge_request/user_squashes_merge_request_spec.rb +++ b/spec/features/merge_request/user_squashes_merge_request_spec.rb @@ -92,7 +92,7 @@ RSpec.describe 'User squashes a merge request', :js do before do visit project_new_merge_request_path(project, merge_request: { target_branch: 'master', source_branch: source_branch }) check 'merge_request[squash]' - click_on 'Submit merge request' + click_on 'Create merge request' wait_for_requests end @@ -121,7 +121,7 @@ RSpec.describe 'User squashes a merge request', :js do context 'when squash is not enabled on merge request creation', :sidekiq_might_not_need_inline do before do visit project_new_merge_request_path(project, merge_request: { target_branch: 'master', source_branch: source_branch }) - click_on 'Submit merge request' + click_on 'Create merge request' wait_for_requests end diff --git a/spec/features/merge_request/user_views_open_merge_request_spec.rb b/spec/features/merge_request/user_views_open_merge_request_spec.rb index 5f99d762ecb..f1b44010f63 100644 --- a/spec/features/merge_request/user_views_open_merge_request_spec.rb +++ b/spec/features/merge_request/user_views_open_merge_request_spec.rb @@ -125,7 +125,7 @@ RSpec.describe 'User views an open merge request' do end it 'encodes branch name' do - expect(find('cite.ref-name')[:title]).to eq(source_branch) + expect(find("[data-testid='ref-name']")[:title]).to eq(source_branch) end end end diff --git a/spec/features/milestones/user_creates_milestone_spec.rb b/spec/features/milestones/user_creates_milestone_spec.rb index d80796b8f9a..dd377aa4a26 100644 --- a/spec/features/milestones/user_creates_milestone_spec.rb +++ b/spec/features/milestones/user_creates_milestone_spec.rb @@ -14,7 +14,7 @@ RSpec.describe "User creates milestone", :js do end it "creates milestone" do - title = "v2.3".freeze + title = "v2.3" fill_in("Title", with: title) fill_in("Description", with: "# Description header") diff --git a/spec/features/milestones/user_views_milestone_spec.rb b/spec/features/milestones/user_views_milestone_spec.rb index 9c19f842427..8674d59afdf 100644 --- a/spec/features/milestones/user_views_milestone_spec.rb +++ b/spec/features/milestones/user_views_milestone_spec.rb @@ -98,7 +98,7 @@ RSpec.describe "User views milestone" do visit(project_milestone_path(project, milestone)) within('.js-milestone-tabs') do - click_link('Merge Requests') + click_link('Merge requests') end wait_for_requests @@ -116,7 +116,7 @@ RSpec.describe "User views milestone" do visit(group_milestone_path(group, group_milestone)) within('.js-milestone-tabs') do - click_link('Merge Requests') + click_link('Merge requests') end expect(page.find('#tab-merge-requests')).to have_text(project.name) diff --git a/spec/features/milestones/user_views_milestones_spec.rb b/spec/features/milestones/user_views_milestones_spec.rb index f8b4b802a60..58439df92ba 100644 --- a/spec/features/milestones/user_views_milestones_spec.rb +++ b/spec/features/milestones/user_views_milestones_spec.rb @@ -18,7 +18,7 @@ RSpec.describe "User views milestones" do expect(page).to have_content(milestone.title) .and have_content(milestone.expires_at) .and have_content("Issues") - .and have_content("Merge Requests") + .and have_content("Merge requests") end context "with issues", :js do @@ -80,7 +80,6 @@ RSpec.describe "User views milestones with no MR" do expect(page).to have_content(milestone.title) .and have_content(milestone.expires_at) .and have_content("Issues") - .and have_no_content("Merge Requests") end it "opens milestone" do diff --git a/spec/features/participants_autocomplete_spec.rb b/spec/features/participants_autocomplete_spec.rb index b22778012a8..2781cfffbaf 100644 --- a/spec/features/participants_autocomplete_spec.rb +++ b/spec/features/participants_autocomplete_spec.rb @@ -3,9 +3,9 @@ require 'spec_helper' RSpec.describe 'Member autocomplete', :js do - let(:project) { create(:project, :public) } - let(:user) { create(:user) } - let(:author) { create(:user) } + let_it_be(:project) { create(:project, :public, :repository) } + let_it_be(:user) { create(:user) } + let_it_be(:author) { create(:user) } let(:note) { create(:note, noteable: noteable, project: noteable.project) } before do @@ -15,20 +15,16 @@ RSpec.describe 'Member autocomplete', :js do shared_examples "open suggestions when typing @" do |resource_name| before do - page.within('.new-note') do - if resource_name == 'commit' - find('#note_note').send_keys('@') - else - find('#note-body').send_keys('@') - end + if resource_name == 'commit' + fill_in 'note[note]', with: '@' + else + fill_in 'Comment', with: '@' end end it 'suggests noteable author and note author' do - page.within('.atwho-view', visible: true) do - expect(page).to have_content(author.username) - expect(page).to have_content(note.author.username) - end + expect(find_autocomplete_menu).to have_text(author.username) + expect(find_autocomplete_menu).to have_text(note.author.username) end end @@ -51,22 +47,17 @@ RSpec.describe 'Member autocomplete', :js do stub_feature_flags(tribute_autocomplete: true) visit project_issue_path(project, noteable) - page.within('.new-note') do - find('#note-body').send_keys('@') - end + fill_in 'Comment', with: '@' 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 + expect(find_tribute_autocomplete_menu).to have_content(author.username) + expect(find_tribute_autocomplete_menu).to have_content(note.author.username) end end end context 'adding a new note on a Merge Request' do - let(:project) { create(:project, :public, :repository) } let(:noteable) do create(:merge_request, source_project: project, target_project: project, author: author) @@ -80,7 +71,6 @@ RSpec.describe 'Member autocomplete', :js do end context 'adding a new note on a Commit' do - let(:project) { create(:project, :public, :repository) } let(:noteable) { project.commit } let(:note) { create(:note_on_commit, project: project, commit_id: project.commit.id) } @@ -94,4 +84,14 @@ RSpec.describe 'Member autocomplete', :js do include_examples "open suggestions when typing @", 'commit' end + + private + + def find_autocomplete_menu + find('.atwho-view ul', visible: true) + end + + def find_tribute_autocomplete_menu + find('.tribute-container ul', visible: true) + end end diff --git a/spec/features/profiles/emails_spec.rb b/spec/features/profiles/emails_spec.rb index bdf1f8b022a..6b6f628e2d5 100644 --- a/spec/features/profiles/emails_spec.rb +++ b/spec/features/profiles/emails_spec.rb @@ -43,7 +43,7 @@ RSpec.describe 'Profile > Emails' do end it 'user removes email' do - user.emails.create(email: 'my@email.com') + user.emails.create!(email: 'my@email.com') visit profile_emails_path expect(page).to have_content("my@email.com") @@ -52,7 +52,7 @@ RSpec.describe 'Profile > Emails' do end it 'user confirms email' do - email = user.emails.create(email: 'my@email.com') + email = user.emails.create!(email: 'my@email.com') visit profile_emails_path expect(page).to have_content("#{email.email} Unverified") @@ -64,7 +64,7 @@ RSpec.describe 'Profile > Emails' do end it 'user re-sends confirmation email' do - email = user.emails.create(email: 'my@email.com') + email = user.emails.create!(email: 'my@email.com') visit profile_emails_path expect { click_link("Resend confirmation email") }.to have_enqueued_job.on_queue('mailers') @@ -72,7 +72,7 @@ RSpec.describe 'Profile > Emails' do end it 'old unconfirmed emails show Send Confirmation button' do - email = user.emails.create(email: 'my@email.com') + email = user.emails.create!(email: 'my@email.com') email.update_attribute(:confirmation_sent_at, nil) visit profile_emails_path diff --git a/spec/features/profiles/password_spec.rb b/spec/features/profiles/password_spec.rb index 039966080d8..c9059395377 100644 --- a/spec/features/profiles/password_spec.rb +++ b/spec/features/profiles/password_spec.rb @@ -119,7 +119,7 @@ RSpec.describe 'Profile > Password' do before do sign_in(user) - user.update(password_expires_at: 1.hour.ago) + user.update!(password_expires_at: 1.hour.ago) user.identities.delete expect(user.ldap_user?).to eq false end diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb index 9e56ef087ae..c85657c89d5 100644 --- a/spec/features/profiles/personal_access_tokens_spec.rb +++ b/spec/features/profiles/personal_access_tokens_spec.rb @@ -95,7 +95,7 @@ RSpec.describe 'Profile > Personal Access Tokens', :js do end it "removes expired tokens from 'active' section" do - personal_access_token.update(expires_at: 5.days.ago) + personal_access_token.update!(expires_at: 5.days.ago) visit profile_personal_access_tokens_path expect(page).to have_selector(".settings-message") diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb index bd4917824d1..57f7c7878e3 100644 --- a/spec/features/profiles/user_edit_profile_spec.rb +++ b/spec/features/profiles/user_edit_profile_spec.rb @@ -212,8 +212,10 @@ RSpec.describe 'User edit profile' do end it 'shows author as busy in the assignee dropdown' do - find('.block.assignee .edit-link').click - wait_for_requests + page.within('.assignee') do + click_button('Edit') + wait_for_requests + end page.within '.dropdown-menu-user' do expect(page).to have_content("#{user.name} (Busy)") @@ -227,7 +229,7 @@ RSpec.describe 'User edit profile' do visit project_issue_path(project, issue) wait_for_requests - expect(page.find('[data-testid="expanded-assignee"]')).to have_text("#{user.name} (Busy)") + expect(page.find('.issuable-assignees')).to have_content("#{user.name} (Busy)") end end diff --git a/spec/features/profiles/user_search_settings_spec.rb b/spec/features/profiles/user_search_settings_spec.rb index 60df0d7532b..64a8556e349 100644 --- a/spec/features/profiles/user_search_settings_spec.rb +++ b/spec/features/profiles/user_search_settings_spec.rb @@ -10,9 +10,11 @@ RSpec.describe 'User searches their settings', :js do end context 'in profile page' do - let(:visit_path) { profile_path } + before do + visit profile_path + end - it_behaves_like 'can search settings with feature flag check', 'Public Avatar', 'Main settings' + it_behaves_like 'can search settings', 'Public Avatar', 'Main settings' end context 'in preferences page' do diff --git a/spec/features/projects/active_tabs_spec.rb b/spec/features/projects/active_tabs_spec.rb index 86fe59f003f..9de43e7d18c 100644 --- a/spec/features/projects/active_tabs_spec.rb +++ b/spec/features/projects/active_tabs_spec.rb @@ -79,7 +79,7 @@ RSpec.describe 'Project active tab' do visit project_merge_requests_path(project) end - it_behaves_like 'page has active tab', 'Merge Requests' + it_behaves_like 'page has active tab', 'Merge requests' end context 'on project Wiki' do diff --git a/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb b/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb index a65a82fab43..1a368676a5e 100644 --- a/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb +++ b/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb @@ -44,17 +44,6 @@ RSpec.describe 'Blob button line permalinks (BlobLinePermalinkUpdater)', :js do expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(project_blob_path(project, tree_join(sha, path), anchor: ending_fragment))) end - it 'changes fragment hash if icon inside line number link is clicked' do - ending_fragment = "L7" - - visit_blob - - find("##{ending_fragment}").hover - find("##{ending_fragment} svg").click - - expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(project_blob_path(project, tree_join(sha, path), anchor: ending_fragment))) - end - it 'with initial fragment hash, changes fragment hash if line number clicked' do fragment = "L1" ending_fragment = "L5" @@ -94,17 +83,6 @@ RSpec.describe 'Blob button line permalinks (BlobLinePermalinkUpdater)', :js do expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(project_blame_path(project, tree_join('master', path), anchor: ending_fragment))) end - it 'changes fragment hash if icon inside line number link is clicked' do - ending_fragment = "L7" - - visit_blob - - find("##{ending_fragment}").hover - find("##{ending_fragment} svg").click - - expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(project_blame_path(project, tree_join('master', path), anchor: ending_fragment))) - end - it 'with initial fragment hash, changes fragment hash if line number clicked' do fragment = "L1" ending_fragment = "L5" diff --git a/spec/features/projects/blobs/user_views_pipeline_editor_button_spec.rb b/spec/features/projects/blobs/user_views_pipeline_editor_button_spec.rb new file mode 100644 index 00000000000..b872fa701c8 --- /dev/null +++ b/spec/features/projects/blobs/user_views_pipeline_editor_button_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User views pipeline editor button on root ci config file', :js do + include BlobSpecHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :public, :repository) } + + context "when the ci config is the root file" do + before do + project.add_developer(user) + sign_in(user) + end + + it 'shows the button to the Pipeline Editor' do + project.update!(ci_config_path: '.my-config.yml') + project.repository.create_file(user, project.ci_config_path_or_default, 'test', message: 'testing', branch_name: 'master') + visit project_blob_path(project, File.join('master', '.my-config.yml')) + + expect(page).to have_content('Pipeline Editor') + end + + it 'does not shows the Pipeline Editor button' do + project.repository.create_file(user, '.my-sub-config.yml', 'test', message: 'testing', branch_name: 'master') + visit project_blob_path(project, File.join('master', '.my-sub-config.yml')) + + expect(page).not_to have_content('Pipeline Editor') + end + end + + context "when user cannot collaborate" do + before do + sign_in(user) + end + it 'does not shows the Pipeline Editor button' do + visit project_blob_path(project, File.join('master', '.my-config.yml')) + expect(page).not_to have_content('Pipeline Editor') + end + end +end diff --git a/spec/features/projects/branches/user_creates_branch_spec.rb b/spec/features/projects/branches/user_creates_branch_spec.rb index 52c860bfe36..18d083f7d88 100644 --- a/spec/features/projects/branches/user_creates_branch_spec.rb +++ b/spec/features/projects/branches/user_creates_branch_spec.rb @@ -16,7 +16,7 @@ RSpec.describe "User creates branch", :js do end it "creates new branch" do - branch_name = "deploy_keys".freeze + branch_name = "deploy_keys" create_branch(branch_name) @@ -25,7 +25,7 @@ RSpec.describe "User creates branch", :js do context "when branch name is invalid" do it "does not create new branch" do - invalid_branch_name = "1.0 stable".freeze + invalid_branch_name = "1.0 stable" fill_in("branch_name", with: invalid_branch_name) page.find("body").click # defocus the branch_name input diff --git a/spec/features/projects/branches/user_deletes_branch_spec.rb b/spec/features/projects/branches/user_deletes_branch_spec.rb index c480c41709c..bebb4bb679b 100644 --- a/spec/features/projects/branches/user_deletes_branch_spec.rb +++ b/spec/features/projects/branches/user_deletes_branch_spec.rb @@ -9,12 +9,15 @@ RSpec.describe "User deletes branch", :js do before do project.add_developer(user) sign_in(user) - - visit(project_branches_path(project)) end it "deletes branch" do - fill_in("branch-search", with: "improve/awesome").native.send_keys(:enter) + visit(project_branches_path(project)) + + branch_search = find('input[data-testid="branch-search"]') + + branch_search.set('improve/awesome') + branch_search.native.send_keys(:enter) page.within(".js-branch-improve\\/awesome") do accept_alert { find(".btn-danger").click } diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb index 4bfe8852291..f805416b03d 100644 --- a/spec/features/projects/branches_spec.rb +++ b/spec/features/projects/branches_spec.rb @@ -88,8 +88,10 @@ RSpec.describe 'Branches' do it 'shows filtered branches', :js do visit project_branches_path(project) - fill_in 'branch-search', with: 'fix' - find('#branch-search').native.send_keys(:enter) + branch_search = find('input[data-testid="branch-search"]') + + branch_search.set('fix') + branch_search.native.send_keys(:enter) expect(page).to have_content('fix') expect(find('.all-branches')).to have_selector('li', count: 1) @@ -114,20 +116,24 @@ RSpec.describe 'Branches' do expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :updated_desc)) end - it 'sorts the branches by name' do + it 'sorts the branches by name', :js do visit project_branches_filtered_path(project, state: 'all') click_button "Last updated" # Open sorting dropdown - click_link "Name" + within '[data-testid="branches-dropdown"]' do + find('p', text: 'Name').click + end expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :name)) end - it 'sorts the branches by oldest updated' do + it 'sorts the branches by oldest updated', :js do visit project_branches_filtered_path(project, state: 'all') click_button "Last updated" # Open sorting dropdown - click_link "Oldest updated" + within '[data-testid="branches-dropdown"]' do + find('p', text: 'Oldest updated').click + end expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :updated_asc)) end @@ -145,8 +151,10 @@ RSpec.describe 'Branches' do it 'shows filtered branches', :js do visit project_branches_filtered_path(project, state: 'all') - fill_in 'branch-search', with: 'fix' - find('#branch-search').native.send_keys(:enter) + branch_search = find('input[data-testid="branch-search"]') + + branch_search.set('fix') + branch_search.native.send_keys(:enter) expect(page).to have_content('fix') expect(find('.all-branches')).to have_selector('li', count: 1) @@ -157,9 +165,10 @@ RSpec.describe 'Branches' do it 'removes branch after confirmation', :js do visit project_branches_filtered_path(project, state: 'all') - fill_in 'branch-search', with: 'fix' + branch_search = find('input[data-testid="branch-search"]') - find('#branch-search').native.send_keys(:enter) + branch_search.set('fix') + branch_search.native.send_keys(:enter) expect(page).to have_content('fix') expect(find('.all-branches')).to have_selector('li', count: 1) diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index d34dde6a8f2..8c497cded8e 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -119,7 +119,7 @@ RSpec.describe 'Gcp Cluster', :js do context 'when user disables the cluster' do before do page.find(:css, '.js-cluster-enable-toggle-area .js-project-feature-toggle').click - page.within('.js-cluster-integration-form') { click_button 'Save changes' } + page.within('.js-cluster-details-form') { click_button 'Save changes' } end it 'user sees the successful message' do diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb index 748eba558aa..5b60edbcf87 100644 --- a/spec/features/projects/clusters/user_spec.rb +++ b/spec/features/projects/clusters/user_spec.rb @@ -84,7 +84,7 @@ RSpec.describe 'User Cluster', :js do context 'when user disables the cluster' do before do page.find(:css, '.js-cluster-enable-toggle-area .js-project-feature-toggle').click - page.within('.js-cluster-integration-form') { click_button 'Save changes' } + page.within('.js-cluster-details-form') { click_button 'Save changes' } end it 'user sees the successful message' do diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb index 6da66989b09..6b03301aa74 100644 --- a/spec/features/projects/clusters_spec.rb +++ b/spec/features/projects/clusters_spec.rb @@ -58,7 +58,7 @@ RSpec.describe 'Clusters', :js do before do click_link 'default-cluster' fill_in 'cluster_environment_scope', with: 'production/*' - within '.js-cluster-integration-form' do + within '.js-cluster-details-form' do click_button 'Save changes' end end @@ -149,7 +149,7 @@ RSpec.describe 'Clusters', :js do before do click_link 'default-cluster' fill_in 'cluster_environment_scope', with: 'production/*' - within ".js-cluster-integration-form" do + within ".js-cluster-details-form" do click_button 'Save changes' end end diff --git a/spec/features/projects/commit/cherry_pick_spec.rb b/spec/features/projects/commit/cherry_pick_spec.rb index 489a90cc8fc..cd944436228 100644 --- a/spec/features/projects/commit/cherry_pick_spec.rb +++ b/spec/features/projects/commit/cherry_pick_spec.rb @@ -91,7 +91,7 @@ RSpec.describe 'Cherry-pick Commits', :js do context 'when the project is archived' do let(:project) { create(:project, :repository, :archived, namespace: user.namespace) } - it 'does not show the cherry-pick link' do + it 'does not show the cherry-pick button' do open_dropdown expect(page).not_to have_text("Cherry-pick") @@ -106,12 +106,15 @@ RSpec.describe 'Cherry-pick Commits', :js do end def open_dropdown - find('.header-action-buttons .dropdown').click + find(dropdown_selector).click end def open_modal open_dropdown - find('[data-testid="cherry-pick-commit-link"]').click + + page.within(dropdown_selector) do + click_button 'Cherry-pick' + end end def submit_cherry_pick(create_merge_request: false) @@ -121,6 +124,10 @@ RSpec.describe 'Cherry-pick Commits', :js do end end + def dropdown_selector + '[data-testid="commit-options-dropdown"]' + end + def modal_selector '[data-testid="modal-commit"]' end diff --git a/spec/features/projects/commit/comments/user_edits_comments_spec.rb b/spec/features/projects/commit/comments/user_edits_comments_spec.rb index 787d8cdb02b..8ac15c9cb7f 100644 --- a/spec/features/projects/commit/comments/user_edits_comments_spec.rb +++ b/spec/features/projects/commit/comments/user_edits_comments_spec.rb @@ -19,7 +19,7 @@ RSpec.describe "User edits a comment on a commit", :js do end it "edits comment" do - new_comment_text = "+1 Awesome!".freeze + new_comment_text = "+1 Awesome!" page.within(".main-notes-list") do note = find(".note") diff --git a/spec/features/projects/commit/mini_pipeline_graph_spec.rb b/spec/features/projects/commit/mini_pipeline_graph_spec.rb index 7d206f76031..6de02556175 100644 --- a/spec/features/projects/commit/mini_pipeline_graph_spec.rb +++ b/spec/features/projects/commit/mini_pipeline_graph_spec.rb @@ -16,46 +16,28 @@ RSpec.describe 'Mini Pipeline Graph in Commit View', :js do let(:build) { create(:ci_build, pipeline: pipeline, status: :running) } - shared_examples 'shows ci icon and mini pipeline' do - before do - build.run - visit project_commit_path(project, project.commit.id) - end - - it 'display icon with status' do - expect(page).to have_selector('.ci-status-icon-running') - end - - it 'displays a mini pipeline graph' do - expect(page).to have_selector('.mr-widget-pipeline-graph') - - first('.mini-pipeline-graph-dropdown-toggle').click - - wait_for_requests - - page.within '.js-builds-dropdown-list' do - expect(page).to have_selector('.ci-status-icon-running') - expect(page).to have_content(build.stage) - end + before do + build.run + visit project_commit_path(project, project.commit.id) + end - build.drop - end + it 'display icon with status' do + expect(page).to have_selector('.ci-status-icon-running') end - context 'when ci_commit_pipeline_mini_graph_vue is disabled' do - before do - stub_feature_flags(ci_commit_pipeline_mini_graph_vue: false) - end + it 'displays a mini pipeline graph' do + expect(page).to have_selector('[data-testid="pipeline-mini-graph"]') - it_behaves_like 'shows ci icon and mini pipeline' - end + first('.mini-pipeline-graph-dropdown-toggle').click - context 'when ci_commit_pipeline_mini_graph_vue is enabled' do - before do - stub_feature_flags(ci_commit_pipeline_mini_graph_vue: true) + wait_for_requests + + page.within '.js-builds-dropdown-list' do + expect(page).to have_selector('.ci-status-icon-running') + expect(page).to have_content(build.stage) end - it_behaves_like 'shows ci icon and mini pipeline' + build.drop end end @@ -65,7 +47,7 @@ RSpec.describe 'Mini Pipeline Graph in Commit View', :js do end it 'does not display a mini pipeline graph' do - expect(page).not_to have_selector('.mr-widget-pipeline-graph') + expect(page).not_to have_selector('[data-testid="pipeline-mini-graph"]') end end end diff --git a/spec/features/projects/commit/user_reverts_commit_spec.rb b/spec/features/projects/commit/user_reverts_commit_spec.rb index 72c639a027e..ad327b86aa7 100644 --- a/spec/features/projects/commit/user_reverts_commit_spec.rb +++ b/spec/features/projects/commit/user_reverts_commit_spec.rb @@ -62,10 +62,10 @@ RSpec.describe 'User reverts a commit', :js do context 'when the project is archived' do let(:project) { create(:project, :repository, :archived, namespace: user.namespace) } - it 'does not show the revert link' do + it 'does not show the revert button' do open_dropdown - expect(page).not_to have_link('Revert') + expect(page).not_to have_button('Revert') end end end @@ -75,17 +75,24 @@ RSpec.describe 'User reverts a commit', :js do page.within(modal_selector) do uncheck('create_merge_request') unless create_merge_request - click_button('Revert') + click_button 'Revert' end end def open_dropdown - find('.header-action-buttons .dropdown').click + find(dropdown_selector).click end def open_modal open_dropdown - find('[data-testid="revert-commit-link"]').click + + page.within(dropdown_selector) do + click_button 'Revert' + end + end + + def dropdown_selector + '[data-testid="commit-options-dropdown"]' end def modal_selector diff --git a/spec/features/projects/commits/user_browses_commits_spec.rb b/spec/features/projects/commits/user_browses_commits_spec.rb index 4894e2b7f3e..76162fb800a 100644 --- a/spec/features/projects/commits/user_browses_commits_spec.rb +++ b/spec/features/projects/commits/user_browses_commits_spec.rb @@ -20,9 +20,14 @@ RSpec.describe 'User browses commits' do .and have_content('Side-by-side') end - it 'fill commit sha when click new tag from commit page' do + it 'fill commit sha when click new tag from commit page', :js do + dropdown_selector = '[data-testid="commit-options-dropdown"]' visit project_commit_path(project, sample_commit.id) - click_link 'Tag' + find(dropdown_selector).click + + page.within(dropdown_selector) do + click_link 'Tag' + end expect(page).to have_selector("input[value='#{sample_commit.id}']", visible: false) end diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb index c94247f65d2..ab82a4750d3 100644 --- a/spec/features/projects/features_visibility_spec.rb +++ b/spec/features/projects/features_visibility_spec.rb @@ -134,7 +134,7 @@ RSpec.describe 'Edit Project Settings' do it 'renders 200 if user is member of group' do group = create(:group) project.group = group - project.save + project.save! group.add_owner(member) diff --git a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb index 55b9f38d8e7..b0ccb5fca94 100644 --- a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb +++ b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb @@ -5,10 +5,14 @@ require 'spec_helper' RSpec.describe 'Projects > Files > User wants to add a .gitlab-ci.yml file', :js do include Spec::Support::Helpers::Features::EditorLiteSpecHelpers + let(:params) { {} } + let(:filename) { '.gitlab-ci.yml' } + + let_it_be(:project) { create(:project, :repository) } + before do - project = create(:project, :repository) sign_in project.owner - visit project_new_blob_path(project, 'master', file_name: '.gitlab-ci.yml') + visit project_new_blob_path(project, 'master', file_name: filename, **params) end it 'user can pick a template from the dropdown' do @@ -29,4 +33,38 @@ RSpec.describe 'Projects > Files > User wants to add a .gitlab-ci.yml file', :js expect(editor_get_value).to have_content('This file is a template, and might need editing before it works on your project') expect(editor_get_value).to have_content('jekyll build -d test') end + + context 'when template param is provided' do + let(:params) { { template: 'Jekyll' } } + + it 'uses the given template' do + wait_for_requests + + expect(page).to have_css('.gitlab-ci-yml-selector .dropdown-toggle-text', text: 'Apply a template') + expect(editor_get_value).to have_content('This file is a template, and might need editing before it works on your project') + expect(editor_get_value).to have_content('jekyll build -d test') + end + end + + context 'when provided template param is not a valid template name' do + let(:params) { { template: 'non-existing-template' } } + + it 'leaves the editor empty' do + wait_for_requests + + expect(page).to have_css('.gitlab-ci-yml-selector .dropdown-toggle-text', text: 'Apply a template') + expect(editor_get_value).to have_content('') + end + end + + context 'when template is not available for the given file' do + let(:filename) { 'Dockerfile' } + let(:params) { { template: 'Jekyll' } } + + it 'leaves the editor empty' do + wait_for_requests + + expect(editor_get_value).to have_content('') + end + end end diff --git a/spec/features/projects/files/user_creates_directory_spec.rb b/spec/features/projects/files/user_creates_directory_spec.rb index f2074c78dba..46b93d738e1 100644 --- a/spec/features/projects/files/user_creates_directory_spec.rb +++ b/spec/features/projects/files/user_creates_directory_spec.rb @@ -77,7 +77,7 @@ RSpec.describe 'Projects > Files > User creates a directory', :js do it 'creates the directory in the new branch and redirect to the merge request' do expect(page).to have_content('new-feature') expect(page).to have_content('The directory has been successfully created') - expect(page).to have_content('New Merge Request') + expect(page).to have_content('New merge request') expect(page).to have_content('From new-feature into master') expect(page).to have_content('Add new directory') diff --git a/spec/features/projects/files/user_uploads_files_spec.rb b/spec/features/projects/files/user_uploads_files_spec.rb index 944d08df3f3..54e816d3d13 100644 --- a/spec/features/projects/files/user_uploads_files_spec.rb +++ b/spec/features/projects/files/user_uploads_files_spec.rb @@ -3,8 +3,6 @@ require 'spec_helper' RSpec.describe 'Projects > Files > User uploads files' do - include DropzoneHelper - let(:user) { create(:user) } let(:project) { create(:project, :repository, name: 'Shop', creator: user) } let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } @@ -17,36 +15,17 @@ RSpec.describe 'Projects > Files > User uploads files' do context 'when a user has write access' do before do visit(project_tree_path(project)) - end - - include_examples 'it uploads and commit a new text file' - - include_examples 'it uploads and commit a new image file' - it 'uploads a file to a sub-directory', :js do - click_link 'files' - - page.within('.repo-breadcrumb') do - expect(page).to have_content('files') - end - - find('.add-to-tree').click - click_link('Upload file') - drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt')) + wait_for_requests + end - page.within('#modal-upload-blob') do - fill_in(:commit_message, with: 'New commit message') - end + include_examples 'it uploads and commits a new text file' - click_button('Upload file') + include_examples 'it uploads and commits a new image file' - expect(page).to have_content('New commit message') + include_examples 'it uploads and commits a new pdf file' - page.within('.repo-breadcrumb') do - expect(page).to have_content('files') - expect(page).to have_content('doc_sample.txt') - end - end + include_examples 'it uploads a file to a sub-directory' end context 'when a user does not have write access' do @@ -56,6 +35,6 @@ RSpec.describe 'Projects > Files > User uploads files' do visit(project_tree_path(project2)) end - include_examples 'it uploads and commit a new file to a forked project' + include_examples 'it uploads and commits a new file to a forked project' end end diff --git a/spec/features/projects/fork_spec.rb b/spec/features/projects/fork_spec.rb index 7abbd207b24..2b7ea70fe5a 100644 --- a/spec/features/projects/fork_spec.rb +++ b/spec/features/projects/fork_spec.rb @@ -76,7 +76,7 @@ RSpec.describe 'Project fork' do let(:forking_access_level) { ProjectFeature::PRIVATE } before do - project.update(visibility_level: Gitlab::VisibilityLevel::INTERNAL) + project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) end context 'user is not a team member' do @@ -118,6 +118,50 @@ RSpec.describe 'Project fork' do it_behaves_like 'fork button on project page' it_behaves_like 'create fork page', 'Fork project' + context 'fork form', :js do + let(:group) { create(:group) } + let(:user) { create(:group_member, :maintainer, user: create(:user), group: group ).user } + + def submit_form + select(group.name) + click_button 'Fork project' + end + + it 'forks the project', :sidekiq_might_not_need_inline do + visit new_project_fork_path(project) + submit_form + + expect(page).to have_content 'Forked from' + end + + it 'shows the new forked project on the forks page' do + visit new_project_fork_path(project) + submit_form + wait_for_requests + + visit project_forks_path(project) + + page.within('.js-projects-list-holder') do + expect(page).to have_content("#{group.name} / #{project.name}") + end + end + + it 'shows the filled in info forked project on the forks page' do + fork_name = 'some-name' + visit new_project_fork_path(project) + fill_in('fork-name', with: fork_name, fill_options: { clear: :backspace }) + fill_in('fork-slug', with: fork_name, fill_options: { clear: :backspace }) + submit_form + wait_for_requests + + visit project_forks_path(project) + + page.within('.js-projects-list-holder') do + expect(page).to have_content("#{group.name} / #{fork_name}") + end + end + end + context 'with fork_project_form feature flag disabled' do before do stub_feature_flags(fork_project_form: false) @@ -164,7 +208,7 @@ RSpec.describe 'Project fork' do expect(page).to have_content(/new merge request/i) page.within '.nav-sidebar' do - first(:link, 'Merge Requests').click + first(:link, 'Merge requests').click end expect(page).to have_content(/new merge request/i) diff --git a/spec/features/projects/jobs/permissions_spec.rb b/spec/features/projects/jobs/permissions_spec.rb index e87880d74b1..140d5dee270 100644 --- a/spec/features/projects/jobs/permissions_spec.rb +++ b/spec/features/projects/jobs/permissions_spec.rb @@ -12,6 +12,8 @@ RSpec.describe 'Project Jobs Permissions' do let_it_be(:job) { create(:ci_build, :running, :coverage, :trace_artifact, pipeline: pipeline) } before do + stub_feature_flags(jobs_table_vue: false) + sign_in(user) project.enable_ci diff --git a/spec/features/projects/jobs/user_browses_jobs_spec.rb b/spec/features/projects/jobs/user_browses_jobs_spec.rb index 5abebf2320e..dbcd7b5caf5 100644 --- a/spec/features/projects/jobs/user_browses_jobs_spec.rb +++ b/spec/features/projects/jobs/user_browses_jobs_spec.rb @@ -9,6 +9,7 @@ RSpec.describe 'User browses jobs' do let(:user) { create(:user) } before do + stub_feature_flags(jobs_table_vue: false) project.add_maintainer(user) project.enable_ci project.update_attribute(:build_coverage_regex, /Coverage (\d+)%/) @@ -24,14 +25,6 @@ RSpec.describe 'User browses jobs' do end end - it 'shows the "CI Lint" button' do - page.within('.nav-controls') do - ci_lint_tool_link = page.find_link('CI Lint') - - expect(ci_lint_tool_link[:href]).to end_with(project_ci_lint_path(project)) - end - end - context 'with a failed job' do let!(:build) { create(:ci_build, :coverage, :failed, pipeline: pipeline) } diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 7811394b541..18a6ad12240 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -20,6 +20,7 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do end before do + stub_feature_flags(jobs_table_vue: false) project.add_role(user, user_access_level) sign_in(user) end @@ -32,7 +33,7 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do it 'shows the empty state page' do expect(page).to have_content('Use jobs to automate your tasks') - expect(page).to have_link('Create CI/CD configuration file', href: project.present(current_user: user).add_ci_yml_path) + expect(page).to have_link('Create CI/CD configuration file', href: project_ci_pipeline_editor_path(project)) end end @@ -1057,7 +1058,7 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do before do job.run! job.cancel! - project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) sign_out(:user) sign_in(create(:user)) diff --git a/spec/features/projects/labels/user_removes_labels_spec.rb b/spec/features/projects/labels/user_removes_labels_spec.rb index 217f86b92cf..11d73a56965 100644 --- a/spec/features/projects/labels/user_removes_labels_spec.rb +++ b/spec/features/projects/labels/user_removes_labels_spec.rb @@ -18,17 +18,17 @@ RSpec.describe "User removes labels" do visit(project_labels_path(project)) end - it "removes label" do + it "removes label", :js do page.within(".other-labels") do page.first(".label-list-item") do first('.js-label-options-dropdown').click - first(".remove-row").click + first('.js-delete-label-modal-button').click end + end - expect(page).to have_content("#{label.title} will be permanently deleted from #{project.name}. This cannot be undone.") + expect(page).to have_content("#{label.title} will be permanently deleted from #{project.name}. This cannot be undone.") - first(:link, "Delete label").click - end + first(:link, "Delete label").click expect(page).to have_content("Label was removed").and have_no_content(label.title) end diff --git a/spec/features/projects/members/list_spec.rb b/spec/features/projects/members/list_spec.rb index 0830585da9b..384b8ae9929 100644 --- a/spec/features/projects/members/list_spec.rb +++ b/spec/features/projects/members/list_spec.rb @@ -175,7 +175,7 @@ RSpec.describe 'Project members list', :js do click_on 'Invite members' page.within '#invite-members-modal' do - fill_in 'Search for members to invite', with: id + fill_in 'Select members or type email addresses', with: id wait_for_requests click_button id diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb index a339130ee3c..7073741a92d 100644 --- a/spec/features/projects/members/user_requests_access_spec.rb +++ b/spec/features/projects/members/user_requests_access_spec.rb @@ -13,7 +13,7 @@ RSpec.describe 'Projects > Members > User requests access', :js do end it 'request access feature is disabled' do - project.update(request_access_enabled: false) + project.update!(request_access_enabled: false) visit project_path(project) expect(page).not_to have_content 'Request Access' diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb index 9547ba8a390..93bbabcc3f8 100644 --- a/spec/features/projects/merge_request_button_spec.rb +++ b/spec/features/projects/merge_request_button_spec.rb @@ -14,7 +14,9 @@ RSpec.describe 'Merge Request button' do it 'does not show Create merge request button' do visit url - expect(page).not_to have_link(label) + within '.content-wrapper' do + expect(page).not_to have_link(label) + end end end diff --git a/spec/features/projects/navbar_spec.rb b/spec/features/projects/navbar_spec.rb index 4ff3827b240..7dc3ee63669 100644 --- a/spec/features/projects/navbar_spec.rb +++ b/spec/features/projects/navbar_spec.rb @@ -13,6 +13,8 @@ RSpec.describe 'Project navbar' do before do insert_package_nav(_('Operations')) + insert_infrastructure_registry_nav + stub_config(registry: { enabled: false }) project.add_maintainer(user) sign_in(user) @@ -60,7 +62,7 @@ RSpec.describe 'Project navbar' do before do stub_config(registry: { enabled: true }) - insert_container_nav(_('Operations')) + insert_container_nav visit project_path(project) end diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index ec34640bd00..7119039d5ff 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -12,6 +12,72 @@ RSpec.describe 'New project', :js do sign_in(user) end + context 'new repo experiment', :experiment do + it 'when in control renders "project"' do + stub_experiments(new_repo: :control) + + visit new_project_path + + find('li.header-new.dropdown').click + + page.within('li.header-new.dropdown') do + expect(page).to have_selector('a', text: 'New project') + expect(page).to have_no_selector('a', text: 'New project/repository') + end + + expect(page).to have_selector('.blank-state-title', text: 'Create blank project') + expect(page).to have_no_selector('.blank-state-title', text: 'Create blank project/repository') + end + + it 'when in candidate renders "project/repository"' do + stub_experiments(new_repo: :candidate) + + visit new_project_path + + find('li.header-new.dropdown').click + + page.within('li.header-new.dropdown') do + expect(page).to have_selector('a', text: 'New project/repository') + end + + expect(page).to have_selector('.blank-state-title', text: 'Create blank project/repository') + end + + context 'with combined_menu feature disabled' do + before do + stub_feature_flags(combined_menu: false) + end + + it 'when in control it renders "project" in the new projects dropdown' do + stub_experiments(new_repo: :control) + + visit new_project_path + + find('#nav-projects-dropdown').click + + page.within('#nav-projects-dropdown') do + expect(page).to have_selector('a', text: 'Create blank project') + expect(page).to have_selector('a', text: 'Import project') + expect(page).to have_no_selector('a', text: 'Create blank project/repository') + expect(page).to have_no_selector('a', text: 'Import project/repository') + end + end + + it 'when in candidate it renders "project/repository" in the new projects dropdown' do + stub_experiments(new_repo: :candidate) + + visit new_project_path + + find('#nav-projects-dropdown').click + + page.within('#nav-projects-dropdown') do + expect(page).to have_selector('a', text: 'Create blank project/repository') + expect(page).to have_selector('a', text: 'Import project/repository') + end + end + end + end + it 'shows a message if multiple levels are restricted' do Gitlab::CurrentSettings.update!( restricted_visibility_levels: [Gitlab::VisibilityLevel::PRIVATE, Gitlab::VisibilityLevel::INTERNAL] diff --git a/spec/features/projects/pages/user_edits_settings_spec.rb b/spec/features/projects/pages/user_edits_settings_spec.rb index 6156b5243de..412ba17cf20 100644 --- a/spec/features/projects/pages/user_edits_settings_spec.rb +++ b/spec/features/projects/pages/user_edits_settings_spec.rb @@ -175,7 +175,6 @@ RSpec.describe 'Pages edits pages settings', :js do expect(page).not_to have_field(:project_pages_https_only) expect(page).not_to have_content('Force HTTPS (requires valid certificates)') - expect(page).to have_button('Save') end end end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 94800717677..4a0581bb5cf 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -566,7 +566,7 @@ RSpec.describe 'Pipeline', :js do end before do - pipeline.update(user: user) + pipeline.update!(user: user) end it 'shows the pipeline information' do @@ -628,7 +628,7 @@ RSpec.describe 'Pipeline', :js do context 'when user does not have access to read jobs' do before do - project.update(public_builds: false) + project.update!(public_builds: false) end describe 'GET /:project/-/pipelines/:id' do @@ -709,9 +709,9 @@ RSpec.describe 'Pipeline', :js do end end - it 'displays the PipelineSchedule in an active state' do + it 'displays the PipelineSchedule in an inactive state' do visit project_pipeline_schedules_path(project) - page.click_link('Active') + page.click_link('Inactive') expect(page).to have_selector('table.ci-table > tbody > tr > td', text: 'blocked user schedule') end @@ -1185,7 +1185,7 @@ RSpec.describe 'Pipeline', :js do let(:role) { :guest } before do - project.update(public_builds: false) + project.update!(public_builds: false) end context 'when accessing failed jobs page' do diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 9037aa5c9a8..e375bc10dbf 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -14,7 +14,6 @@ RSpec.describe 'Pipelines', :js do sign_in(user) stub_feature_flags(graphql_pipeline_details: false) stub_feature_flags(graphql_pipeline_details_users: false) - stub_feature_flags(new_pipelines_table: false) project.add_developer(user) project.update!(auto_devops_attributes: { enabled: false }) @@ -94,12 +93,12 @@ RSpec.describe 'Pipelines', :js do wait_for_requests end - it 'renders run pipeline link' do - expect(page).to have_link('Run Pipeline') + it 'renders "CI lint" link' do + expect(page).to have_link('CI lint') end - it 'renders ci lint link' do - expect(page).to have_link('CI Lint') + it 'renders "Run pipeline" link' do + expect(page).to have_link('Run pipeline') end end @@ -534,7 +533,7 @@ RSpec.describe 'Pipelines', :js do end it 'renders a mini pipeline graph' do - expect(page).to have_selector('[data-testid="widget-mini-pipeline-graph"]') + expect(page).to have_selector('[data-testid="pipeline-mini-graph"]') expect(page).to have_selector(dropdown_selector) end @@ -677,7 +676,7 @@ RSpec.describe 'Pipelines', :js do end it 'creates a new pipeline' do - expect { click_on 'Run Pipeline' } + expect { click_on 'Run pipeline' } .to change { Ci::Pipeline.count }.by(1) expect(Ci::Pipeline.last).to be_web @@ -690,7 +689,7 @@ RSpec.describe 'Pipelines', :js do fill_in "Input variable value", with: "value" end - expect { click_on 'Run Pipeline' } + expect { click_on 'Run pipeline' } .to change { Ci::Pipeline.count }.by(1) expect(Ci::Pipeline.last.variables.map { |var| var.slice(:key, :secret_value) }) @@ -701,7 +700,7 @@ RSpec.describe 'Pipelines', :js do context 'without gitlab-ci.yml' do before do - click_on 'Run Pipeline' + click_on 'Run pipeline' end it { expect(page).to have_content('Missing CI config file') } @@ -714,44 +713,13 @@ RSpec.describe 'Pipelines', :js do click_link 'master' end - expect { click_on 'Run Pipeline' } + expect { click_on 'Run pipeline' } .to change { Ci::Pipeline.count }.by(1) end end end end - describe 'Run Pipelines' do - let(:project) { create(:project, :repository) } - - before do - stub_feature_flags(new_pipeline_form: false) - visit new_project_pipeline_path(project) - end - - describe 'new pipeline page' do - it 'has field to add a new pipeline' do - expect(page).to have_selector('.js-branch-select') - expect(find('.js-branch-select')).to have_content project.default_branch - expect(page).to have_content('Run for') - end - end - - describe 'find pipelines' do - it 'shows filtered pipelines', :js do - click_button project.default_branch - - page.within '.dropdown-menu' do - find('.dropdown-input-field').native.send_keys('fix') - - page.within '.dropdown-content' do - expect(page).to have_content('fix') - end - end - end - end - end - describe 'Reset runner caches' do let(:project) { create(:project, :repository) } @@ -762,17 +730,17 @@ RSpec.describe 'Pipelines', :js do end it 'has a clear caches button' do - expect(page).to have_button 'Clear Runner Caches' + expect(page).to have_button 'Clear runner caches' end describe 'user clicks the button' do context 'when project already has jobs_cache_index' do before do - project.update(jobs_cache_index: 1) + project.update!(jobs_cache_index: 1) end it 'increments jobs_cache_index' do - click_button 'Clear Runner Caches' + click_button 'Clear runner caches' wait_for_requests expect(page.find('.flash-notice')).to have_content 'Project cache successfully reset.' end @@ -780,7 +748,7 @@ RSpec.describe 'Pipelines', :js do context 'when project does not have jobs_cache_index' do it 'sets jobs_cache_index to 1' do - click_button 'Clear Runner Caches' + click_button 'Clear runner caches' wait_for_requests expect(page.find('.flash-notice')).to have_content 'Project cache successfully reset.' end @@ -788,6 +756,37 @@ RSpec.describe 'Pipelines', :js do end end + describe 'Run Pipelines' do + let(:project) { create(:project, :repository) } + + before do + stub_feature_flags(new_pipeline_form: false) + visit new_project_pipeline_path(project) + end + + describe 'new pipeline page' do + it 'has field to add a new pipeline' do + expect(page).to have_selector('.js-branch-select') + expect(find('.js-branch-select')).to have_content project.default_branch + expect(page).to have_content('Run for') + end + end + + describe 'find pipelines' do + it 'shows filtered pipelines', :js do + click_button project.default_branch + + page.within '.dropdown-menu' do + find('.dropdown-input-field').native.send_keys('fix') + + page.within '.dropdown-content' do + expect(page).to have_content('fix') + end + end + end + end + end + describe 'Empty State' do let(:project) { create(:project, :repository) } diff --git a/spec/features/projects/releases/user_views_edit_release_spec.rb b/spec/features/projects/releases/user_views_edit_release_spec.rb index bb54b6be9c4..024c0a227c5 100644 --- a/spec/features/projects/releases/user_views_edit_release_spec.rb +++ b/spec/features/projects/releases/user_views_edit_release_spec.rb @@ -37,7 +37,7 @@ RSpec.describe 'User edits Release', :js do end it 'renders the edit Release form' do - expect(page).to have_content('Releases are based on Git tags. We recommend tags that use semantic versioning, for example v1.0, v2.0-pre.') + expect(page).to have_content('Releases are based on Git tags. We recommend tags that use semantic versioning, for example v1.0.0, v2.1.0-pre.') expect(find_field('Tag name', disabled: true).value).to eq(release.tag) expect(find_field('Release title').value).to eq(release.name) diff --git a/spec/features/projects/releases/user_views_release_spec.rb b/spec/features/projects/releases/user_views_release_spec.rb index 186122536ce..4410f345e56 100644 --- a/spec/features/projects/releases/user_views_release_spec.rb +++ b/spec/features/projects/releases/user_views_release_spec.rb @@ -5,7 +5,6 @@ require 'spec_helper' RSpec.describe 'User views Release', :js do let(:project) { create(:project, :repository) } let(:user) { create(:user) } - let(:graphql_feature_flag) { true } let(:release) do create(:release, @@ -15,8 +14,6 @@ RSpec.describe 'User views Release', :js do end before do - stub_feature_flags(graphql_individual_release_page: graphql_feature_flag) - project.add_developer(user) sign_in(user) @@ -26,35 +23,23 @@ RSpec.describe 'User views Release', :js do it_behaves_like 'page meta description', 'Lorem ipsum dolor sit amet' - shared_examples 'release page' do - it 'renders the breadcrumbs' do - within('.breadcrumbs') do - expect(page).to have_content("#{project.creator.name} #{project.name} Releases #{release.name}") - - expect(page).to have_link(project.creator.name, href: user_path(project.creator)) - expect(page).to have_link(project.name, href: project_path(project)) - expect(page).to have_link('Releases', href: project_releases_path(project)) - expect(page).to have_link(release.name, href: project_release_path(project, release)) - end - end + it 'renders the breadcrumbs' do + within('.breadcrumbs') do + expect(page).to have_content("#{project.creator.name} #{project.name} Releases #{release.name}") - it 'renders the release details' do - within('.release-block') do - expect(page).to have_content(release.name) - expect(page).to have_content(release.tag) - expect(page).to have_content(release.commit.short_id) - expect(page).to have_content('Lorem ipsum dolor sit amet') - end + expect(page).to have_link(project.creator.name, href: user_path(project.creator)) + expect(page).to have_link(project.name, href: project_path(project)) + expect(page).to have_link('Releases', href: project_releases_path(project)) + expect(page).to have_link(release.name, href: project_release_path(project, release)) end end - describe 'when the graphql_individual_release_page feature flag is enabled' do - it_behaves_like 'release page' - end - - describe 'when the graphql_individual_release_page feature flag is disabled' do - let(:graphql_feature_flag) { false } - - it_behaves_like 'release page' + it 'renders the release details' do + within('.release-block') do + expect(page).to have_content(release.name) + expect(page).to have_content(release.tag) + expect(page).to have_content(release.commit.short_id) + expect(page).to have_content('Lorem ipsum dolor sit amet') + end end end diff --git a/spec/features/projects/remote_mirror_spec.rb b/spec/features/projects/remote_mirror_spec.rb index 26d27c914cc..7bbffe627f6 100644 --- a/spec/features/projects/remote_mirror_spec.rb +++ b/spec/features/projects/remote_mirror_spec.rb @@ -15,7 +15,7 @@ RSpec.describe 'Project remote mirror', :feature do context 'when last_error is present but last_update_at is not' do it 'renders error message without timstamp' do - remote_mirror.update(last_error: 'Some new error', last_update_at: nil) + remote_mirror.update!(last_error: 'Some new error', last_update_at: nil) visit project_mirror_path(project) @@ -25,7 +25,7 @@ RSpec.describe 'Project remote mirror', :feature do context 'when last_error and last_update_at are present' do it 'renders error message with timestamp' do - remote_mirror.update(last_error: 'Some new error', last_update_at: Time.now - 5.minutes) + remote_mirror.update!(last_error: 'Some new error', last_update_at: Time.now - 5.minutes) visit project_mirror_path(project) diff --git a/spec/features/projects/services/disable_triggers_spec.rb b/spec/features/projects/services/disable_triggers_spec.rb index b3a3d7f0622..d9e200cf563 100644 --- a/spec/features/projects/services/disable_triggers_spec.rb +++ b/spec/features/projects/services/disable_triggers_spec.rb @@ -12,10 +12,10 @@ RSpec.describe 'Disable individual triggers', :js do end context 'service has multiple supported events' do - let(:service_name) { 'HipChat' } + let(:service_name) { 'Jenkins' } it 'shows trigger checkboxes' do - event_count = HipchatService.supported_events.count + event_count = JenkinsService.supported_events.count expect(page).to have_content "Trigger" expect(page).to have_css(checkbox_selector, visible: :all, count: event_count) diff --git a/spec/features/projects/services/user_activates_asana_spec.rb b/spec/features/projects/services/user_activates_asana_spec.rb index e95e7e89fc2..cf2290383e8 100644 --- a/spec/features/projects/services/user_activates_asana_spec.rb +++ b/spec/features/projects/services/user_activates_asana_spec.rb @@ -7,7 +7,7 @@ RSpec.describe 'User activates Asana' do it 'activates service', :js do visit_project_integration('Asana') - fill_in('Api key', with: 'verySecret') + fill_in('API key', with: 'verySecret') fill_in('Restrict to branch', with: 'verySecret') click_test_then_save_integration diff --git a/spec/features/projects/services/user_activates_atlassian_bamboo_ci_spec.rb b/spec/features/projects/services/user_activates_atlassian_bamboo_ci_spec.rb index a9d91454670..91db375be3a 100644 --- a/spec/features/projects/services/user_activates_atlassian_bamboo_ci_spec.rb +++ b/spec/features/projects/services/user_activates_atlassian_bamboo_ci_spec.rb @@ -10,19 +10,20 @@ RSpec.describe 'User activates Atlassian Bamboo CI' do end it 'activates service', :js do - visit_project_integration('Atlassian Bamboo CI') - fill_in('Bamboo url', with: 'http://bamboo.example.com') + visit_project_integration('Atlassian Bamboo') + fill_in('Bamboo URL', with: 'http://bamboo.example.com') fill_in('Build key', with: 'KEY') fill_in('Username', with: 'user') fill_in('Password', with: 'verySecret') click_test_then_save_integration(expect_test_to_fail: false) - expect(page).to have_content('Atlassian Bamboo CI settings saved and active.') + expect(page).to have_content('Atlassian Bamboo settings saved and active.') # Password field should not be filled in. - click_link('Atlassian Bamboo CI') + click_link('Atlassian Bamboo') - expect(find_field('Enter new Password').value).to be_blank + expect(find_field('Enter new password').value).to be_blank + expect(page).to have_content('Leave blank to use your current password') end end diff --git a/spec/features/projects/services/user_activates_hipchat_spec.rb b/spec/features/projects/services/user_activates_hipchat_spec.rb deleted file mode 100644 index cffb780e05d..00000000000 --- a/spec/features/projects/services/user_activates_hipchat_spec.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'User activates HipChat', :js do - include_context 'project service activation' - - context 'with standard settings' do - before do - stub_request(:post, /.*api.hipchat.com.*/) - end - - it 'activates service' do - visit_project_integration('HipChat') - fill_in('Room', with: 'gitlab') - fill_in('Token', with: 'verySecret') - - click_test_then_save_integration(expect_test_to_fail: false) - - expect(page).to have_content('HipChat settings saved and active.') - end - end - - context 'with custom settings' do - before do - stub_request(:post, /.*chat.example.com.*/) - end - - it 'activates service' do - visit_project_integration('HipChat') - fill_in('Room', with: 'gitlab_custom') - fill_in('Token', with: 'secretCustom') - fill_in('Server', with: 'https://chat.example.com') - - click_test_then_save_integration(expect_test_to_fail: false) - - expect(page).to have_content('HipChat settings saved and active.') - end - end -end diff --git a/spec/features/projects/services/user_activates_jetbrains_teamcity_ci_spec.rb b/spec/features/projects/services/user_activates_jetbrains_teamcity_ci_spec.rb index 72881054c6c..17bfe8fc1e2 100644 --- a/spec/features/projects/services/user_activates_jetbrains_teamcity_ci_spec.rb +++ b/spec/features/projects/services/user_activates_jetbrains_teamcity_ci_spec.rb @@ -10,16 +10,16 @@ RSpec.describe 'User activates JetBrains TeamCity CI' do end it 'activates service', :js do - visit_project_integration('JetBrains TeamCity CI') + visit_project_integration('JetBrains TeamCity') check('Push') check('Merge Request') - fill_in('Teamcity url', with: 'http://teamcity.example.com') + fill_in('TeamCity server URL', with: 'http://teamcity.example.com') fill_in('Build type', with: 'GitlabTest_Build') fill_in('Username', with: 'user') fill_in('Password', with: 'verySecret') click_test_then_save_integration(expect_test_to_fail: false) - expect(page).to have_content('JetBrains TeamCity CI settings saved and active.') + expect(page).to have_content('JetBrains TeamCity settings saved and active.') end end diff --git a/spec/features/projects/services/user_activates_jira_spec.rb b/spec/features/projects/services/user_activates_jira_spec.rb index 85afc54be48..10f84aae93f 100644 --- a/spec/features/projects/services/user_activates_jira_spec.rb +++ b/spec/features/projects/services/user_activates_jira_spec.rb @@ -6,12 +6,13 @@ RSpec.describe 'User activates Jira', :js do include_context 'project service activation' include_context 'project service Jira context' + before do + stub_request(:get, test_url).to_return(body: { key: 'value' }.to_json) + end + describe 'user tests Jira Service' do context 'when Jira connection test succeeds' do before do - server_info = { key: 'value' }.to_json - stub_request(:get, test_url).with(basic_auth: %w(username password)).to_return(body: server_info) - visit_project_integration('Jira') fill_form click_test_then_save_integration(expect_test_to_fail: false) @@ -81,4 +82,68 @@ RSpec.describe 'User activates Jira', :js do end end end + + describe 'issue transition settings' do + it 'using custom transitions' do + visit_project_integration('Jira') + + expect(page).to have_field('Enable Jira transitions', checked: false) + + check 'Enable Jira transitions' + + expect(page).to have_field('Move to Done', checked: true) + + fill_form + choose 'Use custom transitions' + click_save_integration + + within '[data-testid="issue-transition-mode"]' do + expect(page).to have_content('This field is required.') + end + + fill_in 'service[jira_issue_transition_id]', with: '1, 2, 3' + click_save_integration + + expect(page).to have_content('Jira settings saved and active.') + expect(project.reload.jira_service.data_fields).to have_attributes( + jira_issue_transition_automatic: false, + jira_issue_transition_id: '1, 2, 3' + ) + end + + it 'using automatic transitions' do + create(:jira_service, project: project, jira_issue_transition_automatic: false, jira_issue_transition_id: '1, 2, 3') + visit_project_integration('Jira') + + expect(page).to have_field('Enable Jira transitions', checked: true) + expect(page).to have_field('Use custom transitions', checked: true) + expect(page).to have_field('service[jira_issue_transition_id]', with: '1, 2, 3') + + choose 'Move to Done' + click_save_integration + + expect(page).to have_content('Jira settings saved and active.') + expect(project.reload.jira_service.data_fields).to have_attributes( + jira_issue_transition_automatic: true, + jira_issue_transition_id: '' + ) + end + + it 'disabling issue transitions' do + create(:jira_service, project: project, jira_issue_transition_automatic: true, jira_issue_transition_id: '1, 2, 3') + visit_project_integration('Jira') + + expect(page).to have_field('Enable Jira transitions', checked: true) + expect(page).to have_field('Move to Done', checked: true) + + uncheck 'Enable Jira transitions' + click_save_integration + + expect(page).to have_content('Jira settings saved and active.') + expect(project.reload.jira_service.data_fields).to have_attributes( + jira_issue_transition_automatic: false, + jira_issue_transition_id: '' + ) + end + end end 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 88812fc188b..54a501e89a2 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 @@ -15,7 +15,7 @@ RSpec.describe 'Set up Mattermost slash commands', :js do let(:mattermost_enabled) { true } it 'shows a help message' do - expect(page).to have_content("This service allows users to perform common") + expect(page).to have_content("Use this service to perform common") end it 'shows a token placeholder' do diff --git a/spec/features/projects/services/user_activates_pushover_spec.rb b/spec/features/projects/services/user_activates_pushover_spec.rb index 3cfd069032a..97003ab7c2a 100644 --- a/spec/features/projects/services/user_activates_pushover_spec.rb +++ b/spec/features/projects/services/user_activates_pushover_spec.rb @@ -11,7 +11,7 @@ RSpec.describe 'User activates Pushover' do it 'activates service', :js do visit_project_integration('Pushover') - fill_in('Api key', with: 'verySecret') + fill_in('API key', with: 'verySecret') fill_in('User key', with: 'verySecret') fill_in('Device', with: 'myDevice') select('High Priority', from: 'Priority') diff --git a/spec/features/projects/services/user_activates_slack_notifications_spec.rb b/spec/features/projects/services/user_activates_slack_notifications_spec.rb index 2a880e05e0f..0cba1ee1c4c 100644 --- a/spec/features/projects/services/user_activates_slack_notifications_spec.rb +++ b/spec/features/projects/services/user_activates_slack_notifications_spec.rb @@ -25,7 +25,7 @@ RSpec.describe 'User activates Slack notifications', :js do before do service.fields - service.update( + service.update!( push_channel: 1, issue_channel: 2, merge_request_channel: 3, diff --git a/spec/features/projects/services/user_views_services_spec.rb b/spec/features/projects/services/user_views_services_spec.rb index fef6b7bd991..b936a7f38f6 100644 --- a/spec/features/projects/services/user_views_services_spec.rb +++ b/spec/features/projects/services/user_views_services_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User views services' do +RSpec.describe 'User views services', :js do include_context 'project service activation' it 'shows the list of available services' do @@ -10,7 +10,7 @@ RSpec.describe 'User views services' do expect(page).to have_content('Integrations') expect(page).to have_content('Campfire') - expect(page).to have_content('HipChat') + expect(page).to have_content('Jira') expect(page).to have_content('Assembla') expect(page).to have_content('Pushover') expect(page).to have_content('Atlassian Bamboo') diff --git a/spec/features/projects/settings/access_tokens_spec.rb b/spec/features/projects/settings/access_tokens_spec.rb index 45fe19deb8e..8083c851bb7 100644 --- a/spec/features/projects/settings/access_tokens_spec.rb +++ b/spec/features/projects/settings/access_tokens_spec.rb @@ -5,7 +5,8 @@ require 'spec_helper' RSpec.describe 'Project > Settings > Access Tokens', :js do let_it_be(:user) { create(:user) } let_it_be(:bot_user) { create(:user, :project_bot) } - let_it_be(:project) { create(:project) } + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } before_all do project.add_maintainer(user) @@ -33,6 +34,18 @@ RSpec.describe 'Project > Settings > Access Tokens', :js do find('#created-personal-access-token').value end + context 'when user is not a project maintainer' do + before do + project.add_developer(user) + end + + it 'does not show project access token page' do + visit project_settings_access_tokens_path(project) + + expect(page).to have_content("Page Not Found") + end + end + describe 'token creation' do it 'allows creation of a project access token' do name = 'My project access token' @@ -57,6 +70,81 @@ RSpec.describe 'Project > Settings > Access Tokens', :js do expect(active_project_access_tokens).to have_text('read_api') expect(created_project_access_token).not_to be_empty end + + context 'when token creation is not allowed' do + before do + group.namespace_settings.update_column(:resource_access_token_creation_allowed, false) + end + + it 'does not show project access token creation form' do + visit project_settings_access_tokens_path(project) + + expect(page).not_to have_selector('#new_project_access_token') + end + + it 'shows project access token creation disabled text' do + visit project_settings_access_tokens_path(project) + + expect(page).to have_text('Project access token creation is disabled in this group. You can still use and manage existing tokens.') + end + + context 'with a project in a personal namespace' do + let(:personal_project) { create(:project) } + + before do + personal_project.add_maintainer(user) + end + + it 'shows project access token creation form and text' do + visit project_settings_access_tokens_path(personal_project) + + expect(page).to have_selector('#new_project_access_token') + expect(page).to have_text('You can generate an access token scoped to this project for each application to use the GitLab API.') + end + end + + context 'group settings link' do + context 'when user is not a group owner' do + before do + group.add_developer(user) + end + + it 'does not show group settings link' do + visit project_settings_access_tokens_path(project) + + expect(page).not_to have_link('group settings', href: edit_group_path(group)) + end + end + + context 'with nested groups' do + let(:subgroup) { create(:group, parent: group) } + + context 'when user is not a top level group owner' do + before do + subgroup.add_owner(user) + end + + it 'does not show group settings link' do + visit project_settings_access_tokens_path(project) + + expect(page).not_to have_link('group settings', href: edit_group_path(group)) + end + end + end + + context 'when user is a group owner' do + before do + group.add_owner(user) + end + + it 'shows group settings link' do + visit project_settings_access_tokens_path(project) + + expect(page).to have_link('group settings', href: edit_group_path(group)) + end + end + end + end end describe 'active tokens' do @@ -83,11 +171,25 @@ RSpec.describe 'Project > Settings > Access Tokens', :js do end it 'removes expired tokens from active section' do - project_access_token.update(expires_at: 5.days.ago) + project_access_token.update!(expires_at: 5.days.ago) visit project_settings_access_tokens_path(project) expect(page).to have_selector('.settings-message') expect(no_project_access_tokens_message).to have_text(no_active_tokens_text) end + + context 'when resource access token creation is not allowed' do + before do + group.namespace_settings.update_column(:resource_access_token_creation_allowed, false) + end + + it 'allows revocation of an active token' do + visit project_settings_access_tokens_path(project) + accept_confirm { click_on 'Revoke' } + + expect(page).to have_selector('.settings-message') + expect(no_project_access_tokens_message).to have_text(no_active_tokens_text) + end + end end end diff --git a/spec/features/projects/settings/forked_project_settings_spec.rb b/spec/features/projects/settings/forked_project_settings_spec.rb index f6c25d483ad..a84516e19f9 100644 --- a/spec/features/projects/settings/forked_project_settings_spec.rb +++ b/spec/features/projects/settings/forked_project_settings_spec.rb @@ -15,7 +15,7 @@ RSpec.describe 'Projects > Settings > For a forked project', :js do end shared_examples 'project settings for a forked projects' do - it 'allows deleting the link to the forked project' do + it 'allows deleting the link to the forked project', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/327817' do visit edit_project_path(forked_project) click_button 'Remove fork relationship' @@ -25,7 +25,8 @@ RSpec.describe 'Projects > Settings > For a forked project', :js do fill_in('confirm_name_input', with: forked_project.name) click_button('Confirm') - expect(page).to have_content('The fork relationship has been removed.') + wait_for_requests + expect(forked_project.reload.forked?).to be_falsy end end diff --git a/spec/features/projects/settings/operations_settings_spec.rb b/spec/features/projects/settings/operations_settings_spec.rb index fe0ee52e4fa..ca976997142 100644 --- a/spec/features/projects/settings/operations_settings_spec.rb +++ b/spec/features/projects/settings/operations_settings_spec.rb @@ -146,7 +146,7 @@ RSpec.describe 'Projects > Settings > For a forked project', :js do click_button('Connect') - assert_text('Connection has failed. Re-check Auth Token and try again.') + assert_text('Connection failed. Check Auth Token and try again.') end end end diff --git a/spec/features/projects/settings/registry_settings_spec.rb b/spec/features/projects/settings/registry_settings_spec.rb index 6e4082d1391..bc60cdd2f8e 100644 --- a/spec/features/projects/settings/registry_settings_spec.rb +++ b/spec/features/projects/settings/registry_settings_spec.rb @@ -39,7 +39,7 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p select('7 days', from: 'Remove tags older than:') fill_in('Remove tags matching:', with: '.*-production') - submit_button = find('.btn.gl-button.btn-success') + submit_button = find('[data-testid="save-button"') expect(submit_button).not_to be_disabled submit_button.click end @@ -53,7 +53,7 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p within '#js-registry-policies' do fill_in('Remove tags matching:', with: '*-production') - submit_button = find('.btn.gl-button.btn-success') + submit_button = find('[data-testid="save-button"') expect(submit_button).not_to be_disabled submit_button.click 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 397c334a2b8..ebda5c9ff59 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 @@ -2,6 +2,8 @@ require 'spec_helper' RSpec.describe 'Projects > Settings > User manages merge request settings' do + include ProjectForksHelper + let(:user) { create(:user) } let(:project) { create(:project, :public, namespace: user.namespace, path: 'gitlab', name: 'sample') } @@ -198,4 +200,36 @@ RSpec.describe 'Projects > Settings > User manages merge request settings' do expect(project.reload.project_setting.squash_option).to eq('never') end end + + describe 'target project settings' do + context 'when project is a fork' do + let_it_be(:upstream) { create(:project, :public) } + + let(:project) { fork_project(upstream, user) } + + it 'allows to change merge request target project behavior' do + expect(page).to have_content 'The default target project for merge requests' + + radio = find_field('project_project_setting_attributes_mr_default_target_self_false') + expect(radio).to be_checked + + choose('project_project_setting_attributes_mr_default_target_self_true') + + within('.merge-request-settings-form') do + find('.rspec-save-merge-request-changes') + click_on('Save changes') + end + + find('.flash-notice') + radio = find_field('project_project_setting_attributes_mr_default_target_self_true') + + expect(radio).to be_checked + expect(project.reload.project_setting.mr_default_target_self).to be_truthy + end + end + + it 'does not show target project section' do + expect(page).not_to have_content 'The default target project for merge requests' + end + end end diff --git a/spec/features/projects/settings/user_searches_in_settings_spec.rb b/spec/features/projects/settings/user_searches_in_settings_spec.rb index 4c5b39d5282..9b09958bae5 100644 --- a/spec/features/projects/settings/user_searches_in_settings_spec.rb +++ b/spec/features/projects/settings/user_searches_in_settings_spec.rb @@ -4,16 +4,42 @@ require 'spec_helper' RSpec.describe 'User searches project settings', :js do let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project, :repository, namespace: user.namespace) } + let_it_be(:project) { create(:project, :repository, namespace: user.namespace, pages_https_only: false) } before do sign_in(user) end context 'in general settings page' do - let(:visit_path) { edit_project_path(project) } + before do + visit edit_project_path(project) + end + + it_behaves_like 'can search settings', 'Naming', 'Visibility' + end + + context 'in Integrations page' do + before do + visit project_settings_integrations_path(project) + end + + it_behaves_like 'can highlight results', 'third-party applications' + end + + context 'in Webhooks page' do + before do + visit project_hooks_path(project) + end - it_behaves_like 'can search settings with feature flag check', 'Naming', 'Visibility' + it_behaves_like 'can highlight results', 'Secret token' + end + + context 'in Access Tokens page' do + before do + visit project_settings_access_tokens_path(project) + end + + it_behaves_like 'can highlight results', 'Expires at' end context 'in Repository page' do @@ -37,6 +63,16 @@ RSpec.describe 'User searches project settings', :js do visit project_settings_operations_path(project) end - it_behaves_like 'can search settings', 'Alerts', 'Error tracking' + it_behaves_like 'can search settings', 'Alert integrations', 'Error tracking' + end + + context 'in Pages page' do + before do + allow(Gitlab.config.pages).to receive(:enabled).and_return(true) + + visit project_pages_path(project) + end + + it_behaves_like 'can highlight results', 'static website' end end diff --git a/spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb b/spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb index d0f297d2067..eed3494ef5b 100644 --- a/spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb +++ b/spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb @@ -13,16 +13,10 @@ RSpec.describe 'Repository Settings > User sees revoke deploy token modal', :js sign_in(user) stub_feature_flags(ajax_new_deploy_token: project) visit(project_settings_repository_path(project)) - click_link('Revoke') + click_button('Revoke') end it 'shows the revoke deploy token modal' do expect(page).to have_content('You are about to revoke') end - - it 'closes the revoke deploy token modal with escape keypress' do - find('.modal.show').send_keys(:escape) - - expect(page).not_to have_content('You are about to revoke') - end end diff --git a/spec/features/projects/show/user_sees_deletion_failure_message_spec.rb b/spec/features/projects/show/user_sees_deletion_failure_message_spec.rb index 5e878411f6a..b7af0c29b33 100644 --- a/spec/features/projects/show/user_sees_deletion_failure_message_spec.rb +++ b/spec/features/projects/show/user_sees_deletion_failure_message_spec.rb @@ -10,7 +10,7 @@ RSpec.describe 'Projects > Show > User sees a deletion failure message' do end it 'shows error message if deletion for project fails' do - project.update(delete_error: "Something went wrong", pending_delete: false) + project.update!(delete_error: "Something went wrong", pending_delete: false) visit project_path(project) 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 9b51e867156..dc551158895 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 @@ -226,11 +226,11 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do expect(project.repository.gitlab_ci_yml).to be_nil page.within('.project-buttons') do - expect(page).to have_link('Set up CI/CD', href: presenter.add_ci_yml_path) + expect(page).to have_link('Set up CI/CD', href: project_ci_pipeline_editor_path(project)) end end - it 'no "Set up CI/CD" button if the project already has a .gitlab-ci.yml' do + it '"Set up CI/CD" button is renamed if the project already has a .gitlab-ci.yml' do Files::CreateService.new( project, project.creator, @@ -247,6 +247,7 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do page.within('.project-buttons') do expect(page).not_to have_link('Set up CI/CD') + expect(page).to have_link('CI/CD configuration') end end end diff --git a/spec/features/projects/show/user_uploads_files_spec.rb b/spec/features/projects/show/user_uploads_files_spec.rb index 2030c4d998a..eb230082bfa 100644 --- a/spec/features/projects/show/user_uploads_files_spec.rb +++ b/spec/features/projects/show/user_uploads_files_spec.rb @@ -17,11 +17,17 @@ RSpec.describe 'Projects > Show > User uploads files' do context 'when a user has write access' do before do visit(project_path(project)) + + wait_for_requests end - include_examples 'it uploads and commit a new text file' + include_examples 'it uploads and commits a new text file' + + include_examples 'it uploads and commits a new image file' + + include_examples 'it uploads and commits a new pdf file' - include_examples 'it uploads and commit a new image file' + include_examples 'it uploads a file to a sub-directory' end context 'when a user does not have write access' do @@ -31,7 +37,7 @@ RSpec.describe 'Projects > Show > User uploads files' do visit(project_path(project2)) end - include_examples 'it uploads and commit a new file to a forked project' + include_examples 'it uploads and commits a new file to a forked project' end context 'when in the empty_repo_upload experiment' do diff --git a/spec/features/projects/snippets/user_comments_on_snippet_spec.rb b/spec/features/projects/snippets/user_comments_on_snippet_spec.rb index b37d40c0eed..3ccb73c88ef 100644 --- a/spec/features/projects/snippets/user_comments_on_snippet_spec.rb +++ b/spec/features/projects/snippets/user_comments_on_snippet_spec.rb @@ -29,7 +29,6 @@ RSpec.describe 'Projects > Snippets > User comments on a snippet', :js do end it 'has autocomplete' do - find('#note_note').native.send_keys('') fill_in 'note[note]', with: '@' expect(page).to have_selector('.atwho-view') diff --git a/spec/features/projects/sub_group_issuables_spec.rb b/spec/features/projects/sub_group_issuables_spec.rb index 8c1d88276df..d7614201740 100644 --- a/spec/features/projects/sub_group_issuables_spec.rb +++ b/spec/features/projects/sub_group_issuables_spec.rb @@ -16,18 +16,18 @@ RSpec.describe 'Subgroup Issuables', :js do it 'shows the full subgroup title when issues index page is empty' do visit project_issues_path(project) - expect_to_have_full_subgroup_title + expect_to_have_breadcrumb_links end it 'shows the full subgroup title when merge requests index page is empty' do visit project_merge_requests_path(project) - expect_to_have_full_subgroup_title + expect_to_have_breadcrumb_links end - def expect_to_have_full_subgroup_title - title = find('.breadcrumbs-links') + def expect_to_have_breadcrumb_links + links = find('[data-testid="breadcrumb-links"]') - expect(title).to have_content 'group subgroup project' + expect(links).to have_content 'group subgroup project' end end diff --git a/spec/features/projects/user_sees_sidebar_spec.rb b/spec/features/projects/user_sees_sidebar_spec.rb index e5ba6b503cc..ff6217d02a7 100644 --- a/spec/features/projects/user_sees_sidebar_spec.rb +++ b/spec/features/projects/user_sees_sidebar_spec.rb @@ -208,7 +208,7 @@ RSpec.describe 'Projects > User sees sidebar' do it 'shows build tab if builds are public' do project.public_builds = true - project.save + project.save! visit project_path(project) diff --git a/spec/features/projects/user_sees_user_popover_spec.rb b/spec/features/projects/user_sees_user_popover_spec.rb index 52e65deae3b..e357824a533 100644 --- a/spec/features/projects/user_sees_user_popover_spec.rb +++ b/spec/features/projects/user_sees_user_popover_spec.rb @@ -35,7 +35,7 @@ RSpec.describe 'User sees user popover', :js do end end - it "displays user popover in system note" do + it 'displays user popover in system note', :sidekiq_inline do add_note("/assign @#{user.username}") find('.system-note-message .js-user-link').hover diff --git a/spec/features/projects/user_uses_shortcuts_spec.rb b/spec/features/projects/user_uses_shortcuts_spec.rb index f97c8d820e3..b6fde19e0d4 100644 --- a/spec/features/projects/user_uses_shortcuts_spec.rb +++ b/spec/features/projects/user_uses_shortcuts_spec.rb @@ -151,7 +151,7 @@ RSpec.describe 'User uses shortcuts', :js do find('body').native.send_key('g') find('body').native.send_key('m') - expect(page).to have_active_navigation('Merge Requests') + expect(page).to have_active_navigation('Merge requests') end end diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 4730679feb8..c18b0f2688b 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -174,26 +174,6 @@ RSpec.describe 'Project' do end end - describe 'remove forked relationship', :js do - let(:user) { create(:user) } - let(:project) { fork_project(create(:project, :public), user, namespace: user.namespace) } - - before do - sign_in user - visit edit_project_path(project) - end - - it 'removes fork' do - expect(page).to have_content 'Remove fork relationship' - - remove_with_confirm('Remove fork relationship', project.path) - - expect(page).to have_content 'The fork relationship has been removed.' - expect(project.reload.forked?).to be_falsey - expect(page).not_to have_content 'Remove fork relationship' - end - end - describe 'showing information about source of a project fork' do let(:user) { create(:user) } let(:base_project) { create(:project, :public, :repository) } diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb index eb099359df9..207b74c990a 100644 --- a/spec/features/protected_branches_spec.rb +++ b/spec/features/protected_branches_spec.rb @@ -24,8 +24,8 @@ RSpec.describe 'Protected Branches', :js do it 'does not allow developer to removes protected branch' do visit project_branches_path(project) - fill_in 'branch-search', with: 'fix' - find('#branch-search').native.send_keys(:enter) + find('input[data-testid="branch-search"]').set('fix') + find('input[data-testid="branch-search"]').native.send_keys(:enter) expect(page).to have_css('.btn-danger.disabled') end @@ -47,8 +47,8 @@ RSpec.describe 'Protected Branches', :js do it 'removes branch after modal confirmation' do visit project_branches_path(project) - fill_in 'branch-search', with: 'fix' - find('#branch-search').native.send_keys(:enter) + find('input[data-testid="branch-search"]').set('fix') + find('input[data-testid="branch-search"]').native.send_keys(:enter) expect(page).to have_content('fix') expect(find('.all-branches')).to have_selector('li', count: 1) @@ -58,8 +58,8 @@ RSpec.describe 'Protected Branches', :js do fill_in 'delete_branch_input', with: 'fix' click_link 'Delete protected branch' - fill_in 'branch-search', with: 'fix' - find('#branch-search').native.send_keys(:enter) + find('input[data-testid="branch-search"]').set('fix') + find('input[data-testid="branch-search"]').native.send_keys(:enter) expect(page).to have_content('No branches to show') end diff --git a/spec/features/registrations/welcome_spec.rb b/spec/features/registrations/welcome_spec.rb new file mode 100644 index 00000000000..74320b69f19 --- /dev/null +++ b/spec/features/registrations/welcome_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Welcome screen' do + let(:user) { create(:user) } + + before do + gitlab_sign_in(user) + + visit users_sign_up_welcome_path + end + + it 'shows the email opt in' do + select 'Software Developer', from: 'user_role' + check 'user_email_opted_in' + click_button 'Get started!' + + expect(user.reload.email_opted_in).to eq(true) + end +end diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index cc024ab8f35..acfb7c2602a 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -49,19 +49,19 @@ RSpec.describe 'Runners' do visit project_runners_path(project) within '.activated-specific-runners' do - expect(page).to have_content('Pause') + expect(page).to have_link('Pause') end click_on 'Pause' within '.activated-specific-runners' do - expect(page).to have_content('Resume') + expect(page).to have_link('Resume') end click_on 'Resume' within '.activated-specific-runners' do - expect(page).to have_content('Pause') + expect(page).to have_link('Pause') end end @@ -79,7 +79,7 @@ RSpec.describe 'Runners' do visit project_runners_path(project) within '.activated-specific-runners' do - first('.edit-runner > a').click + first('[data-testid="edit-runner-link"]').click end expect(page.find_field('runner[access_level]')).not_to be_checked @@ -92,14 +92,14 @@ RSpec.describe 'Runners' do context 'when a runner has a tag' do before do - specific_runner.update(tag_list: ['tag']) + specific_runner.update!(tag_list: ['tag']) end it 'user edits runner not to run untagged jobs' do visit project_runners_path(project) within '.activated-specific-runners' do - first('.edit-runner > a').click + first('[data-testid="edit-runner-link"]').click end expect(page.find_field('runner[run_untagged]')).to be_checked @@ -370,7 +370,7 @@ RSpec.describe 'Runners' do context 'when a runner has a tag' do before do - runner.update(tag_list: ['tag']) + runner.update!(tag_list: ['tag']) end it 'user edits runner not to run untagged jobs' do @@ -450,7 +450,7 @@ RSpec.describe 'Runners' do context 'when a runner has a tag' do before do - runner.update(tag_list: ['tag']) + runner.update!(tag_list: ['tag']) end it 'user edits runner not to run untagged jobs' do diff --git a/spec/features/search/user_uses_header_search_field_spec.rb b/spec/features/search/user_uses_header_search_field_spec.rb index 9296a3f33d4..4c42800cf05 100644 --- a/spec/features/search/user_uses_header_search_field_spec.rb +++ b/spec/features/search/user_uses_header_search_field_spec.rb @@ -175,7 +175,7 @@ RSpec.describe 'User uses header search field', :js do fill_in_search('Merge') within(dashboard_search_options_popup_menu) do - expect(page).to have_text('Merge Requests') + expect(page).to have_text('Merge requests') end end diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb index 2440b738db3..9dcef13757a 100644 --- a/spec/features/security/project/internal_access_spec.rb +++ b/spec/features/security/project/internal_access_spec.rb @@ -356,7 +356,7 @@ RSpec.describe "Internal Project Access" do context "when allowed for public and internal" do before do - project.update(public_builds: true) + project.update!(public_builds: true) end it { is_expected.to be_allowed_for(:admin) } @@ -372,7 +372,7 @@ RSpec.describe "Internal Project Access" do context "when disallowed for public and internal" do before do - project.update(public_builds: false) + project.update!(public_builds: false) end it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) } @@ -396,7 +396,7 @@ RSpec.describe "Internal Project Access" do context "when allowed for public and internal" do before do - project.update(public_builds: true) + project.update!(public_builds: true) end it { is_expected.to be_allowed_for(:admin) } @@ -412,7 +412,7 @@ RSpec.describe "Internal Project Access" do context "when disallowed for public and internal" do before do - project.update(public_builds: false) + project.update!(public_builds: false) end it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) } @@ -436,7 +436,7 @@ RSpec.describe "Internal Project Access" do context 'when allowed for public and internal' do before do - project.update(public_builds: true) + project.update!(public_builds: true) end it { is_expected.to be_allowed_for(:admin) } @@ -452,7 +452,7 @@ RSpec.describe "Internal Project Access" do context 'when disallowed for public and internal' do before do - project.update(public_builds: false) + project.update!(public_builds: false) end it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) } diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index 9d3109b92e6..5a200bea80a 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -319,7 +319,7 @@ RSpec.describe "Private Project Access" do context 'when public builds is enabled' do before do - project.update(public_builds: true) + project.update!(public_builds: true) end it { is_expected.to be_allowed_for(:guest).of(project) } @@ -348,7 +348,7 @@ RSpec.describe "Private Project Access" do context 'when public builds is enabled' do before do - project.update(public_builds: true) + project.update!(public_builds: true) end it { is_expected.to be_allowed_for(:guest).of(project) } @@ -375,7 +375,7 @@ RSpec.describe "Private Project Access" do context 'when public builds is enabled' do before do - project.update(public_builds: true) + project.update!(public_builds: true) end it { is_expected.to be_allowed_for(:guest).of(project) } @@ -405,7 +405,7 @@ RSpec.describe "Private Project Access" do context 'when public builds is enabled' do before do - project.update(public_builds: true) + project.update!(public_builds: true) end it { is_expected.to be_allowed_for(:guest).of(project) } @@ -414,7 +414,7 @@ RSpec.describe "Private Project Access" do context 'when public buils are disabled' do before do project.public_builds = false - project.save + project.save! end it { is_expected.to be_denied_for(:guest).of(project) } @@ -440,7 +440,7 @@ RSpec.describe "Private Project Access" do context 'when public builds is enabled' do before do - project.update(public_builds: true) + project.update!(public_builds: true) end it { is_expected.to be_allowed_for(:guest).of(project) } @@ -448,7 +448,7 @@ RSpec.describe "Private Project Access" do context 'when public builds is disabled' do before do - project.update(public_builds: false) + project.update!(public_builds: false) end it { is_expected.to be_denied_for(:guest).of(project) } diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index 28a1f1cda7f..8ceb6920e77 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -164,7 +164,7 @@ RSpec.describe "Public Project Access" do context "when allowed for public" do before do - project.update(public_builds: true) + project.update!(public_builds: true) end it { is_expected.to be_allowed_for(:admin) } @@ -180,7 +180,7 @@ RSpec.describe "Public Project Access" do context "when disallowed for public" do before do - project.update(public_builds: false) + project.update!(public_builds: false) end it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) } @@ -204,7 +204,7 @@ RSpec.describe "Public Project Access" do context "when allowed for public" do before do - project.update(public_builds: true) + project.update!(public_builds: true) end it { is_expected.to be_allowed_for(:admin) } @@ -220,7 +220,7 @@ RSpec.describe "Public Project Access" do context "when disallowed for public" do before do - project.update(public_builds: false) + project.update!(public_builds: false) end it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) } @@ -244,7 +244,7 @@ RSpec.describe "Public Project Access" do context 'when allowed for public' do before do - project.update(public_builds: true) + project.update!(public_builds: true) end it { is_expected.to be_allowed_for(:admin) } @@ -260,7 +260,7 @@ RSpec.describe "Public Project Access" do context 'when disallowed for public' do before do - project.update(public_builds: false) + project.update!(public_builds: false) end it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { is_expected.to be_allowed_for(:admin) } diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb index ce9a2d1461e..47dad9bd88e 100644 --- a/spec/features/snippets/notes_on_personal_snippets_spec.rb +++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb @@ -108,9 +108,6 @@ RSpec.describe 'Comments on personal snippets', :js do end it 'does not have autocomplete' do - wait_for_requests - - find('#note_note').native.send_keys('') fill_in 'note[note]', with: '@' wait_for_requests diff --git a/spec/features/users/anonymous_sessions_spec.rb b/spec/features/users/anonymous_sessions_spec.rb index 420fb225f94..273d3aa346f 100644 --- a/spec/features/users/anonymous_sessions_spec.rb +++ b/spec/features/users/anonymous_sessions_spec.rb @@ -10,7 +10,7 @@ RSpec.describe 'Session TTLs', :clean_gitlab_redis_shared_state do fill_in 'user_password', with: '12345678' click_button 'Sign in' - expect(page).to have_content('Invalid Login or password') + expect(page).to have_content('Invalid login or password') expect_single_session_with_expiration(Settings.gitlab['unauthenticated_session_expire_delay']) end diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index 1d1120709b5..e60d9d6ab69 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -138,7 +138,7 @@ RSpec.describe 'Login' do gitlab_sign_in(User.ghost) - expect(page).to have_content('Invalid Login or password.') + expect(page).to have_content('Invalid login or password.') end it 'does not update Devise trackable attributes', :clean_gitlab_redis_shared_state do @@ -239,7 +239,7 @@ RSpec.describe 'Login' do expect(codes.size).to eq 10 # Ensure the generated codes get saved - user.save(touch: false) + user.save!(touch: false) end context 'with valid code' do @@ -406,7 +406,7 @@ RSpec.describe 'Login' do gitlab_sign_in(user) - expect(page).to have_content('Invalid Login or password.') + expect(page).to have_content('Invalid login or password.') end end end diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb index a8372800700..56d2aaea203 100644 --- a/spec/features/users/show_spec.rb +++ b/spec/features/users/show_spec.rb @@ -33,7 +33,7 @@ RSpec.describe 'User page' do context 'work information' do it 'shows job title and organization details' do - user.update(organization: 'GitLab - work info test', job_title: 'Frontend Engineer') + user.update!(organization: 'GitLab - work info test', job_title: 'Frontend Engineer') subject @@ -41,7 +41,7 @@ RSpec.describe 'User page' do end it 'shows job title' do - user.update(organization: nil, job_title: 'Frontend Engineer - work info test') + user.update!(organization: nil, job_title: 'Frontend Engineer - work info test') subject @@ -49,7 +49,7 @@ RSpec.describe 'User page' do end it 'shows organization details' do - user.update(organization: 'GitLab - work info test', job_title: '') + user.update!(organization: 'GitLab - work info test', job_title: '') subject diff --git a/spec/features/users/terms_spec.rb b/spec/features/users/terms_spec.rb index 7500f2fe59a..8ba79d77c22 100644 --- a/spec/features/users/terms_spec.rb +++ b/spec/features/users/terms_spec.rb @@ -121,7 +121,7 @@ RSpec.describe 'Users > Terms' do enforce_terms - click_button 'Submit issue' + click_button 'Create issue' expect(current_path).to eq(terms_path) diff --git a/spec/features/whats_new_spec.rb b/spec/features/whats_new_spec.rb index 7c5625486f5..55b96361f03 100644 --- a/spec/features/whats_new_spec.rb +++ b/spec/features/whats_new_spec.rb @@ -2,34 +2,60 @@ require "spec_helper" -RSpec.describe "renders a `whats new` dropdown item", :js do +RSpec.describe "renders a `whats new` dropdown item" do let_it_be(:user) { create(:user) } - before do - sign_in(user) - end + context 'when not logged in' do + it 'and on .com it renders' do + allow(Gitlab).to receive(:com?).and_return(true) - it 'shows notification dot and count and removes it once viewed' do - visit root_dashboard_path + visit user_path(user) - page.within '.header-help' do - expect(page).to have_selector('.notification-dot', visible: true) + page.within '.header-help' do + find('.header-help-dropdown-toggle').click - find('.header-help-dropdown-toggle').click + expect(page).to have_button(text: "What's new") + end + end + + it "doesn't render what's new" do + visit user_path(user) - expect(page).to have_button(text: "What's new") - expect(page).to have_selector('.js-whats-new-notification-count') + page.within '.header-help' do + find('.header-help-dropdown-toggle').click + + expect(page).not_to have_button(text: "What's new") + end + end + end - find('button', text: "What's new").click + context 'when logged in', :js do + before do + sign_in(user) end - find('.whats-new-drawer .gl-drawer-close-button').click - find('.header-help-dropdown-toggle').click + it 'shows notification dot and count and removes it once viewed' do + visit root_dashboard_path + + page.within '.header-help' do + expect(page).to have_selector('.notification-dot', visible: true) + + find('.header-help-dropdown-toggle').click + + expect(page).to have_button(text: "What's new") + expect(page).to have_selector('.js-whats-new-notification-count') + + find('button', text: "What's new").click + end + + find('.whats-new-drawer .gl-drawer-close-button').click + find('.header-help-dropdown-toggle').click - page.within '.header-help' do - expect(page).not_to have_selector('.notification-dot', visible: true) - expect(page).to have_button(text: "What's new") - expect(page).not_to have_selector('.js-whats-new-notification-count') + page.within '.header-help' do + expect(page).not_to have_selector('.notification-dot', visible: true) + expect(page).to have_button(text: "What's new") + expect(page).not_to have_selector('.js-whats-new-notification-count') + end end end end diff --git a/spec/finders/applications_finder_spec.rb b/spec/finders/applications_finder_spec.rb index dc615144b88..b6c48d8cdae 100644 --- a/spec/finders/applications_finder_spec.rb +++ b/spec/finders/applications_finder_spec.rb @@ -5,18 +5,48 @@ require 'spec_helper' RSpec.describe ApplicationsFinder do let(:application1) { create(:application, name: 'some_application', owner: nil, redirect_uri: 'http://some_application.url', scopes: '') } let(:application2) { create(:application, name: 'another_application', owner: nil, redirect_uri: 'http://other_application.url', scopes: '') } + let(:user_application) { create(:application, name: 'user_application', owner: create(:user), redirect_uri: 'http://user_application.url', scopes: '') } + let(:group_application) { create(:application, name: 'group_application', owner: create(:group), redirect_uri: 'http://group_application.url', scopes: '') } describe '#execute' do - it 'returns an array of applications' do + it 'returns an array of instance applications' do found = described_class.new.execute expect(found).to match_array([application1, application2]) end - it 'returns the application by id' do - params = { id: application1.id } - found = described_class.new(params).execute - expect(found).to match(application1) + context 'by_id' do + context 'with existing id' do + it 'returns the application' do + params = { id: application1.id } + found = described_class.new(params).execute + + expect(found).to match(application1) + end + end + + context 'with invalid id' do + it 'returns nil for user application' do + params = { id: user_application.id } + found = described_class.new(params).execute + + expect(found).to be_nil + end + + it 'returns nil for group application' do + params = { id: group_application.id } + found = described_class.new(params).execute + + expect(found).to be_nil + end + + it 'returns nil for non-existing application' do + params = { id: non_existing_record_id } + found = described_class.new(params).execute + + expect(found).to be_nil + end + end end end end diff --git a/spec/finders/ci/variables_finder_spec.rb b/spec/finders/ci/variables_finder_spec.rb index cd5f950ca8e..683788452cc 100644 --- a/spec/finders/ci/variables_finder_spec.rb +++ b/spec/finders/ci/variables_finder_spec.rb @@ -3,42 +3,57 @@ require 'spec_helper' RSpec.describe Ci::VariablesFinder do - let!(:project) { create(:project) } - let!(:params) { {} } + shared_examples 'scoped variables' do + describe '#initialize' do + subject { described_class.new(owner, 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') } + context 'without key filter' do + let!(:params) { {} } - 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]') + it 'raises an error' do + expect { subject }.to raise_error(ArgumentError, 'Please provide params[:key]') + end end end - end - describe '#execute' do - subject { described_class.new(project.reload, params).execute } + describe '#execute' do + subject { described_class.new(owner.reload, params).execute } - context 'with key filter' do - let!(:params) { { key: 'key1' } } + context 'with key filter' do + let!(:params) { { key: 'key1' } } - it 'returns var1' do - expect(subject).to contain_exactly(var1) + it 'returns var1' do + expect(subject).to contain_exactly(var1) + end end - end - context 'with key and environment_scope filter' do - let!(:params) { { key: 'key2', filter: { environment_scope: 'staging' } } } + 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) + it 'returns var2' do + expect(subject).to contain_exactly(var2) + end end end end + + context 'for a project' do + let(:owner) { create(:project) } + + let!(:var1) { create(:ci_variable, project: owner, key: 'key1', environment_scope: 'staging') } + let!(:var2) { create(:ci_variable, project: owner, key: 'key2', environment_scope: 'staging') } + let!(:var3) { create(:ci_variable, project: owner, key: 'key2', environment_scope: 'production') } + + include_examples 'scoped variables' + end + + context 'for a group' do + let(:owner) { create(:group) } + + let!(:var1) { create(:ci_group_variable, group: owner, key: 'key1', environment_scope: 'staging') } + let!(:var2) { create(:ci_group_variable, group: owner, key: 'key2', environment_scope: 'staging') } + let!(:var3) { create(:ci_group_variable, group: owner, key: 'key2', environment_scope: 'production') } + + include_examples 'scoped variables' + end end diff --git a/spec/finders/concerns/finder_with_group_hierarchy_spec.rb b/spec/finders/concerns/finder_with_group_hierarchy_spec.rb new file mode 100644 index 00000000000..8c2026a00a1 --- /dev/null +++ b/spec/finders/concerns/finder_with_group_hierarchy_spec.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe FinderWithGroupHierarchy do + let(:finder_class) do + Class.new do + include FinderWithGroupHierarchy + include Gitlab::Utils::StrongMemoize + + def initialize(current_user, params = {}) + @current_user = current_user + @params = params + end + + def execute(skip_authorization: false) + @skip_authorization = skip_authorization + + item_ids + end + + # normally an array of item ids would be returned, + # however for this spec just return the group ids + def item_ids + group? ? group_ids_for(group) : [] + end + + private + + attr_reader :current_user, :params, :skip_authorization + + def read_permission + :read_label + end + end + end + + let_it_be(:parent_group) { create(:group) } + let_it_be(:group) { create(:group, parent: parent_group) } + let_it_be(:private_group) { create(:group, :private) } + let_it_be(:private_subgroup) { create(:group, :private, parent: private_group) } + + let(:user) { create(:user) } + + context 'when specifying group' do + it 'returns only the group by default' do + finder = finder_class.new(user, group: group) + + expect(finder.execute).to match_array([group.id]) + end + end + + context 'when specifying group_id' do + it 'returns only the group by default' do + finder = finder_class.new(user, group_id: group.id) + + expect(finder.execute).to match_array([group.id]) + end + end + + context 'when including items from group ancestors' do + before do + private_subgroup.add_developer(user) + end + + it 'returns group and its ancestors' do + private_group.add_developer(user) + + finder = finder_class.new(user, group: private_subgroup, include_ancestor_groups: true) + + expect(finder.execute).to match_array([private_group.id, private_subgroup.id]) + end + + it 'ignores groups which user can not read' do + finder = finder_class.new(user, group: private_subgroup, include_ancestor_groups: true) + + expect(finder.execute).to match_array([private_subgroup.id]) + end + + it 'returns them all when skip_authorization is true' do + finder = finder_class.new(user, group: private_subgroup, include_ancestor_groups: true) + + expect(finder.execute(skip_authorization: true)).to match_array([private_group.id, private_subgroup.id]) + end + end + + context 'when including items from group descendants' do + before do + private_subgroup.add_developer(user) + end + + it 'returns items from group and its descendants' do + private_group.add_developer(user) + + finder = finder_class.new(user, group: private_group, include_descendant_groups: true) + + expect(finder.execute).to match_array([private_group.id, private_subgroup.id]) + end + + it 'ignores items from groups which user can not read' do + finder = finder_class.new(user, group: private_group, include_descendant_groups: true) + + expect(finder.execute).to match_array([private_subgroup.id]) + end + + it 'returns them all when skip_authorization is true' do + finder = finder_class.new(user, group: private_group, include_descendant_groups: true) + + expect(finder.execute(skip_authorization: true)).to match_array([private_group.id, private_subgroup.id]) + end + end +end diff --git a/spec/finders/concerns/packages/finder_helper_spec.rb b/spec/finders/concerns/packages/finder_helper_spec.rb index 73f77647573..c1740ee1796 100644 --- a/spec/finders/concerns/packages/finder_helper_spec.rb +++ b/spec/finders/concerns/packages/finder_helper_spec.rb @@ -6,7 +6,6 @@ RSpec.describe ::Packages::FinderHelper do describe '#packages_visible_to_user' do using RSpec::Parameterized::TableSyntax - let_it_be(:user) { create(:user) } let_it_be_with_reload(:group) { create(:group) } let_it_be_with_reload(:project1) { create(:project, namespace: group) } let_it_be(:package1) { create(:package, project: project1) } @@ -44,41 +43,87 @@ RSpec.describe ::Packages::FinderHelper do it { is_expected.to be_empty } end - where(:group_visibility, :subgroup_visibility, :project2_visibility, :user_role, :shared_example_name) do - 'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :maintainer | 'returning both packages' - 'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :developer | 'returning both packages' - 'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :guest | 'returning both packages' - 'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :anonymous | 'returning both packages' - 'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :maintainer | 'returning both packages' - 'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :developer | 'returning both packages' - 'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :guest | 'returning package1' - 'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :anonymous | 'returning package1' - 'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :maintainer | 'returning both packages' - 'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :developer | 'returning both packages' - 'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :guest | 'returning package1' - 'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :anonymous | 'returning package1' - 'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :maintainer | 'returning both packages' - 'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :developer | 'returning both packages' - 'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :guest | 'returning no packages' - 'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :anonymous | 'returning no packages' + context 'with a user' do + let_it_be(:user) { create(:user) } + + where(:group_visibility, :subgroup_visibility, :project2_visibility, :user_role, :shared_example_name) do + 'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :maintainer | 'returning both packages' + 'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :developer | 'returning both packages' + 'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :guest | 'returning both packages' + 'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :anonymous | 'returning both packages' + 'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :maintainer | 'returning both packages' + 'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :developer | 'returning both packages' + 'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :guest | 'returning package1' + 'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :anonymous | 'returning package1' + 'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :maintainer | 'returning both packages' + 'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :developer | 'returning both packages' + 'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :guest | 'returning package1' + 'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :anonymous | 'returning package1' + 'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :maintainer | 'returning both packages' + 'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :developer | 'returning both packages' + 'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :guest | 'returning no packages' + 'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :anonymous | 'returning no packages' + end + + with_them do + before do + unless user_role == :anonymous + group.send("add_#{user_role}", user) + subgroup.send("add_#{user_role}", user) + project1.send("add_#{user_role}", user) + project2.send("add_#{user_role}", user) + end + + project2.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project2_visibility, false)) + subgroup.update!(visibility_level: Gitlab::VisibilityLevel.const_get(subgroup_visibility, false)) + project1.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility, false)) + group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility, false)) + end + + it_behaves_like params[:shared_example_name] + end end - with_them do - before do - unless user_role == :anonymous - group.send("add_#{user_role}", user) - subgroup.send("add_#{user_role}", user) - project1.send("add_#{user_role}", user) - project2.send("add_#{user_role}", user) + context 'with a group deploy token' do + let_it_be(:user) { create(:deploy_token, :group, read_package_registry: true) } + let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) } + + shared_examples 'handling all conditions' do + where(:group_visibility, :subgroup_visibility, :project2_visibility, :shared_example_name) do + 'PUBLIC' | 'PUBLIC' | 'PUBLIC' | 'returning both packages' + 'PUBLIC' | 'PUBLIC' | 'PRIVATE' | 'returning both packages' + 'PUBLIC' | 'PRIVATE' | 'PRIVATE' | 'returning both packages' + 'PRIVATE' | 'PRIVATE' | 'PRIVATE' | 'returning both packages' + end + + with_them do + before do + project2.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project2_visibility, false)) + subgroup.update!(visibility_level: Gitlab::VisibilityLevel.const_get(subgroup_visibility, false)) + project1.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility, false)) + group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility, false)) + end + + it_behaves_like params[:shared_example_name] + end + end + + context 'with packages_finder_helper_deploy_token enabled' do + before do + expect(group).not_to receive(:all_projects) end - project2.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project2_visibility, false)) - subgroup.update!(visibility_level: Gitlab::VisibilityLevel.const_get(subgroup_visibility, false)) - project1.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility, false)) - group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility, false)) + it_behaves_like 'handling all conditions' end - it_behaves_like params[:shared_example_name] + context 'with packages_finder_helper_deploy_token disabled' do + before do + stub_feature_flags(packages_finder_helper_deploy_token: false) + expect(group).to receive(:all_projects).and_call_original + end + + it_behaves_like 'handling all conditions' + end end end @@ -121,41 +166,87 @@ RSpec.describe ::Packages::FinderHelper do it { is_expected.to be_empty } end - where(:group_visibility, :subgroup_visibility, :project2_visibility, :user_role, :shared_example_name) do - 'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :maintainer | 'returning both projects' - 'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :developer | 'returning both projects' - 'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :guest | 'returning both projects' - 'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :anonymous | 'returning both projects' - 'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :maintainer | 'returning both projects' - 'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :developer | 'returning both projects' - 'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :guest | 'returning project1' - 'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :anonymous | 'returning project1' - 'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :maintainer | 'returning both projects' - 'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :developer | 'returning both projects' - 'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :guest | 'returning project1' - 'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :anonymous | 'returning project1' - 'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :maintainer | 'returning both projects' - 'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :developer | 'returning both projects' - 'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :guest | 'returning no project' - 'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :anonymous | 'returning no project' + context 'with a user' do + let_it_be(:user) { create(:user) } + + where(:group_visibility, :subgroup_visibility, :project2_visibility, :user_role, :shared_example_name) do + 'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :maintainer | 'returning both projects' + 'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :developer | 'returning both projects' + 'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :guest | 'returning both projects' + 'PUBLIC' | 'PUBLIC' | 'PUBLIC' | :anonymous | 'returning both projects' + 'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :maintainer | 'returning both projects' + 'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :developer | 'returning both projects' + 'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :guest | 'returning project1' + 'PUBLIC' | 'PUBLIC' | 'PRIVATE' | :anonymous | 'returning project1' + 'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :maintainer | 'returning both projects' + 'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :developer | 'returning both projects' + 'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :guest | 'returning project1' + 'PUBLIC' | 'PRIVATE' | 'PRIVATE' | :anonymous | 'returning project1' + 'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :maintainer | 'returning both projects' + 'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :developer | 'returning both projects' + 'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :guest | 'returning no project' + 'PRIVATE' | 'PRIVATE' | 'PRIVATE' | :anonymous | 'returning no project' + end + + with_them do + before do + unless user_role == :anonymous + group.send("add_#{user_role}", user) + subgroup.send("add_#{user_role}", user) + project1.send("add_#{user_role}", user) + project2.send("add_#{user_role}", user) + end + + project2.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project2_visibility, false)) + subgroup.update!(visibility_level: Gitlab::VisibilityLevel.const_get(subgroup_visibility, false)) + project1.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility, false)) + group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility, false)) + end + + it_behaves_like params[:shared_example_name] + end end - with_them do - before do - unless user_role == :anonymous - group.send("add_#{user_role}", user) - subgroup.send("add_#{user_role}", user) - project1.send("add_#{user_role}", user) - project2.send("add_#{user_role}", user) + context 'with a group deploy token' do + let_it_be(:user) { create(:deploy_token, :group, read_package_registry: true) } + let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) } + + shared_examples 'handling all conditions' do + where(:group_visibility, :subgroup_visibility, :project2_visibility, :shared_example_name) do + 'PUBLIC' | 'PUBLIC' | 'PUBLIC' | 'returning both projects' + 'PUBLIC' | 'PUBLIC' | 'PRIVATE' | 'returning both projects' + 'PUBLIC' | 'PRIVATE' | 'PRIVATE' | 'returning both projects' + 'PRIVATE' | 'PRIVATE' | 'PRIVATE' | 'returning both projects' end - project2.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project2_visibility, false)) - subgroup.update!(visibility_level: Gitlab::VisibilityLevel.const_get(subgroup_visibility, false)) - project1.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility, false)) - group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility, false)) + with_them do + before do + project2.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project2_visibility, false)) + subgroup.update!(visibility_level: Gitlab::VisibilityLevel.const_get(subgroup_visibility, false)) + project1.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility, false)) + group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility, false)) + end + + it_behaves_like params[:shared_example_name] + end end - it_behaves_like params[:shared_example_name] + context 'with packages_finder_helper_deploy_token enabled' do + before do + expect(group).not_to receive(:all_projects) + end + + it_behaves_like 'handling all conditions' + end + + context 'with packages_finder_helper_deploy_token disabled' do + before do + stub_feature_flags(packages_finder_helper_deploy_token: false) + expect(group).to receive(:all_projects).and_call_original + end + + it_behaves_like 'handling all conditions' + end end end end diff --git a/spec/finders/design_management/designs_finder_spec.rb b/spec/finders/design_management/designs_finder_spec.rb index feb78a4bc4b..631f23b7312 100644 --- a/spec/finders/design_management/designs_finder_spec.rb +++ b/spec/finders/design_management/designs_finder_spec.rb @@ -11,6 +11,7 @@ RSpec.describe DesignManagement::DesignsFinder do let_it_be(:design1) { create(:design, :with_file, issue: issue, versions_count: 1, relative_position: 3) } let_it_be(:design2) { create(:design, :with_file, issue: issue, versions_count: 1, relative_position: 2) } let_it_be(:design3) { create(:design, :with_file, issue: issue, versions_count: 1, relative_position: 1) } + let(:params) { {} } subject(:designs) { described_class.new(issue, user, params).execute } diff --git a/spec/finders/design_management/versions_finder_spec.rb b/spec/finders/design_management/versions_finder_spec.rb index 6a56ccb10b8..0d606ef46f1 100644 --- a/spec/finders/design_management/versions_finder_spec.rb +++ b/spec/finders/design_management/versions_finder_spec.rb @@ -10,6 +10,7 @@ RSpec.describe DesignManagement::VersionsFinder do let_it_be(:issue) { create(:issue, project: project) } let_it_be(:design_1) { create(:design, :with_file, issue: issue, versions_count: 1) } let_it_be(:design_2) { create(:design, :with_file, issue: issue, versions_count: 1) } + let(:version_1) { design_1.versions.first } let(:version_2) { design_2.versions.first } let(:design_or_collection) { issue.design_collection } diff --git a/spec/finders/environments_by_deployments_finder_spec.rb b/spec/finders/environments_by_deployments_finder_spec.rb new file mode 100644 index 00000000000..f5fcc4ef72a --- /dev/null +++ b/spec/finders/environments_by_deployments_finder_spec.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe EnvironmentsByDeploymentsFinder do + let(:project) { create(:project, :repository) } + let(:user) { project.creator } + let(:environment) { create(:environment, :available, project: project) } + + before do + project.add_maintainer(user) + end + + describe '#execute' do + context 'tagged deployment' do + let(:environment_two) { create(:environment, project: project) } + # Environments need to include commits, so rewind two commits to fit + let(:commit) { project.commit('HEAD~2') } + + before do + create(:deployment, :success, environment: environment, ref: 'v1.0.0', tag: true, sha: project.commit.id) + create(:deployment, :success, environment: environment_two, ref: 'v1.1.0', tag: true, sha: project.commit('HEAD~1').id) + end + + it 'returns environment when with_tags is set' do + expect(described_class.new(project, user, ref: 'master', commit: commit, with_tags: true).execute) + .to contain_exactly(environment, environment_two) + end + + it 'does not return environment when no with_tags is set' do + expect(described_class.new(project, user, ref: 'master', commit: commit).execute) + .to be_empty + end + + it 'does not return environment when commit is not part of deployment' do + expect(described_class.new(project, user, ref: 'master', commit: project.commit('feature')).execute) + .to be_empty + end + + # We expect two Gitaly calls: FindCommit, CommitIsAncestor + # This tests to ensure we don't call one CommitIsAncestor per environment + it 'only calls Gitaly twice when multiple environments are present', :request_store do + expect do + result = described_class.new(project, user, ref: 'master', commit: commit, with_tags: true, find_latest: true).execute + + expect(result).to contain_exactly(environment_two) + end.to change { Gitlab::GitalyClient.get_request_count }.by(2) + end + end + + context 'branch deployment' do + before do + create(:deployment, :success, environment: environment, ref: 'master', sha: project.commit.id) + end + + it 'returns environment when ref is set' do + expect(described_class.new(project, user, ref: 'master', commit: project.commit).execute) + .to contain_exactly(environment) + end + + it 'does not environment when ref is different' do + expect(described_class.new(project, user, ref: 'feature', commit: project.commit).execute) + .to be_empty + end + + it 'does not return environment when commit is not part of deployment' do + expect(described_class.new(project, user, ref: 'master', commit: project.commit('feature')).execute) + .to be_empty + end + + it 'returns environment when commit constraint is not set' do + expect(described_class.new(project, user, ref: 'master').execute) + .to contain_exactly(environment) + end + end + + context 'commit deployment' do + before do + create(:deployment, :success, environment: environment, ref: 'master', sha: project.commit.id) + end + + it 'returns environment' do + expect(described_class.new(project, user, commit: project.commit).execute) + .to contain_exactly(environment) + end + end + + context 'recently updated' do + context 'when last deployment to environment is the most recent one' do + before do + create(:deployment, :success, environment: environment, ref: 'feature') + end + + it 'finds recently updated environment' do + expect(described_class.new(project, user, ref: 'feature', recently_updated: true).execute) + .to contain_exactly(environment) + end + end + + context 'when last deployment to environment is not the most recent' do + before do + create(:deployment, :success, environment: environment, ref: 'feature') + create(:deployment, :success, environment: environment, ref: 'master') + end + + it 'does not find environment' do + expect(described_class.new(project, user, ref: 'feature', recently_updated: true).execute) + .to be_empty + end + end + + context 'when there are two environments that deploy to the same branch' do + let(:second_environment) { create(:environment, project: project) } + + before do + create(:deployment, :success, environment: environment, ref: 'feature') + create(:deployment, :success, environment: second_environment, ref: 'feature') + end + + it 'finds both environments' do + expect(described_class.new(project, user, ref: 'feature', recently_updated: true).execute) + .to contain_exactly(environment, second_environment) + end + end + end + end +end diff --git a/spec/finders/environments_finder_spec.rb b/spec/finders/environments_finder_spec.rb index fd714ab9a8f..c2022331ad9 100644 --- a/spec/finders/environments_finder_spec.rb +++ b/spec/finders/environments_finder_spec.rb @@ -12,150 +12,36 @@ RSpec.describe EnvironmentsFinder do end describe '#execute' do - context 'tagged deployment' do - let(:environment_two) { create(:environment, project: project) } - # Environments need to include commits, so rewind two commits to fit - let(:commit) { project.commit('HEAD~2') } - - before do - create(:deployment, :success, environment: environment, ref: 'v1.0.0', tag: true, sha: project.commit.id) - create(:deployment, :success, environment: environment_two, ref: 'v1.1.0', tag: true, sha: project.commit('HEAD~1').id) - end - - it 'returns environment when with_tags is set' do - expect(described_class.new(project, user, ref: 'master', commit: commit, with_tags: true).execute) - .to contain_exactly(environment, environment_two) - end - - it 'does not return environment when no with_tags is set' do - expect(described_class.new(project, user, ref: 'master', commit: commit).execute) - .to be_empty - end - - it 'does not return environment when commit is not part of deployment' do - expect(described_class.new(project, user, ref: 'master', commit: project.commit('feature')).execute) - .to be_empty - end - - # We expect two Gitaly calls: FindCommit, CommitIsAncestor - # This tests to ensure we don't call one CommitIsAncestor per environment - it 'only calls Gitaly twice when multiple environments are present', :request_store do - expect do - result = described_class.new(project, user, ref: 'master', commit: commit, with_tags: true, find_latest: true).execute - - expect(result).to contain_exactly(environment_two) - end.to change { Gitlab::GitalyClient.get_request_count }.by(2) - end - end - - context 'branch deployment' do - before do - create(:deployment, :success, environment: environment, ref: 'master', sha: project.commit.id) - end - - it 'returns environment when ref is set' do - expect(described_class.new(project, user, ref: 'master', commit: project.commit).execute) - .to contain_exactly(environment) - end - - it 'does not environment when ref is different' do - expect(described_class.new(project, user, ref: 'feature', commit: project.commit).execute) - .to be_empty - end - - it 'does not return environment when commit is not part of deployment' do - expect(described_class.new(project, user, ref: 'master', commit: project.commit('feature')).execute) - .to be_empty - end - - it 'returns environment when commit constraint is not set' do - expect(described_class.new(project, user, ref: 'master').execute) - .to contain_exactly(environment) - end - end - - context 'commit deployment' do - before do - create(:deployment, :success, environment: environment, ref: 'master', sha: project.commit.id) - end - - it 'returns environment' do - expect(described_class.new(project, user, commit: project.commit).execute) - .to contain_exactly(environment) - end - end - - context 'recently updated' do - context 'when last deployment to environment is the most recent one' do - before do - create(:deployment, :success, environment: environment, ref: 'feature') - end - - it 'finds recently updated environment' do - expect(described_class.new(project, user, ref: 'feature', recently_updated: true).execute) - .to contain_exactly(environment) - end - end - - context 'when last deployment to environment is not the most recent' do - before do - create(:deployment, :success, environment: environment, ref: 'feature') - create(:deployment, :success, environment: environment, ref: 'master') - end - - it 'does not find environment' do - expect(described_class.new(project, user, ref: 'feature', recently_updated: true).execute) - .to be_empty - end - end - - context 'when there are two environments that deploy to the same branch' do - let(:second_environment) { create(:environment, project: project) } - - before do - create(:deployment, :success, environment: environment, ref: 'feature') - create(:deployment, :success, environment: second_environment, ref: 'feature') - end - - it 'finds both environments' do - expect(described_class.new(project, user, ref: 'feature', recently_updated: true).execute) - .to contain_exactly(environment, second_environment) - end - end - end - end - - describe '#find' do context 'with states parameter' do let(:stopped_environment) { create(:environment, :stopped, project: project) } it 'returns environments with the requested state' do - result = described_class.new(project, user, states: 'available').find + result = described_class.new(project, user, states: 'available').execute expect(result).to contain_exactly(environment) end it 'returns environments with any of the requested states' do - result = described_class.new(project, user, states: %w(available stopped)).find + result = described_class.new(project, user, states: %w(available stopped)).execute expect(result).to contain_exactly(environment, stopped_environment) end it 'raises exception when requested state is invalid' do - expect { described_class.new(project, user, states: %w(invalid stopped)).find }.to( + expect { described_class.new(project, user, states: %w(invalid stopped)).execute }.to( raise_error(described_class::InvalidStatesError, 'Requested states are invalid') ) end context 'works with symbols' do it 'returns environments with the requested state' do - result = described_class.new(project, user, states: :available).find + result = described_class.new(project, user, states: :available).execute expect(result).to contain_exactly(environment) end it 'returns environments with any of the requested states' do - result = described_class.new(project, user, states: [:available, :stopped]).find + result = described_class.new(project, user, states: [:available, :stopped]).execute expect(result).to contain_exactly(environment, stopped_environment) end @@ -167,7 +53,7 @@ RSpec.describe EnvironmentsFinder do let(:environment3) { create(:environment, :available, name: 'test3', project: project) } it 'searches environments by name and state' do - result = described_class.new(project, user, search: 'test', states: :available).find + result = described_class.new(project, user, search: 'test', states: :available).execute expect(result).to contain_exactly(environment3) end diff --git a/spec/finders/group_members_finder_spec.rb b/spec/finders/group_members_finder_spec.rb index a87a05d4408..3238f6744f7 100644 --- a/spec/finders/group_members_finder_spec.rb +++ b/spec/finders/group_members_finder_spec.rb @@ -3,174 +3,180 @@ require 'spec_helper' RSpec.describe GroupMembersFinder, '#execute' do - let(:group) { create(:group) } - let(:nested_group) { create(:group, parent: group) } - let(:deeper_nested_group) { create(:group, parent: nested_group) } - let(:user1) { create(:user) } - let(:user2) { create(:user) } - let(:user3) { create(:user) } - let(:user4) { create(:user) } - let(:user5) { create(:user, :two_factor_via_otp) } - - it 'returns members for top-level group' do - member1 = group.add_maintainer(user1) - member2 = group.add_maintainer(user2) - member3 = group.add_maintainer(user3) - create(:group_member, :minimal_access, user: create(:user), source: group) - - result = described_class.new(group).execute - - expect(result.to_a).to match_array([member3, member2, member1]) + let(:group) { create(:group) } + let(:sub_group) { create(:group, parent: group) } + let(:sub_sub_group) { create(:group, parent: sub_group) } + let(:user1) { create(:user) } + let(:user2) { create(:user) } + let(:user3) { create(:user) } + let(:user4) { create(:user) } + let(:user5) { create(:user, :two_factor_via_otp) } + + let(:groups) do + { + group: group, + sub_group: sub_group, + sub_sub_group: sub_sub_group + } end - it 'returns members & inherited members for nested group by default' do - group.add_developer(user2) - nested_group.request_access(user4) - member1 = group.add_maintainer(user1) - member3 = nested_group.add_maintainer(user2) - member4 = nested_group.add_maintainer(user3) - - result = described_class.new(nested_group).execute - - expect(result.to_a).to match_array([member1, member3, member4]) + context 'relations' do + let!(:members) do + { + user1_sub_sub_group: create(:group_member, :maintainer, group: sub_sub_group, user: user1), + user1_sub_group: create(:group_member, :developer, group: sub_group, user: user1), + user1_group: create(:group_member, :reporter, group: group, user: user1), + user2_sub_sub_group: create(:group_member, :reporter, group: sub_sub_group, user: user2), + user2_sub_group: create(:group_member, :developer, group: sub_group, user: user2), + user2_group: create(:group_member, :maintainer, group: group, user: user2), + user3_sub_sub_group: create(:group_member, :developer, group: sub_sub_group, user: user3, expires_at: 1.day.from_now), + user3_sub_group: create(:group_member, :developer, group: sub_group, user: user3, expires_at: 2.days.from_now), + user3_group: create(:group_member, :reporter, group: group, user: user3), + user4_sub_sub_group: create(:group_member, :reporter, group: sub_sub_group, user: user4), + user4_sub_group: create(:group_member, :developer, group: sub_group, user: user4, expires_at: 1.day.from_now), + user4_group: create(:group_member, :developer, group: group, user: user4, expires_at: 2.days.from_now) + } + end + + using RSpec::Parameterized::TableSyntax + + where(:subject_relations, :subject_group, :expected_members) do + nil | :group | [:user1_group, :user2_group, :user3_group, :user4_group] + [:direct] | :group | [:user1_group, :user2_group, :user3_group, :user4_group] + [:inherited] | :group | [] + [:descendants] | :group | [:user1_sub_sub_group, :user2_sub_group, :user3_sub_group, :user4_sub_group] + [:direct, :inherited] | :group | [:user1_group, :user2_group, :user3_group, :user4_group] + [:direct, :descendants] | :group | [:user1_sub_sub_group, :user2_group, :user3_sub_group, :user4_group] + [:descendants, :inherited] | :group | [:user1_sub_sub_group, :user2_sub_group, :user3_sub_group, :user4_sub_group] + [:direct, :descendants, :inherited] | :group | [:user1_sub_sub_group, :user2_group, :user3_sub_group, :user4_group] + nil | :sub_group | [:user1_sub_group, :user2_group, :user3_sub_group, :user4_group] + [:direct] | :sub_group | [:user1_sub_group, :user2_sub_group, :user3_sub_group, :user4_sub_group] + [:inherited] | :sub_group | [:user1_group, :user2_group, :user3_group, :user4_group] + [:descendants] | :sub_group | [:user1_sub_sub_group, :user2_sub_sub_group, :user3_sub_sub_group, :user4_sub_sub_group] + [:direct, :inherited] | :sub_group | [:user1_sub_group, :user2_group, :user3_sub_group, :user4_group] + [:direct, :descendants] | :sub_group | [:user1_sub_sub_group, :user2_sub_group, :user3_sub_group, :user4_sub_group] + [:descendants, :inherited] | :sub_group | [:user1_sub_sub_group, :user2_group, :user3_sub_sub_group, :user4_group] + [:direct, :descendants, :inherited] | :sub_group | [:user1_sub_sub_group, :user2_group, :user3_sub_group, :user4_group] + nil | :sub_sub_group | [:user1_sub_sub_group, :user2_group, :user3_sub_group, :user4_group] + [:direct] | :sub_sub_group | [:user1_sub_sub_group, :user2_sub_sub_group, :user3_sub_sub_group, :user4_sub_sub_group] + [:inherited] | :sub_sub_group | [:user1_sub_group, :user2_group, :user3_sub_group, :user4_group] + [:descendants] | :sub_sub_group | [] + [:direct, :inherited] | :sub_sub_group | [:user1_sub_sub_group, :user2_group, :user3_sub_group, :user4_group] + [:direct, :descendants] | :sub_sub_group | [:user1_sub_sub_group, :user2_sub_sub_group, :user3_sub_sub_group, :user4_sub_sub_group] + [:descendants, :inherited] | :sub_sub_group | [:user1_sub_group, :user2_group, :user3_sub_group, :user4_group] + [:direct, :descendants, :inherited] | :sub_sub_group | [:user1_sub_sub_group, :user2_group, :user3_sub_group, :user4_group] + end + + with_them do + it 'returns correct members' do + result = if subject_relations + described_class.new(groups[subject_group]).execute(include_relations: subject_relations) + else + described_class.new(groups[subject_group]).execute + end + + expect(result.to_a).to match_array(expected_members.map { |name| members[name] }) + end + end end - it 'does not return inherited members for nested group if requested' do - group.add_maintainer(user1) - group.add_developer(user2) - member2 = nested_group.add_maintainer(user2) - member3 = nested_group.add_maintainer(user3) + context 'search' do + it 'returns searched members if requested' do + group.add_maintainer(user2) + group.add_developer(user3) + member = group.add_maintainer(user1) - result = described_class.new(nested_group).execute(include_relations: [:direct]) + result = described_class.new(group, params: { search: user1.name }).execute - expect(result.to_a).to match_array([member2, member3]) - end + expect(result.to_a).to match_array([member]) + end - it 'returns only inherited members for nested group if requested' do - group.add_developer(user2) - nested_group.request_access(user4) - member1 = group.add_maintainer(user1) - nested_group.add_maintainer(user2) - nested_group.add_maintainer(user3) + it 'returns nothing if search only in inherited relation' do + group.add_maintainer(user2) + group.add_developer(user3) + group.add_maintainer(user1) - result = described_class.new(nested_group).execute(include_relations: [:inherited]) + result = described_class.new(group, params: { search: user1.name }).execute(include_relations: [:inherited]) - expect(result.to_a).to match_array([member1]) - end + expect(result.to_a).to match_array([]) + end - it 'does not return nil if `inherited only` relation is requested on root group' do - group.add_developer(user2) + it 'returns searched member only from sub_group if search only in inherited relation' do + group.add_maintainer(user2) + group.add_developer(user3) + sub_group.add_maintainer(create(:user, name: user1.name)) + member = group.add_maintainer(user1) - result = described_class.new(group).execute(include_relations: [:inherited]) + result = described_class.new(sub_group, params: { search: member.user.name }).execute(include_relations: [:inherited]) - expect(result).not_to be_nil + expect(result.to_a).to contain_exactly(member) + end end - it 'returns members for descendant groups if requested' do - member1 = group.add_maintainer(user2) - member2 = group.add_maintainer(user1) - nested_group.add_maintainer(user2) - member3 = nested_group.add_maintainer(user3) - member4 = nested_group.add_maintainer(user4) + context 'filter by two-factor' do + it 'returns members with two-factor auth if requested by owner' do + group.add_owner(user2) + group.add_maintainer(user1) + member = group.add_maintainer(user5) - result = described_class.new(group).execute(include_relations: [:direct, :descendants]) + result = described_class.new(group, user2, params: { two_factor: 'enabled' }).execute - expect(result.to_a).to match_array([member1, member2, member3, member4]) - end + expect(result.to_a).to contain_exactly(member) + end - it 'returns searched members if requested' do - group.add_maintainer(user2) - group.add_developer(user3) - member = group.add_maintainer(user1) + it 'returns members without two-factor auth if requested by owner' do + member1 = group.add_owner(user2) + member2 = group.add_maintainer(user1) + member_with_2fa = group.add_maintainer(user5) - result = described_class.new(group, params: { search: user1.name }).execute + result = described_class.new(group, user2, params: { two_factor: 'disabled' }).execute - expect(result.to_a).to match_array([member]) - end + expect(result.to_a).not_to include(member_with_2fa) + expect(result.to_a).to match_array([member1, member2]) + end - it 'returns nothing if search only in inherited relation' do - group.add_maintainer(user2) - group.add_developer(user3) - group.add_maintainer(user1) + it 'returns direct members with two-factor auth if requested by owner' do + group.add_owner(user1) + group.add_maintainer(user2) + sub_group.add_maintainer(user3) + member_with_2fa = sub_group.add_maintainer(user5) - result = described_class.new(group, params: { search: user1.name }).execute(include_relations: [:inherited]) + result = described_class.new(sub_group, user1, params: { two_factor: 'enabled' }).execute(include_relations: [:direct]) - expect(result.to_a).to match_array([]) - end + expect(result.to_a).to match_array([member_with_2fa]) + end - it 'returns searched member only from nested_group if search only in inherited relation' do - group.add_maintainer(user2) - group.add_developer(user3) - nested_group.add_maintainer(create(:user, name: user1.name)) - member = group.add_maintainer(user1) + it 'returns inherited members with two-factor auth if requested by owner' do + group.add_owner(user1) + member_with_2fa = group.add_maintainer(user5) + sub_group.add_maintainer(user2) + sub_group.add_maintainer(user3) - result = described_class.new(nested_group, params: { search: member.user.name }).execute(include_relations: [:inherited]) + result = described_class.new(sub_group, user1, params: { two_factor: 'enabled' }).execute(include_relations: [:inherited]) - expect(result.to_a).to contain_exactly(member) - end - - it 'returns members with two-factor auth if requested by owner' do - group.add_owner(user2) - group.add_maintainer(user1) - member = group.add_maintainer(user5) - - result = described_class.new(group, user2, params: { two_factor: 'enabled' }).execute + expect(result.to_a).to match_array([member_with_2fa]) + end - expect(result.to_a).to contain_exactly(member) - end - - it 'returns members without two-factor auth if requested by owner' do - member1 = group.add_owner(user2) - member2 = group.add_maintainer(user1) - member_with_2fa = group.add_maintainer(user5) + it 'returns direct members without two-factor auth if requested by owner' do + group.add_owner(user1) + group.add_maintainer(user2) + member3 = sub_group.add_maintainer(user3) + sub_group.add_maintainer(user5) - result = described_class.new(group, user2, params: { two_factor: 'disabled' }).execute + result = described_class.new(sub_group, user1, params: { two_factor: 'disabled' }).execute(include_relations: [:direct]) - expect(result.to_a).not_to include(member_with_2fa) - expect(result.to_a).to match_array([member1, member2]) - end - - it 'returns direct members with two-factor auth if requested by owner' do - group.add_owner(user1) - group.add_maintainer(user2) - nested_group.add_maintainer(user3) - member_with_2fa = nested_group.add_maintainer(user5) - - result = described_class.new(nested_group, user1, params: { two_factor: 'enabled' }).execute(include_relations: [:direct]) - - expect(result.to_a).to match_array([member_with_2fa]) - end - - it 'returns inherited members with two-factor auth if requested by owner' do - group.add_owner(user1) - member_with_2fa = group.add_maintainer(user5) - nested_group.add_maintainer(user2) - nested_group.add_maintainer(user3) - - result = described_class.new(nested_group, user1, params: { two_factor: 'enabled' }).execute(include_relations: [:inherited]) - - expect(result.to_a).to match_array([member_with_2fa]) - end - - it 'returns direct members without two-factor auth if requested by owner' do - group.add_owner(user1) - group.add_maintainer(user2) - member3 = nested_group.add_maintainer(user3) - nested_group.add_maintainer(user5) - - result = described_class.new(nested_group, user1, params: { two_factor: 'disabled' }).execute(include_relations: [:direct]) - - expect(result.to_a).to match_array([member3]) - end + expect(result.to_a).to match_array([member3]) + end - it 'returns inherited members without two-factor auth if requested by owner' do - member1 = group.add_owner(user1) - group.add_maintainer(user5) - nested_group.add_maintainer(user2) - nested_group.add_maintainer(user3) + it 'returns inherited members without two-factor auth if requested by owner' do + member1 = group.add_owner(user1) + group.add_maintainer(user5) + sub_group.add_maintainer(user2) + sub_group.add_maintainer(user3) - result = described_class.new(nested_group, user1, params: { two_factor: 'disabled' }).execute(include_relations: [:inherited]) + result = described_class.new(sub_group, user1, params: { two_factor: 'disabled' }).execute(include_relations: [:inherited]) - expect(result.to_a).to match_array([member1]) + expect(result.to_a).to match_array([member1]) + end end end diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index b794ab626bf..a2aac857bf5 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -49,6 +49,13 @@ RSpec.describe IssuesFinder do let(:expected_issuables) { [issue3, issue4] } end + context 'when assignee_id does not exist' do + it_behaves_like 'assignee NOT ID filter' do + let(:params) { { not: { assignee_id: -100 } } } + let(:expected_issuables) { [issue1, issue2, issue3, issue4, issue5] } + end + end + context 'filter by username' do let_it_be(:user3) { create(:user) } @@ -71,6 +78,17 @@ RSpec.describe IssuesFinder do let(:params) { { not: { assignee_username: [user.username, user2.username] } } } let(:expected_issuables) { [issue3, issue4] } end + + context 'when assignee_username does not exist' do + it_behaves_like 'assignee NOT username filter' do + before do + issue2.assignees = [user2] + end + + let(:params) { { not: { assignee_username: 'non_existent_username' } } } + let(:expected_issuables) { [issue1, issue2, issue3, issue4, issue5] } + end + end end it_behaves_like 'no assignee filter' do diff --git a/spec/finders/merge_requests/oldest_per_commit_finder_spec.rb b/spec/finders/merge_requests/oldest_per_commit_finder_spec.rb index 4724a8eb5c7..6dffaff294d 100644 --- a/spec/finders/merge_requests/oldest_per_commit_finder_spec.rb +++ b/spec/finders/merge_requests/oldest_per_commit_finder_spec.rb @@ -77,6 +77,45 @@ RSpec.describe MergeRequests::OldestPerCommitFinder do expect(described_class.new(project).execute(commits)).to eq(sha => mr) end + it 'includes a merge request that was squashed into the target branch' do + project = create(:project) + sha = Digest::SHA1.hexdigest('foo') + mr = create( + :merge_request, + :merged, + target_project: project, + squash_commit_sha: sha + ) + + commits = [double(:commit, id: sha)] + + expect(MergeRequestDiffCommit) + .not_to receive(:oldest_merge_request_id_per_commit) + + expect(described_class.new(project).execute(commits)).to eq(sha => mr) + end + + it 'includes a merge request for both a squash and merge commit' do + project = create(:project) + sha1 = Digest::SHA1.hexdigest('foo') + sha2 = Digest::SHA1.hexdigest('bar') + mr = create( + :merge_request, + :merged, + target_project: project, + squash_commit_sha: sha1, + merge_commit_sha: sha2 + ) + + commits = [double(:commit1, id: sha1), double(:commit2, id: sha2)] + + expect(MergeRequestDiffCommit) + .not_to receive(:oldest_merge_request_id_per_commit) + + expect(described_class.new(project).execute(commits)) + .to eq(sha1 => mr, sha2 => mr) + end + it 'includes the oldest merge request when a merge commit is present in a newer merge request' do project = create(:project) sha = Digest::SHA1.hexdigest('foo') diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index b3000498bb6..597d22801ca 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -156,6 +156,18 @@ RSpec.describe MergeRequestsFinder do it { is_expected.to eq([merge_request2]) } end + + context 'when project_id is given' do + subject(:query) { described_class.new(user, merged_after: 15.days.ago, merged_before: 6.days.ago, project_id: merge_request2.project).execute } + + it { is_expected.to eq([merge_request2]) } + + it 'queries merge_request_metrics.target_project_id table' do + expect(query.to_sql).to include(%{"merge_request_metrics"."target_project_id" = #{merge_request2.target_project_id}}) + + expect(query.to_sql).not_to include(%{"merge_requests"."target_project_id"}) + end + end end context 'filtering by group' do diff --git a/spec/finders/metrics/dashboards/annotations_finder_spec.rb b/spec/finders/metrics/dashboards/annotations_finder_spec.rb index 223fd2c047c..7c5932dde1e 100644 --- a/spec/finders/metrics/dashboards/annotations_finder_spec.rb +++ b/spec/finders/metrics/dashboards/annotations_finder_spec.rb @@ -7,6 +7,7 @@ RSpec.describe Metrics::Dashboards::AnnotationsFinder do subject(:annotations) { described_class.new(dashboard: dashboard, params: params).execute } let_it_be(:current_user) { create(:user) } + let(:path) { 'config/prometheus/common_metrics.yml' } let(:params) { {} } let(:environment) { create(:environment) } diff --git a/spec/finders/metrics/users_starred_dashboards_finder_spec.rb b/spec/finders/metrics/users_starred_dashboards_finder_spec.rb index 61dadb5239c..4136cf1123a 100644 --- a/spec/finders/metrics/users_starred_dashboards_finder_spec.rb +++ b/spec/finders/metrics/users_starred_dashboards_finder_spec.rb @@ -7,6 +7,7 @@ RSpec.describe Metrics::UsersStarredDashboardsFinder do subject(:starred_dashboards) { described_class.new(user: user, project: project, params: params).execute } let_it_be(:user) { create(:user) } + let(:project) { create(:project) } let(:dashboard_path) { 'config/prometheus/common_metrics.yml' } let(:params) { {} } diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb index 868b126dc28..11de19cfdbc 100644 --- a/spec/finders/notes_finder_spec.rb +++ b/spec/finders/notes_finder_spec.rb @@ -213,6 +213,24 @@ RSpec.describe NotesFinder do expect { described_class.new(user, params).execute }.to raise_error(RuntimeError) end end + + describe 'sorting' do + it 'allows sorting' do + params = { project: project, sort: 'id_desc' } + + expect(Note).to receive(:order_id_desc).once + + described_class.new(user, params).execute + end + + it 'defaults to sort by .fresh' do + params = { project: project } + + expect(Note).to receive(:fresh).once + + described_class.new(user, params).execute + end + end end describe '.search' do diff --git a/spec/finders/packages/go/package_finder_spec.rb b/spec/finders/packages/go/package_finder_spec.rb new file mode 100644 index 00000000000..b6fad1e7061 --- /dev/null +++ b/spec/finders/packages/go/package_finder_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Packages::Go::PackageFinder do + include_context 'basic Go module' + + let_it_be(:mod) { create :go_module, project: project } + let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.1' } + let_it_be(:package) { create :golang_package, project: project, name: mod.name, version: 'v1.0.1' } + + let(:finder) { described_class.new(project, mod_name, version_name) } + + describe '#exists?' do + subject { finder.exists? } + + context 'with a valid name and version' do + let(:mod_name) { mod.name } + let(:version_name) { version.name } + + it 'executes SELECT 1' do + expect { subject }.to exceed_query_limit(0).for_query(/^SELECT 1/) + end + + it { is_expected.to eq(true) } + end + + context 'with an invalid name' do + let(:mod_name) { 'foo/bar' } + let(:version_name) { 'baz' } + + it { is_expected.to eq(false) } + end + + context 'with an invalid version' do + let(:mod_name) { mod.name } + let(:version_name) { 'baz' } + + it { is_expected.to eq(false) } + end + end + + describe '#execute' do + subject { finder.execute } + + context 'with a valid name and version' do + let(:mod_name) { mod.name } + let(:version_name) { version.name } + + it 'executes a single query' do + expect { subject }.not_to exceed_query_limit(1) + end + + it { is_expected.to eq(package) } + end + + context 'with an invalid name' do + let(:mod_name) { 'foo/bar' } + let(:version_name) { 'baz' } + + it { is_expected.to eq(nil) } + end + + context 'with an invalid version' do + let(:mod_name) { mod.name } + let(:version_name) { 'baz' } + + it { is_expected.to eq(nil) } + end + end +end diff --git a/spec/finders/packages/maven/package_finder_spec.rb b/spec/finders/packages/maven/package_finder_spec.rb index b955c331f28..ca144292501 100644 --- a/spec/finders/packages/maven/package_finder_spec.rb +++ b/spec/finders/packages/maven/package_finder_spec.rb @@ -11,71 +11,144 @@ RSpec.describe ::Packages::Maven::PackageFinder do let(:param_path) { nil } let(:param_project) { nil } let(:param_group) { nil } - let(:finder) { described_class.new(param_path, user, project: param_project, group: param_group) } + let(:param_order_by_package_file) { false } + let(:finder) { described_class.new(param_path, user, project: param_project, group: param_group, order_by_package_file: param_order_by_package_file) } before do group.add_developer(user) end - describe '#execute!' do - subject { finder.execute! } + shared_examples 'Packages::Maven::PackageFinder examples' do + describe '#execute!' do + subject { finder.execute! } - shared_examples 'handling valid and invalid paths' do - context 'with a valid path' do - let(:param_path) { package.maven_metadatum.path } + shared_examples 'handling valid and invalid paths' do + context 'with a valid path' do + let(:param_path) { package.maven_metadatum.path } - it { is_expected.to eq(package) } + it { is_expected.to eq(package) } + end + + context 'with an invalid path' do + let(:param_path) { 'com/example/my-app/1.0-SNAPSHOT' } + + it 'raises an error' do + expect { subject }.to raise_error(ActiveRecord::RecordNotFound) + end + end end - context 'with an invalid path' do - let(:param_path) { 'com/example/my-app/1.0-SNAPSHOT' } + context 'within the project' do + let(:param_project) { project } + it_behaves_like 'handling valid and invalid paths' + end + + context 'within a group' do + let(:param_group) { group } + + context 'with maven_packages_group_level_improvements enabled' do + before do + stub_feature_flags(maven_packages_group_level_improvements: true) + expect(finder).to receive(:packages_visible_to_user).with(user, within_group: group).and_call_original + end + + it_behaves_like 'handling valid and invalid paths' + end + + context 'with maven_packages_group_level_improvements disabled' do + before do + stub_feature_flags(maven_packages_group_level_improvements: false) + expect(finder).not_to receive(:packages_visible_to_user) + end + + it_behaves_like 'handling valid and invalid paths' + end + end + + context 'across all projects' do it 'raises an error' do expect { subject }.to raise_error(ActiveRecord::RecordNotFound) end end - end - context 'within the project' do - let(:param_project) { project } + context 'versionless maven-metadata.xml package' do + let_it_be(:sub_group1) { create(:group, parent: group) } + let_it_be(:sub_group2) { create(:group, parent: group) } + let_it_be(:project1) { create(:project, group: sub_group1) } + let_it_be(:project2) { create(:project, group: sub_group2) } + let_it_be(:project3) { create(:project, group: sub_group1) } + let_it_be(:package_name) { 'foo' } + let_it_be(:package1) { create(:maven_package, project: project1, name: package_name, version: nil) } + let_it_be(:package2) { create(:maven_package, project: project2, name: package_name, version: nil) } + let_it_be(:package3) { create(:maven_package, project: project3, name: package_name, version: nil) } + + let(:param_group) { group } + let(:param_path) { package_name } + + before do + sub_group1.add_developer(user) + sub_group2.add_developer(user) + # the package with the most recently published file should be returned + create(:package_file, :xml, package: package2) + end - it_behaves_like 'handling valid and invalid paths' - end + context 'with maven_packages_group_level_improvements enabled' do + before do + stub_feature_flags(maven_packages_group_level_improvements: true) + expect(finder).not_to receive(:versionless_package?) + end - context 'within a group' do - let(:param_group) { group } + context 'without order by package file' do + it { is_expected.to eq(package3) } + end - it_behaves_like 'handling valid and invalid paths' - end + context 'with order by package file' do + let(:param_order_by_package_file) { true } + + it { is_expected.to eq(package2) } + end + end + + context 'with maven_packages_group_level_improvements disabled' do + before do + stub_feature_flags(maven_packages_group_level_improvements: false) + expect(finder).to receive(:versionless_package?).and_call_original + end - context 'across all projects' do - it 'raises an error' do - expect { subject }.to raise_error(ActiveRecord::RecordNotFound) + context 'without order by package file' do + it { is_expected.to eq(package2) } + end + + context 'with order by package file' do + let(:param_order_by_package_file) { true } + + it { is_expected.to eq(package2) } + end + end end end + end + + context 'when the maven_metadata_by_path_with_optimization_fence feature flag is off' do + before do + stub_feature_flags(maven_metadata_by_path_with_optimization_fence: false) + end - context 'versionless maven-metadata.xml package' do - let_it_be(:sub_group1) { create(:group, parent: group) } - let_it_be(:sub_group2) { create(:group, parent: group) } - let_it_be(:project1) { create(:project, group: sub_group1) } - let_it_be(:project2) { create(:project, group: sub_group2) } - let_it_be(:project3) { create(:project, group: sub_group1) } - let_it_be(:package_name) { 'foo' } - let_it_be(:package1) { create(:maven_package, project: project1, name: package_name, version: nil) } - let_it_be(:package2) { create(:maven_package, project: project2, name: package_name, version: nil) } - let_it_be(:package3) { create(:maven_package, project: project3, name: package_name, version: nil) } - - let(:param_group) { group } - let(:param_path) { package_name } - - before do - sub_group1.add_developer(user) - sub_group2.add_developer(user) - # the package with the most recently published file should be returned - create(:package_file, :xml, package: package2) - end + it_behaves_like 'Packages::Maven::PackageFinder examples' + end + + context 'when the maven_metadata_by_path_with_optimization_fence feature flag is on' do + before do + stub_feature_flags(maven_metadata_by_path_with_optimization_fence: true) + end + + it_behaves_like 'Packages::Maven::PackageFinder examples' + + it 'uses CTE in the query' do + sql = described_class.new('some_path', user, group: group).send(:packages_with_path).to_sql - it { is_expected.to eq(package2) } + expect(sql).to include('WITH "maven_metadata_by_path" AS') end end end diff --git a/spec/finders/pending_todos_finder_spec.rb b/spec/finders/pending_todos_finder_spec.rb index 10d3c2905be..b17915f0d59 100644 --- a/spec/finders/pending_todos_finder_spec.rb +++ b/spec/finders/pending_todos_finder_spec.rb @@ -4,13 +4,15 @@ require 'spec_helper' RSpec.describe PendingTodosFinder do let(:user) { create(:user) } + let(:user2) { create(:user) } + let(:users) { [user, user2] } describe '#execute' do it 'returns only pending todos' do create(:todo, :done, user: user) todo = create(:todo, :pending, user: user) - todos = described_class.new(user).execute + todos = described_class.new(users).execute expect(todos).to eq([todo]) end @@ -22,7 +24,7 @@ RSpec.describe PendingTodosFinder do create(:todo, :pending, user: user, project: project2) todo = create(:todo, :pending, user: user, project: project1) - todos = described_class.new(user, project_id: project1.id).execute + todos = described_class.new(users, project_id: project1.id).execute expect(todos).to eq([todo]) end @@ -34,7 +36,7 @@ RSpec.describe PendingTodosFinder do create(:todo, :pending, user: user, target: note) - todos = described_class.new(user, target_id: issue.id).execute + todos = described_class.new(users, target_id: issue.id).execute expect(todos).to eq([todo]) end @@ -46,7 +48,7 @@ RSpec.describe PendingTodosFinder do create(:todo, :pending, user: user, target: note) - todos = described_class.new(user, target_type: issue.class.name).execute + todos = described_class.new(users, target_type: issue.class.name).execute expect(todos).to eq([todo]) end @@ -55,7 +57,7 @@ RSpec.describe PendingTodosFinder do create(:todo, :pending, user: user, commit_id: '456') todo = create(:todo, :pending, user: user, commit_id: '123') - todos = described_class.new(user, commit_id: '123').execute + todos = described_class.new(users, commit_id: '123').execute expect(todos).to eq([todo]) end diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb index 4d9ff30daba..a178261e899 100644 --- a/spec/finders/projects_finder_spec.rb +++ b/spec/finders/projects_finder_spec.rb @@ -129,6 +129,12 @@ RSpec.describe ProjectsFinder do it { is_expected.to eq([public_project]) } end + + context 'as string' do + let(:params) { { visibility_level: Gitlab::VisibilityLevel::INTERNAL.to_s } } + + it { is_expected.to eq([internal_project]) } + end end describe 'filter by tags' do diff --git a/spec/finders/repositories/branch_names_finder_spec.rb b/spec/finders/repositories/branch_names_finder_spec.rb new file mode 100644 index 00000000000..4d8bfcc0f20 --- /dev/null +++ b/spec/finders/repositories/branch_names_finder_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Repositories::BranchNamesFinder do + let(:project) { create(:project, :repository) } + + let(:branch_names_finder) { described_class.new(project.repository, search: 'conflict-*') } + + describe '#execute' do + subject(:execute) { branch_names_finder.execute } + + it 'filters branch names' do + expect(execute).to contain_exactly( + 'conflict-binary-file', + 'conflict-resolvable', + 'conflict-contains-conflict-markers', + 'conflict-missing-side', + 'conflict-start', + 'conflict-non-utf8', + 'conflict-too-large' + ) + end + end +end diff --git a/spec/finders/repositories/changelog_tag_finder_spec.rb b/spec/finders/repositories/changelog_tag_finder_spec.rb new file mode 100644 index 00000000000..cd79beb3e9e --- /dev/null +++ b/spec/finders/repositories/changelog_tag_finder_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Repositories::ChangelogTagFinder do + let(:project) { build_stubbed(:project) } + let(:finder) { described_class.new(project) } + + describe '#execute' do + context 'when the regular expression is invalid' do + it 'raises Gitlab::Changelog::Error' do + expect { described_class.new(project, regex: 'foo+*').execute('1.2.3') } + .to raise_error(Gitlab::Changelog::Error) + end + end + + context 'when there is a previous tag' do + it 'returns the previous tag' do + tag1 = double(:tag1, name: 'v1.0.0') + tag2 = double(:tag2, name: 'v1.1.0') + tag3 = double(:tag3, name: 'v2.0.0') + tag4 = double(:tag4, name: '0.9.0') + tag5 = double(:tag5, name: 'v0.8.0-pre1') + tag6 = double(:tag6, name: 'v0.7.0') + tag7 = double(:tag7, name: '0.5.0+42.ee.0') + + allow(project.repository) + .to receive(:tags) + .and_return([tag1, tag3, tag2, tag4, tag5, tag6, tag7]) + + expect(finder.execute('2.1.0')).to eq(tag3) + expect(finder.execute('2.0.0')).to eq(tag2) + expect(finder.execute('1.5.0')).to eq(tag2) + expect(finder.execute('1.0.1')).to eq(tag1) + expect(finder.execute('1.0.0')).to eq(tag4) + expect(finder.execute('0.9.0')).to eq(tag6) + expect(finder.execute('0.6.0')).to eq(tag7) + end + end + + context 'when there is no previous tag' do + it 'returns nil' do + tag1 = double(:tag1, name: 'v1.0.0') + tag2 = double(:tag2, name: 'v1.1.0') + + allow(project.repository) + .to receive(:tags) + .and_return([tag1, tag2]) + + expect(finder.execute('1.0.0')).to be_nil + end + end + end +end diff --git a/spec/finders/repositories/previous_tag_finder_spec.rb b/spec/finders/repositories/previous_tag_finder_spec.rb deleted file mode 100644 index b332dd158d1..00000000000 --- a/spec/finders/repositories/previous_tag_finder_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Repositories::PreviousTagFinder do - let(:project) { build_stubbed(:project) } - let(:finder) { described_class.new(project) } - - describe '#execute' do - context 'when there is a previous tag' do - it 'returns the previous tag' do - tag1 = double(:tag1, name: 'v1.0.0') - tag2 = double(:tag2, name: 'v1.1.0') - tag3 = double(:tag3, name: 'v2.0.0') - tag4 = double(:tag4, name: '0.9.0') - tag5 = double(:tag5, name: 'v0.8.0-pre1') - tag6 = double(:tag6, name: 'v0.7.0') - - allow(project.repository) - .to receive(:tags) - .and_return([tag1, tag3, tag2, tag4, tag5, tag6]) - - expect(finder.execute('2.1.0')).to eq(tag3) - expect(finder.execute('2.0.0')).to eq(tag2) - expect(finder.execute('1.5.0')).to eq(tag2) - expect(finder.execute('1.0.1')).to eq(tag1) - expect(finder.execute('1.0.0')).to eq(tag4) - expect(finder.execute('0.9.0')).to eq(tag6) - end - end - - context 'when there is no previous tag' do - it 'returns nil' do - tag1 = double(:tag1, name: 'v1.0.0') - tag2 = double(:tag2, name: 'v1.1.0') - - allow(project.repository) - .to receive(:tags) - .and_return([tag1, tag2]) - - expect(finder.execute('1.0.0')).to be_nil - end - end - end -end diff --git a/spec/finders/user_group_notification_settings_finder_spec.rb b/spec/finders/user_group_notification_settings_finder_spec.rb index 453da691866..b9d800d8e55 100644 --- a/spec/finders/user_group_notification_settings_finder_spec.rb +++ b/spec/finders/user_group_notification_settings_finder_spec.rb @@ -129,4 +129,37 @@ RSpec.describe UserGroupNotificationSettingsFinder do end end end + + context 'preloading `emails_disabled`' do + let_it_be(:root_group) { create(:group) } + let_it_be(:sub_group) { create(:group, parent: root_group) } + let_it_be(:sub_sub_group) { create(:group, parent: sub_group) } + + let_it_be(:another_root_group) { create(:group) } + let_it_be(:sub_group_with_emails_disabled) { create(:group, emails_disabled: true, parent: another_root_group) } + let_it_be(:another_sub_sub_group) { create(:group, parent: sub_group_with_emails_disabled) } + + let_it_be(:root_group_with_emails_disabled) { create(:group, emails_disabled: true) } + let_it_be(:group) { create(:group, parent: root_group_with_emails_disabled) } + + let(:groups) { Group.where(id: [sub_sub_group, another_sub_sub_group, group]) } + + before do + described_class.new(user, groups).execute + end + + it 'preloads the `group.emails_disabled` method' do + recorder = ActiveRecord::QueryRecorder.new do + groups.each(&:emails_disabled?) + end + + expect(recorder.count).to eq(0) + end + + it 'preloads the `group.emails_disabled` method correctly' do + groups.each do |group| + expect(group.emails_disabled?).to eq(Group.find(group.id).emails_disabled?) # compare the memoized and the freshly loaded value + end + end + end end diff --git a/spec/fixtures/api/schemas/entities/member.json b/spec/fixtures/api/schemas/entities/member.json index 03b1872632e..f06687f9809 100644 --- a/spec/fixtures/api/schemas/entities/member.json +++ b/spec/fixtures/api/schemas/entities/member.json @@ -8,6 +8,7 @@ "requested_at", "source", "valid_roles", + "type", "can_update", "can_remove", "is_direct_member" @@ -40,6 +41,7 @@ "additionalProperties": false }, "valid_roles": { "type": "object" }, + "type": { "type": "string" }, "created_by": { "type": "object", "required": ["name", "web_url"], diff --git a/spec/fixtures/api/schemas/entities/member_user.json b/spec/fixtures/api/schemas/entities/member_user.json index ebd26bfaaaa..41a1e510de5 100644 --- a/spec/fixtures/api/schemas/entities/member_user.json +++ b/spec/fixtures/api/schemas/entities/member_user.json @@ -18,6 +18,5 @@ }, "additionalProperties": false } - }, - "additionalProperties": false + } } diff --git a/spec/fixtures/api/schemas/external_validation.json b/spec/fixtures/api/schemas/external_validation.json index 1bd00a2e6fc..3ff71626cc0 100644 --- a/spec/fixtures/api/schemas/external_validation.json +++ b/spec/fixtures/api/schemas/external_validation.json @@ -11,11 +11,13 @@ "type": "object", "required": [ "id", - "path" + "path", + "created_at" ], "properties": { "id": { "type": "integer" }, - "path": { "type": "string" } + "path": { "type": "string" }, + "created_at": { "type": ["string", "null"], "format": "date-time" } } }, "user": { @@ -23,12 +25,14 @@ "required": [ "id", "username", - "email" + "email", + "created_at" ], "properties": { "id": { "type": "integer" }, "username": { "type": "string" }, - "email": { "type": "string" } + "email": { "type": "string" }, + "created_at": { "type": ["string", "null"], "format": "date-time" } } }, "pipeline": { @@ -70,6 +74,5 @@ } } } - }, - "additionalProperties": false + } } diff --git a/spec/fixtures/api/schemas/graphql/packages/package_conan_metadata.json b/spec/fixtures/api/schemas/graphql/packages/package_conan_metadata.json new file mode 100644 index 00000000000..31bb861ced5 --- /dev/null +++ b/spec/fixtures/api/schemas/graphql/packages/package_conan_metadata.json @@ -0,0 +1,37 @@ +{ + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "createdAt", + "updatedAt", + "packageUsername", + "packageChannel", + "recipe", + "recipePath", + "packageName" + ], + "properties": { + "id": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "package_username": { + "type": "string" + }, + "package_channel": { + "type": "string" + }, + "recipe": { + "type": "string" + }, + "recipe_path": { + "type": "string" + } + } +} diff --git a/spec/fixtures/api/schemas/graphql/packages/package_details.json b/spec/fixtures/api/schemas/graphql/packages/package_details.json index d2e2e65db54..87b173eefc7 100644 --- a/spec/fixtures/api/schemas/graphql/packages/package_details.json +++ b/spec/fixtures/api/schemas/graphql/packages/package_details.json @@ -2,8 +2,17 @@ "type": "object", "additionalProperties": false, "required": [ - "id", "name", "createdAt", "updatedAt", "version", "packageType", - "project", "tags", "pipelines", "versions", "metadata" + "id", + "name", + "createdAt", + "updatedAt", + "version", + "packageType", + "project", + "tags", + "pipelines", + "versions", + "metadata" ], "properties": { "id": { @@ -23,7 +32,18 @@ }, "packageType": { "type": ["string"], - "enum": ["MAVEN", "NPM", "CONAN", "NUGET", "PYPI", "COMPOSER", "GENERIC", "GOLANG", "DEBIAN"] + "enum": [ + "MAVEN", + "NPM", + "CONAN", + "NUGET", + "PYPI", + "COMPOSER", + "GENERIC", + "GOLANG", + "RUBYGEMS", + "DEBIAN" + ] }, "tags": { "type": "object", @@ -59,8 +79,18 @@ "metadata": { "anyOf": [ { "$ref": "./package_composer_metadata.json" }, + { "$ref": "./package_conan_metadata.json" }, { "type": "null" } ] + }, + "packageFiles": { + "type": "object", + "additionalProperties": false, + "properties": { + "pageInfo": { "type": "object" }, + "edges": { "type": "array" }, + "nodes": { "type": "array" } + } } } } diff --git a/spec/fixtures/api/schemas/public_api/v4/user/public.json b/spec/fixtures/api/schemas/public_api/v4/user/public.json index faa126b65f2..ee848eda9ed 100644 --- a/spec/fixtures/api/schemas/public_api/v4/user/public.json +++ b/spec/fixtures/api/schemas/public_api/v4/user/public.json @@ -70,6 +70,7 @@ "can_create_group": { "type": "boolean" }, "can_create_project": { "type": "boolean" }, "two_factor_enabled": { "type": "boolean" }, - "external": { "type": "boolean" } + "external": { "type": "boolean" }, + "commit_email": { "type": "string" } } } diff --git a/spec/fixtures/ce_sample_schema.json b/spec/fixtures/ce_sample_schema.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/spec/fixtures/config/mail_room_enabled_ms_graph.yml b/spec/fixtures/config/mail_room_enabled_ms_graph.yml new file mode 100644 index 00000000000..791760e1dfd --- /dev/null +++ b/spec/fixtures/config/mail_room_enabled_ms_graph.yml @@ -0,0 +1,26 @@ +test: + incoming_email: + enabled: true + address: "gitlab-incoming+%{key}@gmail.com" + user: "gitlab-incoming@gmail.com" + mailbox: "inbox" + expunge_deleted: true + inbox_method: "microsoft_graph" + inbox_options: + tenant_id: "12345" + client_id: "MY-CLIENT-ID" + client_secret: "MY-CLIENT-SECRET" + poll_interval: 60 + + service_desk_email: + enabled: true + address: "gitlab-incoming+%{key}@gmail.com" + user: "gitlab-incoming@gmail.com" + mailbox: "inbox" + expunge_deleted: true + inbox_method: "microsoft_graph" + inbox_options: + tenant_id: "12345" + client_id: "MY-CLIENT-ID" + client_secret: "MY-CLIENT-SECRET" + poll_interval: 60 diff --git a/spec/fixtures/emails/update_commands_only.eml b/spec/fixtures/emails/update_commands_only.eml new file mode 100644 index 00000000000..9442d9423f0 --- /dev/null +++ b/spec/fixtures/emails/update_commands_only.eml @@ -0,0 +1,22 @@ +Return-Path: +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 ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; 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 +To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo +Message-ID: +In-Reply-To: +References: +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 + +/close diff --git a/spec/fixtures/lib/generators/gitlab/usage_metric_definition_generator/sample_metric.yml b/spec/fixtures/lib/generators/gitlab/usage_metric_definition_generator/sample_metric.yml new file mode 100644 index 00000000000..90d395e1eda --- /dev/null +++ b/spec/fixtures/lib/generators/gitlab/usage_metric_definition_generator/sample_metric.yml @@ -0,0 +1,21 @@ +--- +# See Usage Ping metrics dictionary docs https://docs.gitlab.com/ee/development/usage_ping/metrics_dictionary.html +key_path: counts_weekly.test_metric +description: +product_section: +product_stage: +product_group: +product_category: +value_type: number +status: implemented +milestone: "13.9" +introduced_by_url: +time_frame: 7d +data_source: +distribution: +- ce +# Add here corresponding tiers +# tier: +# - free +# - premium +# - ultimate diff --git a/spec/fixtures/lib/generators/gitlab/usage_metric_definition_generator/sample_metric_with_name_suggestions.yml b/spec/fixtures/lib/generators/gitlab/usage_metric_definition_generator/sample_metric_with_name_suggestions.yml new file mode 100644 index 00000000000..47fc1d7e376 --- /dev/null +++ b/spec/fixtures/lib/generators/gitlab/usage_metric_definition_generator/sample_metric_with_name_suggestions.yml @@ -0,0 +1,22 @@ +--- +# See Usage Ping metrics dictionary docs https://docs.gitlab.com/ee/development/usage_ping/metrics_dictionary.html +key_path: counts_weekly.test_metric +name: test metric name +description: +product_section: +product_stage: +product_group: +product_category: +value_type: number +status: implemented +milestone: "13.9" +introduced_by_url: +time_frame: 7d +data_source: +distribution: +- ce +# Add here corresponding tiers +# tier: +# - free +# - premium +# - ultimate diff --git a/spec/fixtures/lib/gitlab/performance_bar/peek_data.json b/spec/fixtures/lib/gitlab/performance_bar/peek_data.json index 8e207b69ecb..c60e787ddb1 100644 --- a/spec/fixtures/lib/gitlab/performance_bar/peek_data.json +++ b/spec/fixtures/lib/gitlab/performance_bar/peek_data.json @@ -13,6 +13,8 @@ "duration": 1.096, "sql": "SELECT COUNT(*) FROM ((SELECT \"badges\".* FROM \"badges\" WHERE \"badges\".\"type\" = 'ProjectBadge' AND \"badges\".\"project_id\" = 8)\nUNION\n(SELECT \"badges\".* FROM \"badges\" WHERE \"badges\".\"type\" = 'GroupBadge' AND \"badges\".\"group_id\" IN (SELECT \"namespaces\".\"id\" FROM \"namespaces\" WHERE \"namespaces\".\"type\" = 'Group' AND \"namespaces\".\"id\" = 28))) badges", "backtrace": [ + "ee/lib/ee/peek/views/active_record.rb:11:in `generate_detail'", + "lib/peek/views/active_record.rb:42:in `block in setup_subscribers'", "lib/gitlab/pagination/offset_pagination.rb:53:in `add_pagination_headers'", "lib/gitlab/pagination/offset_pagination.rb:15:in `block in paginate'", "lib/gitlab/pagination/offset_pagination.rb:14:in `tap'", diff --git a/spec/fixtures/packages/rubygems/package-0.0.1.gem b/spec/fixtures/packages/rubygems/package-0.0.1.gem index 2143ef408ac..658ef4ee25f 100644 Binary files a/spec/fixtures/packages/rubygems/package-0.0.1.gem and b/spec/fixtures/packages/rubygems/package-0.0.1.gem differ diff --git a/spec/fixtures/packages/rubygems/package.gem b/spec/fixtures/packages/rubygems/package.gem new file mode 100644 index 00000000000..658ef4ee25f Binary files /dev/null and b/spec/fixtures/packages/rubygems/package.gem differ diff --git a/spec/fixtures/packages/rubygems/package.gemspec b/spec/fixtures/packages/rubygems/package.gemspec index bb87c47f5dc..ea03414cc6f 100644 --- a/spec/fixtures/packages/rubygems/package.gemspec +++ b/spec/fixtures/packages/rubygems/package.gemspec @@ -1,15 +1,42 @@ # frozen_string_literal: true Gem::Specification.new do |s| - s.name = %q{package} - s.authors = ["Tanuki Steve"] - s.version = "0.0.1" - s.date = %q{2011-09-29} - s.summary = %q{package is the best} - s.files = [ - "lib/package.rb" - ] + s.name = 'package' + s.authors = ['Tanuki Steve', 'Hal 9000'] + s.author = 'Tanuki Steve' + s.version = '0.0.1' + s.date = '2011-09-29' + s.summary = 'package is the best' + s.files = ['lib/test_gem.rb'] + s.require_paths = ['lib'] + + s.description = 'A test package for GitLab.' + s.email = 'tanuki@not_real.com' + s.homepage = 'https://gitlab.com/ruby-co/my-package' + s.license = 'MIT' + + s.metadata = { + 'bug_tracker_uri' => 'https://gitlab.com/ruby-co/my-package/issues', + 'changelog_uri' => 'https://gitlab.com/ruby-co/my-package/CHANGELOG.md', + 'documentation_uri' => 'https://gitlab.com/ruby-co/my-package/docs', + 'mailing_list_uri' => 'https://gitlab.com/ruby-co/my-package/mailme', + 'source_code_uri' => 'https://gitlab.com/ruby-co/my-package' + } + + s.bindir = 'bin' + s.executables = ['rake'] + s.extensions = ['ext/foo.rb'] + s.extra_rdoc_files = ['README.md', 'doc/userguide.md'] + s.platform = Gem::Platform::RUBY + s.post_install_message = 'Installed, thank you!' + s.rdoc_options = ['--main', 'README.md'] s.required_ruby_version = '>= 2.7.0' - s.rubygems_version = '>= 1.8.11' - s.require_paths = ["lib"] + s.required_rubygems_version = '>= 1.8.11' + s.requirements = 'A high powered server or calculator' + s.rubygems_version = '1.8.09' + + s.add_dependency 'dependency_1', '~> 1.2.3' + s.add_dependency 'dependency_2', '3.0.0' + s.add_dependency 'dependency_3', '>= 1.0.0' + s.add_dependency 'dependency_4' end diff --git a/spec/fixtures/security_reports/master/gl-sast-report.json b/spec/fixtures/security_reports/master/gl-sast-report.json index ab610945508..9da9fdc3832 100644 --- a/spec/fixtures/security_reports/master/gl-sast-report.json +++ b/spec/fixtures/security_reports/master/gl-sast-report.json @@ -1,49 +1,6 @@ { - "version": "1.2", + "version": "14.0.0", "vulnerabilities": [ - { - "category": "sast", - "message": "Probable insecure usage of temp file/directory.", - "cve": "python/hardcoded/hardcoded-tmp.py:52865813c884a507be1f152d654245af34aba8a391626d01f1ab6d3f52ec8779:B108", - "severity": "Medium", - "confidence": "Medium", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/hardcoded/hardcoded-tmp.py", - "start_line": 1, - "end_line": 1 - }, - "identifiers": [ - { - "type": "bandit_test_id", - "name": "Bandit Test ID B108", - "value": "B108", - "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html" - } - ], - "priority": "Medium", - "file": "python/hardcoded/hardcoded-tmp.py", - "line": 1, - "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html", - "tool": "bandit", - "tracking": { - "type": "source", - "items": [ - { - "file": "python/hardcoded/hardcoded-tmp.py", - "start_line": 1, - "end_line": 1, - "fingerprints": [ - { "algorithm": "hash", "value": "HASHVALUE" }, - { "algorithm": "scope_offset", "value": "python/hardcoded/hardcoded-tmp.py:ClassA:method_b:2" } - ] - } - ] - } - }, { "category": "sast", "name": "Predictable pseudorandom number generator", @@ -69,20 +26,15 @@ "value": "PREDICTABLE_RANDOM", "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM" } - ], - "priority": "Medium", - "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy", - "line": 47, - "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM", - "tool": "find_sec_bugs" + ] }, { "category": "sast", "name": "Predictable pseudorandom number generator", "message": "Predictable pseudorandom number generator", "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:41:PREDICTABLE_RANDOM", - "severity": "Medium", - "confidence": "Medium", + "severity": "Low", + "confidence": "Low", "scanner": { "id": "find_sec_bugs", "name": "Find Security Bugs" @@ -101,153 +53,48 @@ "value": "PREDICTABLE_RANDOM", "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM" } - ], - "priority": "Medium", - "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy", - "line": 41, - "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM", - "tool": "find_sec_bugs" - }, - { - "category": "sast", - "message": "Use of insecure MD2, MD4, or MD5 hash function.", - "cve": "python/imports/imports-aliases.py:cb203b465dffb0cb3a8e8bd8910b84b93b0a5995a938e4b903dbb0cd6ffa1254:B303", - "severity": "Medium", - "confidence": "High", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/imports/imports-aliases.py", - "start_line": 11, - "end_line": 11 - }, - "identifiers": [ - { - "type": "bandit_test_id", - "name": "Bandit Test ID B303", - "value": "B303" - } - ], - "priority": "Medium", - "file": "python/imports/imports-aliases.py", - "line": 11, - "tool": "bandit" - }, - { - "category": "sast", - "message": "Use of insecure MD2, MD4, or MD5 hash function.", - "cve": "python/imports/imports-aliases.py:a7173c43ae66bd07466632d819d450e0071e02dbf782763640d1092981f9631b:B303", - "severity": "Medium", - "confidence": "High", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/imports/imports-aliases.py", - "start_line": 12, - "end_line": 12 - }, - "identifiers": [ - { - "type": "bandit_test_id", - "name": "Bandit Test ID B303", - "value": "B303" - } - ], - "priority": "Medium", - "file": "python/imports/imports-aliases.py", - "line": 12, - "tool": "bandit" - }, - { - "category": "sast", - "message": "Use of insecure MD2, MD4, or MD5 hash function.", - "cve": "python/imports/imports-aliases.py:017017b77deb0b8369b6065947833eeea752a92ec8a700db590fece3e934cf0d:B303", - "severity": "Medium", - "confidence": "High", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/imports/imports-aliases.py", - "start_line": 13, - "end_line": 13 - }, - "identifiers": [ - { - "type": "bandit_test_id", - "name": "Bandit Test ID B303", - "value": "B303" - } - ], - "priority": "Medium", - "file": "python/imports/imports-aliases.py", - "line": 13, - "tool": "bandit" + ] }, { "category": "sast", - "message": "Use of insecure MD2, MD4, or MD5 hash function.", - "cve": "python/imports/imports-aliases.py:45fc8c53aea7b84f06bc4e590cc667678d6073c4c8a1d471177ca2146fb22db2:B303", + "name": "ECB mode is insecure", + "message": "ECB mode is insecure", + "description": "The cipher uses ECB mode, which provides poor confidentiality for encrypted data", + "cve": "ea0f905fc76f2739d5f10a1fd1e37a10:ECB_MODE:java-maven/src/main/java/com/gitlab/security_products/tests/App.java:29", "severity": "Medium", "confidence": "High", "scanner": { - "id": "bandit", - "name": "Bandit" + "id": "find_sec_bugs", + "name": "Find Security Bugs" }, "location": { - "file": "python/imports/imports-aliases.py", - "start_line": 14, - "end_line": 14 + "file": "java-maven/src/main/java/com/gitlab/security_products/tests/App.java", + "start_line": 29, + "end_line": 29, + "class": "com.gitlab.security_products.tests.App", + "method": "insecureCypher" }, "identifiers": [ { - "type": "bandit_test_id", - "name": "Bandit Test ID B303", - "value": "B303" - } - ], - "priority": "Medium", - "file": "python/imports/imports-aliases.py", - "line": 14, - "tool": "bandit" - }, - { - "category": "sast", - "message": "Pickle library appears to be in use, possible security issue.", - "cve": "python/imports/imports-aliases.py:5f200d47291e7bbd8352db23019b85453ca048dd98ea0c291260fa7d009963a4:B301", - "severity": "Medium", - "confidence": "High", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/imports/imports-aliases.py", - "start_line": 15, - "end_line": 15 - }, - "identifiers": [ + "type": "find_sec_bugs_type", + "name": "Find Security Bugs-ECB_MODE", + "value": "ECB_MODE", + "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE" + }, { - "type": "bandit_test_id", - "name": "Bandit Test ID B301", - "value": "B301" + "type": "cwe", + "name": "CWE-327", + "value": "327", + "url": "https://cwe.mitre.org/data/definitions/327.html" } - ], - "priority": "Medium", - "file": "python/imports/imports-aliases.py", - "line": 15, - "tool": "bandit" + ] }, { "category": "sast", - "name": "ECB mode is insecure", - "message": "ECB mode is insecure", - "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:ECB_MODE", + "name": "Hard coded key", + "message": "Hard coded key", + "description": "Hard coded cryptographic key found", + "cve": "102ac67e0975ecec02a056008e0faad8:HARD_CODE_KEY:scala-sbt/src/main/scala/example/Main.scala:12", "severity": "Medium", "confidence": "High", "scanner": { @@ -255,25 +102,26 @@ "name": "Find Security Bugs" }, "location": { - "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy", - "start_line": 29, - "end_line": 29, - "class": "com.gitlab.security_products.tests.App", - "method": "insecureCypher" + "file": "scala-sbt/src/main/scala/example/Main.scala", + "start_line": 12, + "end_line": 12, + "class": "example.Main$", + "method": "getBytes" }, "identifiers": [ { "type": "find_sec_bugs_type", - "name": "Find Security Bugs-ECB_MODE", - "value": "ECB_MODE", - "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE" + "name": "Find Security Bugs-HARD_CODE_KEY", + "value": "HARD_CODE_KEY", + "url": "https://find-sec-bugs.github.io/bugs.htm#HARD_CODE_KEY" + }, + { + "type": "cwe", + "name": "CWE-321", + "value": "321", + "url": "https://cwe.mitre.org/data/definitions/321.html" } - ], - "priority": "Medium", - "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy", - "line": 29, - "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE", - "tool": "find_sec_bugs" + ] }, { "category": "sast", @@ -301,693 +149,38 @@ "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY" } ], - "priority": "Medium", - "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy", - "line": 29, - "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY", - "tool": "find_sec_bugs" - }, - { - "category": "sast", - "message": "Probable insecure usage of temp file/directory.", - "cve": "python/hardcoded/hardcoded-tmp.py:63dd4d626855555b816985d82c4614a790462a0a3ada89dc58eb97f9c50f3077:B108", - "severity": "Medium", - "confidence": "Medium", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/hardcoded/hardcoded-tmp.py", - "start_line": 14, - "end_line": 14 - }, - "identifiers": [ - { - "type": "bandit_test_id", - "name": "Bandit Test ID B108", - "value": "B108", - "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html" - } - ], - "priority": "Medium", - "file": "python/hardcoded/hardcoded-tmp.py", - "line": 14, - "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html", - "tool": "bandit" - }, - { - "category": "sast", - "message": "Probable insecure usage of temp file/directory.", - "cve": "python/hardcoded/hardcoded-tmp.py:4ad6d4c40a8c263fc265f3384724014e0a4f8dd6200af83e51ff120420038031:B108", - "severity": "Medium", - "confidence": "Medium", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/hardcoded/hardcoded-tmp.py", - "start_line": 10, - "end_line": 10 - }, - "identifiers": [ - { - "type": "bandit_test_id", - "name": "Bandit Test ID B108", - "value": "B108", - "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html" - } - ], - "priority": "Medium", - "file": "python/hardcoded/hardcoded-tmp.py", - "line": 10, - "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html", - "tool": "bandit" - }, - { - "category": "sast", - "message": "Consider possible security implications associated with Popen module.", - "cve": "python/imports/imports-aliases.py:2c3e1fa1e54c3c6646e8bcfaee2518153c6799b77587ff8d9a7b0631f6d34785:B404", - "severity": "Low", - "confidence": "High", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/imports/imports-aliases.py", - "start_line": 1, - "end_line": 1 - }, - "identifiers": [ - { - "type": "bandit_test_id", - "name": "Bandit Test ID B404", - "value": "B404" - } - ], - "priority": "Low", - "file": "python/imports/imports-aliases.py", - "line": 1, - "tool": "bandit" - }, - { - "category": "sast", - "message": "Consider possible security implications associated with pickle module.", - "cve": "python/imports/imports.py:af58d07f6ad519ef5287fcae65bf1a6999448a1a3a8bc1ac2a11daa80d0b96bf:B403", - "severity": "Low", - "confidence": "High", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/imports/imports.py", - "start_line": 2, - "end_line": 2 - }, - "identifiers": [ - { - "type": "bandit_test_id", - "name": "Bandit Test ID B403", - "value": "B403" - } - ], - "priority": "Low", - "file": "python/imports/imports.py", - "line": 2, - "tool": "bandit" - }, - { - "category": "sast", - "message": "Consider possible security implications associated with subprocess module.", - "cve": "python/imports/imports.py:8de9bc98029d212db530785a5f6780cfa663548746ff228ab8fa96c5bb82f089:B404", - "severity": "Low", - "confidence": "High", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/imports/imports.py", - "start_line": 4, - "end_line": 4 - }, - "identifiers": [ - { - "type": "bandit_test_id", - "name": "Bandit Test ID B404", - "value": "B404" - } - ], - "priority": "Low", - "file": "python/imports/imports.py", - "line": 4, - "tool": "bandit" - }, - { - "category": "sast", - "message": "Possible hardcoded password: 'blerg'", - "cve": "python/hardcoded/hardcoded-passwords.py:97c30f1d76d2a88913e3ce9ae74087874d740f87de8af697a9c455f01119f633:B106", - "severity": "Low", - "confidence": "Medium", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/hardcoded/hardcoded-passwords.py", - "start_line": 22, - "end_line": 22 - }, - "identifiers": [ - { - "type": "bandit_test_id", - "name": "Bandit Test ID B106", - "value": "B106", - "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html" - } - ], - "priority": "Low", - "file": "python/hardcoded/hardcoded-passwords.py", - "line": 22, - "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html", - "tool": "bandit" - }, - { - "category": "sast", - "message": "Possible hardcoded password: 'root'", - "cve": "python/hardcoded/hardcoded-passwords.py:7431c73a0bc16d94ece2a2e75ef38f302574d42c37ac0c3c38ad0b3bf8a59f10:B105", - "severity": "Low", - "confidence": "Medium", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/hardcoded/hardcoded-passwords.py", - "start_line": 5, - "end_line": 5 - }, - "identifiers": [ - { - "type": "bandit_test_id", - "name": "Bandit Test ID B105", - "value": "B105", - "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html" - } - ], - "priority": "Low", - "file": "python/hardcoded/hardcoded-passwords.py", - "line": 5, - "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html", - "tool": "bandit" - }, - { - "category": "sast", - "message": "Possible hardcoded password: ''", - "cve": "python/hardcoded/hardcoded-passwords.py:d2d1857c27caedd49c57bfbcdc23afcc92bd66a22701fcdc632869aab4ca73ee:B105", - "severity": "Low", - "confidence": "Medium", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/hardcoded/hardcoded-passwords.py", - "start_line": 9, - "end_line": 9 - }, - "identifiers": [ - { - "type": "bandit_test_id", - "name": "Bandit Test ID B105", - "value": "B105", - "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html" - } - ], - "priority": "Low", - "file": "python/hardcoded/hardcoded-passwords.py", - "line": 9, - "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html", - "tool": "bandit" - }, - { - "category": "sast", - "message": "Possible hardcoded password: 'ajklawejrkl42348swfgkg'", - "cve": "python/hardcoded/hardcoded-passwords.py:fb3866215a61393a5c9c32a3b60e2058171a23219c353f722cbd3567acab21d2:B105", - "severity": "Low", - "confidence": "Medium", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/hardcoded/hardcoded-passwords.py", - "start_line": 13, - "end_line": 13 - }, - "identifiers": [ - { - "type": "bandit_test_id", - "name": "Bandit Test ID B105", - "value": "B105", - "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html" - } - ], - "priority": "Low", - "file": "python/hardcoded/hardcoded-passwords.py", - "line": 13, - "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html", - "tool": "bandit" - }, - { - "category": "sast", - "message": "Possible hardcoded password: 'blerg'", - "cve": "python/hardcoded/hardcoded-passwords.py:63c62a8b7e1e5224439bd26b28030585ac48741e28ca64561a6071080c560a5f:B105", - "severity": "Low", - "confidence": "Medium", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/hardcoded/hardcoded-passwords.py", - "start_line": 23, - "end_line": 23 - }, - "identifiers": [ - { - "type": "bandit_test_id", - "name": "Bandit Test ID B105", - "value": "B105", - "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html" - } - ], - "priority": "Low", - "file": "python/hardcoded/hardcoded-passwords.py", - "line": 23, - "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html", - "tool": "bandit" - }, - { - "category": "sast", - "message": "Possible hardcoded password: 'blerg'", - "cve": "python/hardcoded/hardcoded-passwords.py:4311b06d08df8fa58229b341c531da8e1a31ec4520597bdff920cd5c098d86f9:B105", - "severity": "Low", - "confidence": "Medium", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/hardcoded/hardcoded-passwords.py", - "start_line": 24, - "end_line": 24 - }, - "identifiers": [ - { - "type": "bandit_test_id", - "name": "Bandit Test ID B105", - "value": "B105", - "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html" - } - ], - "priority": "Low", - "file": "python/hardcoded/hardcoded-passwords.py", - "line": 24, - "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html", - "tool": "bandit" - }, - { - "category": "sast", - "message": "Consider possible security implications associated with subprocess module.", - "cve": "python/imports/imports-function.py:5858400c2f39047787702de44d03361ef8d954c9d14bd54ee1c2bef9e6a7df93:B404", - "severity": "Low", - "confidence": "High", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/imports/imports-function.py", - "start_line": 4, - "end_line": 4 - }, - "identifiers": [ - { - "type": "bandit_test_id", - "name": "Bandit Test ID B404", - "value": "B404" - } - ], - "priority": "Low", - "file": "python/imports/imports-function.py", - "line": 4, - "tool": "bandit" - }, - { - "category": "sast", - "message": "Consider possible security implications associated with pickle module.", - "cve": "python/imports/imports-function.py:dbda3cf4190279d30e0aad7dd137eca11272b0b225e8af4e8bf39682da67d956:B403", - "severity": "Low", - "confidence": "High", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/imports/imports-function.py", - "start_line": 2, - "end_line": 2 - }, - "identifiers": [ - { - "type": "bandit_test_id", - "name": "Bandit Test ID B403", - "value": "B403" - } - ], - "priority": "Low", - "file": "python/imports/imports-function.py", - "line": 2, - "tool": "bandit" - }, - { - "category": "sast", - "message": "Consider possible security implications associated with Popen module.", - "cve": "python/imports/imports-from.py:eb8a0db9cd1a8c1ab39a77e6025021b1261cc2a0b026b2f4a11fca4e0636d8dd:B404", - "severity": "Low", - "confidence": "High", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/imports/imports-from.py", - "start_line": 7, - "end_line": 7 - }, - "identifiers": [ - { - "type": "bandit_test_id", - "name": "Bandit Test ID B404", - "value": "B404" - } - ], - "priority": "Low", - "file": "python/imports/imports-from.py", - "line": 7, - "tool": "bandit" - }, - { - "category": "sast", - "message": "subprocess call with shell=True seems safe, but may be changed in the future, consider rewriting without shell", - "cve": "python/imports/imports-aliases.py:f99f9721e27537fbcb6699a4cf39c6740d6234d2c6f06cfc2d9ea977313c483d:B602", - "severity": "Low", - "confidence": "High", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/imports/imports-aliases.py", - "start_line": 9, - "end_line": 9 - }, - "identifiers": [ - { - "type": "bandit_test_id", - "name": "Bandit Test ID B602", - "value": "B602", - "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html" - } - ], - "priority": "Low", - "file": "python/imports/imports-aliases.py", - "line": 9, - "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html", - "tool": "bandit" - }, - { - "category": "sast", - "message": "Consider possible security implications associated with subprocess module.", - "cve": "python/imports/imports-from.py:332a12ab1146698f614a905ce6a6a5401497a12281aef200e80522711c69dcf4:B404", - "severity": "Low", - "confidence": "High", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/imports/imports-from.py", - "start_line": 6, - "end_line": 6 - }, - "identifiers": [ - { - "type": "bandit_test_id", - "name": "Bandit Test ID B404", - "value": "B404" - } - ], - "priority": "Low", - "file": "python/imports/imports-from.py", - "line": 6, - "tool": "bandit" - }, - { - "category": "sast", - "message": "Consider possible security implications associated with Popen module.", - "cve": "python/imports/imports-from.py:0a48de4a3d5348853a03666cb574697e3982998355e7a095a798bd02a5947276:B404", - "severity": "Low", - "confidence": "High", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/imports/imports-from.py", - "start_line": 1, - "end_line": 2 - }, - "identifiers": [ - { - "type": "bandit_test_id", - "name": "Bandit Test ID B404", - "value": "B404" - } - ], - "priority": "Low", - "file": "python/imports/imports-from.py", - "line": 1, - "tool": "bandit" - }, - { - "category": "sast", - "message": "Consider possible security implications associated with pickle module.", - "cve": "python/imports/imports-aliases.py:51b71661dff994bde3529639a727a678c8f5c4c96f00d300913f6d5be1bbdf26:B403", - "severity": "Low", - "confidence": "High", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/imports/imports-aliases.py", - "start_line": 7, - "end_line": 8 - }, - "identifiers": [ - { - "type": "bandit_test_id", - "name": "Bandit Test ID B403", - "value": "B403" - } - ], - "priority": "Low", - "file": "python/imports/imports-aliases.py", - "line": 7, - "tool": "bandit" - }, - { - "category": "sast", - "message": "Consider possible security implications associated with loads module.", - "cve": "python/imports/imports-aliases.py:6ff02aeb3149c01ab68484d794a94f58d5d3e3bb0d58557ef4153644ea68ea54:B403", - "severity": "Low", - "confidence": "High", - "scanner": { - "id": "bandit", - "name": "Bandit" - }, - "location": { - "file": "python/imports/imports-aliases.py", - "start_line": 6, - "end_line": 6 - }, - "identifiers": [ - { - "type": "bandit_test_id", - "name": "Bandit Test ID B403", - "value": "B403" - } - ], - "priority": "Low", - "file": "python/imports/imports-aliases.py", - "line": 6, - "tool": "bandit" - }, - { - "category": "sast", - "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)", - "cve": "c/subdir/utils.c:b466873101951fe96e1332f6728eb7010acbbd5dfc3b65d7d53571d091a06d9e:CWE-119!/CWE-120", - "confidence": "Low", - "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length", - "scanner": { - "id": "flawfinder", - "name": "Flawfinder" - }, - "location": { - "file": "c/subdir/utils.c", - "start_line": 4 - }, - "identifiers": [ - { - "type": "flawfinder_func_name", - "name": "Flawfinder - char", - "value": "char" - }, - { - "type": "cwe", - "name": "CWE-119", - "value": "119", - "url": "https://cwe.mitre.org/data/definitions/119.html" - }, - { - "type": "cwe", - "name": "CWE-120", - "value": "120", - "url": "https://cwe.mitre.org/data/definitions/120.html" - } - ], - "file": "c/subdir/utils.c", - "line": 4, - "url": "https://cwe.mitre.org/data/definitions/119.html", - "tool": "flawfinder" - }, - { - "category": "sast", - "message": "Check when opening files - can an attacker redirect it (via symlinks), force the opening of special file type (e.g., device files), move things around to create a race condition, control its ancestors, or change its contents? (CWE-362)", - "cve": "c/subdir/utils.c:bab681140fcc8fc3085b6bba74081b44ea145c1c98b5e70cf19ace2417d30770:CWE-362", - "confidence": "Low", - "scanner": { - "id": "flawfinder", - "name": "Flawfinder" - }, - "location": { - "file": "c/subdir/utils.c", - "start_line": 8 - }, - "identifiers": [ - { - "type": "flawfinder_func_name", - "name": "Flawfinder - fopen", - "value": "fopen" - }, - { - "type": "cwe", - "name": "CWE-362", - "value": "362", - "url": "https://cwe.mitre.org/data/definitions/362.html" - } - ], - "file": "c/subdir/utils.c", - "line": 8, - "url": "https://cwe.mitre.org/data/definitions/362.html", - "tool": "flawfinder" - }, - { - "category": "sast", - "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)", - "cve": "cplusplus/src/hello.cpp:c8c6dd0afdae6814194cf0930b719f757ab7b379cf8f261e7f4f9f2f323a818a:CWE-119!/CWE-120", - "confidence": "Low", - "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length", - "scanner": { - "id": "flawfinder", - "name": "Flawfinder" - }, - "location": { - "file": "cplusplus/src/hello.cpp", - "start_line": 6 - }, - "identifiers": [ - { - "type": "flawfinder_func_name", - "name": "Flawfinder - char", - "value": "char" - }, - { - "type": "cwe", - "name": "CWE-119", - "value": "119", - "url": "https://cwe.mitre.org/data/definitions/119.html" - }, - { - "type": "cwe", - "name": "CWE-120", - "value": "120", - "url": "https://cwe.mitre.org/data/definitions/120.html" - } - ], - "file": "cplusplus/src/hello.cpp", - "line": 6, - "url": "https://cwe.mitre.org/data/definitions/119.html", - "tool": "flawfinder" - }, - { - "category": "sast", - "message": "Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120)", - "cve": "cplusplus/src/hello.cpp:331c04062c4fe0c7c486f66f59e82ad146ab33cdd76ae757ca41f392d568cbd0:CWE-120", - "confidence": "Low", - "solution": "Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused)", - "scanner": { - "id": "flawfinder", - "name": "Flawfinder" - }, - "location": { - "file": "cplusplus/src/hello.cpp", - "start_line": 7 - }, - "identifiers": [ - { - "type": "flawfinder_func_name", - "name": "Flawfinder - strcpy", - "value": "strcpy" - }, - { - "type": "cwe", - "name": "CWE-120", - "value": "120", - "url": "https://cwe.mitre.org/data/definitions/120.html" - } - ], - "file": "cplusplus/src/hello.cpp", - "line": 7, - "url": "https://cwe.mitre.org/data/definitions/120.html", - "tool": "flawfinder" + "tracking": { + "type": "source", + "items": [ + { + "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy", + "start_line": 47, + "end_line": 47, + "signatures": [ + { + "algorithm": "hash", + "value": "HASHVALUE" + }, + { + "algorithm": "scope_offset", + "value": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:App[0]:insecureCypher[0]:2" + } + ] + } + ] + } } ], "remediations": [], "scan": { "scanner": { - "id": "gosec", - "name": "Gosec", - "url": "https://github.com/securego/gosec", + "id": "find_sec_bugs", + "name": "Find Security Bugs", + "url": "https://spotbugs.github.io", "vendor": { "name": "GitLab" }, - "version": "2.3.0" + "version": "4.0.2" }, "type": "sast", "status": "success", diff --git a/spec/fixtures/unsafe_javascript.xml b/spec/fixtures/unsafe_javascript.xml new file mode 100644 index 00000000000..0c23d1e07db --- /dev/null +++ b/spec/fixtures/unsafe_javascript.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/spec/frontend/__helpers__/experimentation_helper.js b/spec/frontend/__helpers__/experimentation_helper.js index c08c25155e8..7a2ef61216a 100644 --- a/spec/frontend/__helpers__/experimentation_helper.js +++ b/spec/frontend/__helpers__/experimentation_helper.js @@ -12,3 +12,16 @@ export function withGonExperiment(experimentKey, value = true) { window.gon = origGon; }); } +// This helper is for specs that use `gitlab-experiment` utilities, which have a different schema that gets pushed via Gon compared to `Experimentation Module` +export function assignGitlabExperiment(experimentKey, variant) { + let origGon; + + beforeEach(() => { + origGon = window.gon; + window.gon = { experiment: { [experimentKey]: { variant } } }; + }); + + afterEach(() => { + window.gon = origGon; + }); +} diff --git a/spec/frontend/__helpers__/mock_apollo_helper.js b/spec/frontend/__helpers__/mock_apollo_helper.js index 914cce1d662..bd97a06071a 100644 --- a/spec/frontend/__helpers__/mock_apollo_helper.js +++ b/spec/frontend/__helpers__/mock_apollo_helper.js @@ -2,11 +2,15 @@ import { InMemoryCache } from 'apollo-cache-inmemory'; import { createMockClient } from 'mock-apollo-client'; import VueApollo from 'vue-apollo'; -export default (handlers = [], resolvers = {}) => { - const fragmentMatcher = { match: () => true }; +const defaultCacheOptions = { + fragmentMatcher: { match: () => true }, + addTypename: false, +}; + +export default (handlers = [], resolvers = {}, cacheOptions = {}) => { const cache = new InMemoryCache({ - fragmentMatcher, - addTypename: false, + ...defaultCacheOptions, + ...cacheOptions, }); const mockClient = createMockClient({ cache, resolvers }); diff --git a/spec/frontend/__helpers__/vue_test_utils_helper.js b/spec/frontend/__helpers__/vue_test_utils_helper.js index d6132ef84ac..a94cee84f74 100644 --- a/spec/frontend/__helpers__/vue_test_utils_helper.js +++ b/spec/frontend/__helpers__/vue_test_utils_helper.js @@ -1,4 +1,6 @@ -import { isArray } from 'lodash'; +import * as testingLibrary from '@testing-library/dom'; +import { createWrapper, WrapperArray, mount, shallowMount } from '@vue/test-utils'; +import { isArray, upperFirst } from 'lodash'; const vNodeContainsText = (vnode, text) => (vnode.text && vnode.text.includes(text)) || @@ -37,6 +39,17 @@ export const waitForMutation = (store, expectedMutationType) => }); export const extendedWrapper = (wrapper) => { + // https://testing-library.com/docs/queries/about + const AVAILABLE_QUERIES = [ + 'byRole', + 'byLabelText', + 'byPlaceholderText', + 'byText', + 'byDisplayValue', + 'byAltText', + 'byTitle', + ]; + if (isArray(wrapper) || !wrapper?.find) { // eslint-disable-next-line no-console console.warn( @@ -56,5 +69,63 @@ export const extendedWrapper = (wrapper) => { return this.findAll(`[data-testid="${id}"]`); }, }, + // `findBy` + ...AVAILABLE_QUERIES.reduce((accumulator, query) => { + return { + ...accumulator, + [`find${upperFirst(query)}`]: { + value(text, options = {}) { + const elements = testingLibrary[`queryAll${upperFirst(query)}`]( + wrapper.element, + text, + options, + ); + + // Return VTU `ErrorWrapper` if element is not found + // https://github.com/vuejs/vue-test-utils/blob/dev/packages/test-utils/src/error-wrapper.js + // VTU does not expose `ErrorWrapper` so, as of now, this is the best way to + // create an `ErrorWrapper` + if (!elements.length) { + const emptyElement = document.createElement('div'); + + return createWrapper(emptyElement).find('testing-library-element-not-found'); + } + + return createWrapper(elements[0], this.options || {}); + }, + }, + }; + }, {}), + // `findAllBy` + ...AVAILABLE_QUERIES.reduce((accumulator, query) => { + return { + ...accumulator, + [`findAll${upperFirst(query)}`]: { + value(text, options = {}) { + const elements = testingLibrary[`queryAll${upperFirst(query)}`]( + wrapper.element, + text, + options, + ); + + const wrappers = elements.map((element) => { + const elementWrapper = createWrapper(element, this.options || {}); + elementWrapper.selector = text; + + return elementWrapper; + }); + + const wrapperArray = new WrapperArray(wrappers); + wrapperArray.selector = text; + + return wrapperArray; + }, + }, + }; + }, {}), }); }; + +export const shallowMountExtended = (...args) => extendedWrapper(shallowMount(...args)); + +export const mountExtended = (...args) => extendedWrapper(mount(...args)); diff --git a/spec/frontend/__helpers__/vue_test_utils_helper_spec.js b/spec/frontend/__helpers__/vue_test_utils_helper_spec.js index d4f8e36c169..dfe5a483223 100644 --- a/spec/frontend/__helpers__/vue_test_utils_helper_spec.js +++ b/spec/frontend/__helpers__/vue_test_utils_helper_spec.js @@ -1,7 +1,27 @@ -import { shallowMount } from '@vue/test-utils'; -import { extendedWrapper, shallowWrapperContainsSlotText } from './vue_test_utils_helper'; +import * as testingLibrary from '@testing-library/dom'; +import * as vtu from '@vue/test-utils'; +import { + shallowMount, + Wrapper as VTUWrapper, + WrapperArray as VTUWrapperArray, +} from '@vue/test-utils'; +import { + extendedWrapper, + shallowMountExtended, + mountExtended, + shallowWrapperContainsSlotText, +} from './vue_test_utils_helper'; + +jest.mock('@testing-library/dom', () => ({ + __esModule: true, + ...jest.requireActual('@testing-library/dom'), +})); describe('Vue test utils helpers', () => { + afterAll(() => { + jest.unmock('@testing-library/dom'); + }); + describe('shallowWrapperContainsSlotText', () => { const mockText = 'text'; const mockSlot = `
${mockText}
`; @@ -84,7 +104,7 @@ describe('Vue test utils helpers', () => { ); }); - it('should find the component by test id', () => { + it('should find the element by test id', () => { expect(mockComponent.findByTestId(testId).exists()).toBe(true); }); }); @@ -105,5 +125,187 @@ describe('Vue test utils helpers', () => { expect(mockComponent.findAllByTestId(testId)).toHaveLength(2); }); }); + + describe.each` + findMethod | expectedQuery + ${'findByRole'} | ${'queryAllByRole'} + ${'findByLabelText'} | ${'queryAllByLabelText'} + ${'findByPlaceholderText'} | ${'queryAllByPlaceholderText'} + ${'findByText'} | ${'queryAllByText'} + ${'findByDisplayValue'} | ${'queryAllByDisplayValue'} + ${'findByAltText'} | ${'queryAllByAltText'} + `('$findMethod', ({ findMethod, expectedQuery }) => { + const text = 'foo bar'; + const options = { selector: 'div' }; + const mockDiv = document.createElement('div'); + + let wrapper; + beforeEach(() => { + wrapper = extendedWrapper( + shallowMount({ + template: `
foo bar
`, + }), + ); + }); + + it(`calls Testing Library \`${expectedQuery}\` function with correct parameters`, () => { + jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => [mockDiv]); + + wrapper[findMethod](text, options); + + expect(testingLibrary[expectedQuery]).toHaveBeenLastCalledWith( + wrapper.element, + text, + options, + ); + }); + + describe('when element is found', () => { + beforeEach(() => { + jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => [mockDiv]); + jest.spyOn(vtu, 'createWrapper'); + }); + + it('returns a VTU wrapper', () => { + const result = wrapper[findMethod](text, options); + + expect(vtu.createWrapper).toHaveBeenCalledWith(mockDiv, wrapper.options); + expect(result).toBeInstanceOf(VTUWrapper); + }); + }); + + describe('when multiple elements are found', () => { + beforeEach(() => { + const mockSpan = document.createElement('span'); + jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => [mockDiv, mockSpan]); + jest.spyOn(vtu, 'createWrapper'); + }); + + it('returns the first element as a VTU wrapper', () => { + const result = wrapper[findMethod](text, options); + + expect(vtu.createWrapper).toHaveBeenCalledWith(mockDiv, wrapper.options); + expect(result).toBeInstanceOf(VTUWrapper); + }); + }); + + describe('when element is not found', () => { + beforeEach(() => { + jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => []); + }); + + it('returns a VTU error wrapper', () => { + expect(wrapper[findMethod](text, options).exists()).toBe(false); + }); + }); + }); + + describe.each` + findMethod | expectedQuery + ${'findAllByRole'} | ${'queryAllByRole'} + ${'findAllByLabelText'} | ${'queryAllByLabelText'} + ${'findAllByPlaceholderText'} | ${'queryAllByPlaceholderText'} + ${'findAllByText'} | ${'queryAllByText'} + ${'findAllByDisplayValue'} | ${'queryAllByDisplayValue'} + ${'findAllByAltText'} | ${'queryAllByAltText'} + `('$findMethod', ({ findMethod, expectedQuery }) => { + const text = 'foo bar'; + const options = { selector: 'div' }; + const mockElements = [ + document.createElement('li'), + document.createElement('li'), + document.createElement('li'), + ]; + + let wrapper; + beforeEach(() => { + wrapper = extendedWrapper( + shallowMount({ + template: ` +
    +
  • foo
  • +
  • bar
  • +
  • baz
  • +
+ `, + }), + ); + }); + + it(`calls Testing Library \`${expectedQuery}\` function with correct parameters`, () => { + jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => mockElements); + + wrapper[findMethod](text, options); + + expect(testingLibrary[expectedQuery]).toHaveBeenLastCalledWith( + wrapper.element, + text, + options, + ); + }); + + describe('when elements are found', () => { + beforeEach(() => { + jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => mockElements); + }); + + it('returns a VTU wrapper array', () => { + const result = wrapper[findMethod](text, options); + + expect(result).toBeInstanceOf(VTUWrapperArray); + expect( + result.wrappers.every( + (resultWrapper) => + resultWrapper instanceof VTUWrapper && resultWrapper.options === wrapper.options, + ), + ).toBe(true); + expect(result.length).toBe(3); + }); + }); + + describe('when elements are not found', () => { + beforeEach(() => { + jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => []); + }); + + it('returns an empty VTU wrapper array', () => { + const result = wrapper[findMethod](text, options); + + expect(result).toBeInstanceOf(VTUWrapperArray); + expect(result.length).toBe(0); + }); + }); + }); + }); + + describe.each` + mountExtendedFunction | expectedMountFunction + ${shallowMountExtended} | ${'shallowMount'} + ${mountExtended} | ${'mount'} + `('$mountExtendedFunction', ({ mountExtendedFunction, expectedMountFunction }) => { + const FakeComponent = jest.fn(); + const options = { + propsData: { + foo: 'bar', + }, + }; + + beforeEach(() => { + const mockWrapper = { find: jest.fn() }; + jest.spyOn(vtu, expectedMountFunction).mockImplementation(() => mockWrapper); + }); + + it(`calls \`${expectedMountFunction}\` with passed arguments`, () => { + mountExtendedFunction(FakeComponent, options); + + expect(vtu[expectedMountFunction]).toHaveBeenCalledWith(FakeComponent, options); + }); + + it('returns extended wrapper', () => { + const result = mountExtendedFunction(FakeComponent, options); + + expect(result).toHaveProperty('find'); + expect(result).toHaveProperty('findByTestId'); + }); }); }); diff --git a/spec/frontend/__helpers__/web_worker_fake.js b/spec/frontend/__helpers__/web_worker_fake.js new file mode 100644 index 00000000000..041a9bd8540 --- /dev/null +++ b/spec/frontend/__helpers__/web_worker_fake.js @@ -0,0 +1,71 @@ +import path from 'path'; + +const isRelative = (pathArg) => pathArg.startsWith('.'); + +const transformRequirePath = (base, pathArg) => { + if (!isRelative(pathArg)) { + return pathArg; + } + + return path.resolve(base, pathArg); +}; + +const createRelativeRequire = (filename) => { + const rel = path.relative(__dirname, path.dirname(filename)); + const base = path.resolve(__dirname, rel); + + // reason: Dynamic require should be fine here since the code is dynamically evaluated anyways. + // eslint-disable-next-line import/no-dynamic-require, global-require + return (pathArg) => require(transformRequirePath(base, pathArg)); +}; + +/** + * Simulates a WebWorker module similar to the kind created by Webpack's [`worker-loader`][1] + * + * [1]: https://webpack.js.org/loaders/worker-loader/ + */ +export class FakeWebWorker { + /** + * Constructs a new FakeWebWorker instance + * + * @param {String} filename is the full path of the code, which is used to resolve relative imports. + * @param {String} code is the raw code of the web worker, which is dynamically evaluated on construction. + */ + constructor(filename, code) { + let isAlive = true; + + const clientTarget = new EventTarget(); + const workerTarget = new EventTarget(); + + this.addEventListener = (...args) => clientTarget.addEventListener(...args); + this.removeEventListener = (...args) => clientTarget.removeEventListener(...args); + this.postMessage = (message) => { + if (!isAlive) { + return; + } + + workerTarget.dispatchEvent(new MessageEvent('message', { data: message })); + }; + this.terminate = () => { + isAlive = false; + }; + + const workerScope = { + addEventListener: (...args) => workerTarget.addEventListener(...args), + removeEventListener: (...args) => workerTarget.removeEventListener(...args), + postMessage: (message) => { + if (!isAlive) { + return; + } + + clientTarget.dispatchEvent(new MessageEvent('message', { data: message })); + }, + }; + + // reason: `no-new-func` is like `eval` except it only executed on global scope and it's easy + // to pass in local references. `eval` is very unsafe in production, but in our test environment + // we shold be fine. + // eslint-disable-next-line no-new-func + Function('self', 'require', code)(workerScope, createRelativeRequire(filename)); + } +} diff --git a/spec/frontend/__helpers__/web_worker_mock.js b/spec/frontend/__helpers__/web_worker_mock.js deleted file mode 100644 index 2b4a391e1d2..00000000000 --- a/spec/frontend/__helpers__/web_worker_mock.js +++ /dev/null @@ -1,10 +0,0 @@ -/* eslint-disable class-methods-use-this */ -export default class WebWorkerMock { - addEventListener() {} - - removeEventListener() {} - - terminate() {} - - postMessage() {} -} diff --git a/spec/frontend/__helpers__/web_worker_transformer.js b/spec/frontend/__helpers__/web_worker_transformer.js new file mode 100644 index 00000000000..5b2f7d77947 --- /dev/null +++ b/spec/frontend/__helpers__/web_worker_transformer.js @@ -0,0 +1,18 @@ +/* eslint-disable import/no-commonjs */ +const babelJestTransformer = require('babel-jest'); + +// This Jest will transform the code of a WebWorker module into a FakeWebWorker subclass. +// This is meant to mirror Webpack's [`worker-loader`][1]. +// [1]: https://webpack.js.org/loaders/worker-loader/ +module.exports = { + process: (contentArg, filename, ...args) => { + const { code: content } = babelJestTransformer.process(contentArg, filename, ...args); + + return `const { FakeWebWorker } = require("helpers/web_worker_fake"); + module.exports = class JestTransformedWorker extends FakeWebWorker { + constructor() { + super(${JSON.stringify(filename)}, ${JSON.stringify(content)}); + } + };`; + }, +}; diff --git a/spec/frontend/__mocks__/vue/index.js b/spec/frontend/__mocks__/vue/index.js new file mode 100644 index 00000000000..52a5c6c5fcd --- /dev/null +++ b/spec/frontend/__mocks__/vue/index.js @@ -0,0 +1,7 @@ +import Vue from 'vue'; + +Vue.config.productionTip = false; +Vue.config.devtools = false; + +export default Vue; +export * from 'vue'; diff --git a/spec/frontend/access_tokens/index_spec.js b/spec/frontend/access_tokens/index_spec.js index e3f17e21739..1d8ac7cec25 100644 --- a/spec/frontend/access_tokens/index_spec.js +++ b/spec/frontend/access_tokens/index_spec.js @@ -25,18 +25,22 @@ describe('access tokens', () => { }); describe.each` - initFunction | mountSelector | expectedComponent - ${initExpiresAtField} | ${'js-access-tokens-expires-at'} | ${ExpiresAtField} - ${initProjectsField} | ${'js-access-tokens-projects'} | ${ProjectsField} - `('$initFunction', ({ initFunction, mountSelector, expectedComponent }) => { + initFunction | mountSelector | fieldName | expectedComponent + ${initExpiresAtField} | ${'js-access-tokens-expires-at'} | ${'expiresAt'} | ${ExpiresAtField} + ${initProjectsField} | ${'js-access-tokens-projects'} | ${'projects'} | ${ProjectsField} + `('$initFunction', ({ initFunction, mountSelector, fieldName, expectedComponent }) => { describe('when mount element exists', () => { + const nameAttribute = `access_tokens[${fieldName}]`; + const idAttribute = `access_tokens_${fieldName}`; + beforeEach(() => { const mountEl = document.createElement('div'); mountEl.classList.add(mountSelector); const input = document.createElement('input'); - input.setAttribute('name', 'foo-bar'); - input.setAttribute('id', 'foo-bar'); + input.setAttribute('name', nameAttribute); + input.setAttribute('data-js-name', fieldName); + input.setAttribute('id', idAttribute); input.setAttribute('placeholder', 'Foo bar'); input.setAttribute('value', '1,2'); @@ -57,8 +61,8 @@ describe('access tokens', () => { expect(component.exists()).toBe(true); expect(component.props('inputAttrs')).toEqual({ - name: 'foo-bar', - id: 'foo-bar', + name: nameAttribute, + id: idAttribute, value: '1,2', placeholder: 'Foo bar', }); diff --git a/spec/frontend/admin/signup_restrictions/components/signup_checkbox_spec.js b/spec/frontend/admin/signup_restrictions/components/signup_checkbox_spec.js new file mode 100644 index 00000000000..ae9b6f57ee0 --- /dev/null +++ b/spec/frontend/admin/signup_restrictions/components/signup_checkbox_spec.js @@ -0,0 +1,66 @@ +import { GlFormCheckbox } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import SignupCheckbox from '~/pages/admin/application_settings/general/components/signup_checkbox.vue'; + +describe('Signup Form', () => { + let wrapper; + + const props = { + name: 'name', + helpText: 'some help text', + label: 'a label', + value: true, + dataQaSelector: 'qa_selector', + }; + + const mountComponent = () => { + wrapper = shallowMount(SignupCheckbox, { + propsData: props, + stubs: { + GlFormCheckbox, + }, + }); + }; + + const findByTestId = (id) => wrapper.find(`[data-testid="${id}"]`); + const findHiddenInput = () => findByTestId('input'); + const findCheckbox = () => wrapper.find(GlFormCheckbox); + const findCheckboxLabel = () => findByTestId('label'); + const findHelpText = () => findByTestId('helpText'); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('Signup Checkbox', () => { + beforeEach(() => { + mountComponent(); + }); + + describe('hidden input element', () => { + it('gets passed correct values from props', () => { + expect(findHiddenInput().attributes('name')).toBe(props.name); + + expect(findHiddenInput().attributes('value')).toBe('1'); + }); + }); + + describe('checkbox', () => { + it('gets passed correct checked value', () => { + expect(findCheckbox().attributes('checked')).toBe('true'); + }); + + it('gets passed correct label', () => { + expect(findCheckboxLabel().text()).toBe(props.label); + }); + + it('gets passed correct help text', () => { + expect(findHelpText().text()).toBe(props.helpText); + }); + + it('gets passed data qa selector', () => { + expect(findCheckbox().attributes('data-qa-selector')).toBe(props.dataQaSelector); + }); + }); + }); +}); diff --git a/spec/frontend/admin/signup_restrictions/components/signup_form_spec.js b/spec/frontend/admin/signup_restrictions/components/signup_form_spec.js new file mode 100644 index 00000000000..18339164d5a --- /dev/null +++ b/spec/frontend/admin/signup_restrictions/components/signup_form_spec.js @@ -0,0 +1,331 @@ +import { GlButton, GlModal } from '@gitlab/ui'; +import { within, fireEvent } from '@testing-library/dom'; +import { shallowMount, mount } from '@vue/test-utils'; +import { stubComponent } from 'helpers/stub_component'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import SignupForm from '~/pages/admin/application_settings/general/components/signup_form.vue'; +import { mockData } from '../mock_data'; + +jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' })); + +describe('Signup Form', () => { + let wrapper; + let formSubmitSpy; + + const mountComponent = ({ injectedProps = {}, mountFn = shallowMount, stubs = {} } = {}) => { + wrapper = extendedWrapper( + mountFn(SignupForm, { + provide: { + ...mockData, + ...injectedProps, + }, + stubs, + }), + ); + }; + + const queryByLabelText = (text) => within(wrapper.element).queryByLabelText(text); + + const findForm = () => wrapper.findByTestId('form'); + const findInputCsrf = () => findForm().find('[name="authenticity_token"]'); + const findFormSubmitButton = () => findForm().find(GlButton); + + const findDenyListRawRadio = () => queryByLabelText('Enter denylist manually'); + const findDenyListFileRadio = () => queryByLabelText('Upload denylist file'); + + const findDenyListRawInputGroup = () => wrapper.findByTestId('domain-denylist-raw-input-group'); + const findDenyListFileInputGroup = () => wrapper.findByTestId('domain-denylist-file-input-group'); + + const findRequireAdminApprovalCheckbox = () => + wrapper.findByTestId('require-admin-approval-checkbox'); + const findUserCapInput = () => wrapper.findByTestId('user-cap-input'); + const findModal = () => wrapper.find(GlModal); + + afterEach(() => { + wrapper.destroy(); + + formSubmitSpy = null; + }); + + describe('form data', () => { + beforeEach(() => { + mountComponent(); + }); + + it.each` + prop | propValue | elementSelector | formElementPassedDataType | formElementKey | expected + ${'signupEnabled'} | ${mockData.signupEnabled} | ${'[name="application_setting[signup_enabled]"]'} | ${'prop'} | ${'value'} | ${mockData.signupEnabled} + ${'requireAdminApprovalAfterUserSignup'} | ${mockData.requireAdminApprovalAfterUserSignup} | ${'[name="application_setting[require_admin_approval_after_user_signup]"]'} | ${'prop'} | ${'value'} | ${mockData.requireAdminApprovalAfterUserSignup} + ${'sendUserConfirmationEmail'} | ${mockData.sendUserConfirmationEmail} | ${'[name="application_setting[send_user_confirmation_email]"]'} | ${'prop'} | ${'value'} | ${mockData.sendUserConfirmationEmail} + ${'newUserSignupsCap'} | ${mockData.newUserSignupsCap} | ${'[name="application_setting[new_user_signups_cap]"]'} | ${'attribute'} | ${'value'} | ${mockData.newUserSignupsCap} + ${'minimumPasswordLength'} | ${mockData.minimumPasswordLength} | ${'[name="application_setting[minimum_password_length]"]'} | ${'attribute'} | ${'value'} | ${mockData.minimumPasswordLength} + ${'minimumPasswordLengthMin'} | ${mockData.minimumPasswordLengthMin} | ${'[name="application_setting[minimum_password_length]"]'} | ${'attribute'} | ${'min'} | ${mockData.minimumPasswordLengthMin} + ${'minimumPasswordLengthMax'} | ${mockData.minimumPasswordLengthMax} | ${'[name="application_setting[minimum_password_length]"]'} | ${'attribute'} | ${'max'} | ${mockData.minimumPasswordLengthMax} + ${'domainAllowlistRaw'} | ${mockData.domainAllowlistRaw} | ${'[name="application_setting[domain_allowlist_raw]"]'} | ${'value'} | ${'value'} | ${mockData.domainAllowlistRaw} + ${'domainDenylistEnabled'} | ${mockData.domainDenylistEnabled} | ${'[name="application_setting[domain_denylist_enabled]"]'} | ${'prop'} | ${'value'} | ${mockData.domainDenylistEnabled} + ${'denylistTypeRawSelected'} | ${mockData.denylistTypeRawSelected} | ${'[name="denylist_type"]'} | ${'attribute'} | ${'checked'} | ${'raw'} + ${'domainDenylistRaw'} | ${mockData.domainDenylistRaw} | ${'[name="application_setting[domain_denylist_raw]"]'} | ${'value'} | ${'value'} | ${mockData.domainDenylistRaw} + ${'emailRestrictionsEnabled'} | ${mockData.emailRestrictionsEnabled} | ${'[name="application_setting[email_restrictions_enabled]"]'} | ${'prop'} | ${'value'} | ${mockData.emailRestrictionsEnabled} + ${'emailRestrictions'} | ${mockData.emailRestrictions} | ${'[name="application_setting[email_restrictions]"]'} | ${'value'} | ${'value'} | ${mockData.emailRestrictions} + ${'afterSignUpText'} | ${mockData.afterSignUpText} | ${'[name="application_setting[after_sign_up_text]"]'} | ${'value'} | ${'value'} | ${mockData.afterSignUpText} + `( + 'form element $elementSelector gets $expected value for $formElementKey $formElementPassedDataType when prop $prop is set to $propValue', + ({ elementSelector, expected, formElementKey, formElementPassedDataType }) => { + const formElement = wrapper.find(elementSelector); + + switch (formElementPassedDataType) { + case 'attribute': + expect(formElement.attributes(formElementKey)).toBe(expected); + break; + case 'prop': + expect(formElement.props(formElementKey)).toBe(expected); + break; + case 'value': + expect(formElement.element.value).toBe(expected); + break; + default: + expect(formElement.props(formElementKey)).toBe(expected); + break; + } + }, + ); + it('gets passed the path for action attribute', () => { + expect(findForm().attributes('action')).toBe(mockData.settingsPath); + }); + + it('gets passed the csrf token as a hidden input value', () => { + expect(findInputCsrf().attributes('type')).toBe('hidden'); + + expect(findInputCsrf().attributes('value')).toBe('mock-csrf-token'); + }); + }); + + describe('domain deny list', () => { + describe('when it is set to raw from props', () => { + beforeEach(() => { + mountComponent({ mountFn: mount }); + }); + + it('has raw list selected', () => { + expect(findDenyListRawRadio().checked).toBe(true); + }); + + it('has file not selected', () => { + expect(findDenyListFileRadio().checked).toBe(false); + }); + + it('raw list input is displayed', () => { + expect(findDenyListRawInputGroup().exists()).toBe(true); + }); + + it('file input is not displayed', () => { + expect(findDenyListFileInputGroup().exists()).toBe(false); + }); + + describe('when user clicks on file radio', () => { + beforeEach(() => { + fireEvent.click(findDenyListFileRadio()); + }); + + it('has raw list not selected', () => { + expect(findDenyListRawRadio().checked).toBe(false); + }); + + it('has file selected', () => { + expect(findDenyListFileRadio().checked).toBe(true); + }); + + it('raw list input is not displayed', () => { + expect(findDenyListRawInputGroup().exists()).toBe(false); + }); + + it('file input is displayed', () => { + expect(findDenyListFileInputGroup().exists()).toBe(true); + }); + }); + }); + + describe('when it is set to file from injected props', () => { + beforeEach(() => { + mountComponent({ mountFn: mount, injectedProps: { denylistTypeRawSelected: false } }); + }); + + it('has raw list not selected', () => { + expect(findDenyListRawRadio().checked).toBe(false); + }); + + it('has file selected', () => { + expect(findDenyListFileRadio().checked).toBe(true); + }); + + it('raw list input is not displayed', () => { + expect(findDenyListRawInputGroup().exists()).toBe(false); + }); + + it('file input is displayed', () => { + expect(findDenyListFileInputGroup().exists()).toBe(true); + }); + + describe('when user clicks on raw list radio', () => { + beforeEach(() => { + fireEvent.click(findDenyListRawRadio()); + }); + + it('has raw list selected', () => { + expect(findDenyListRawRadio().checked).toBe(true); + }); + + it('has file not selected', () => { + expect(findDenyListFileRadio().checked).toBe(false); + }); + + it('raw list input is displayed', () => { + expect(findDenyListRawInputGroup().exists()).toBe(true); + }); + + it('file input is not displayed', () => { + expect(findDenyListFileInputGroup().exists()).toBe(false); + }); + }); + }); + }); + + describe('form submit button confirmation modal for side-effect of adding possibly unwanted new users', () => { + it.each` + requireAdminApprovalAction | userCapAction | buttonEffect + ${'unchanged from true'} | ${'unchanged'} | ${'submits form'} + ${'unchanged from false'} | ${'unchanged'} | ${'submits form'} + ${'toggled off'} | ${'unchanged'} | ${'shows confirmation modal'} + ${'toggled on'} | ${'unchanged'} | ${'submits form'} + ${'unchanged from false'} | ${'increased'} | ${'shows confirmation modal'} + ${'unchanged from true'} | ${'increased'} | ${'shows confirmation modal'} + ${'toggled off'} | ${'increased'} | ${'shows confirmation modal'} + ${'toggled on'} | ${'increased'} | ${'shows confirmation modal'} + ${'toggled on'} | ${'decreased'} | ${'submits form'} + ${'unchanged from false'} | ${'changed from limited to unlimited'} | ${'shows confirmation modal'} + ${'unchanged from false'} | ${'changed from unlimited to limited'} | ${'submits form'} + ${'unchanged from false'} | ${'unchanged from unlimited'} | ${'submits form'} + `( + '$buttonEffect if require admin approval for new sign-ups is $requireAdminApprovalAction and the user cap is $userCapAction', + async ({ requireAdminApprovalAction, userCapAction, buttonEffect }) => { + let isModalDisplayed; + + switch (buttonEffect) { + case 'shows confirmation modal': + isModalDisplayed = true; + break; + case 'submits form': + isModalDisplayed = false; + break; + default: + isModalDisplayed = false; + break; + } + + const isFormSubmittedWhenClickingFormSubmitButton = !isModalDisplayed; + + const injectedProps = {}; + + const USER_CAP_DEFAULT = 5; + + switch (userCapAction) { + case 'changed from unlimited to limited': + injectedProps.newUserSignupsCap = ''; + break; + case 'unchanged from unlimited': + injectedProps.newUserSignupsCap = ''; + break; + default: + injectedProps.newUserSignupsCap = USER_CAP_DEFAULT; + break; + } + + switch (requireAdminApprovalAction) { + case 'unchanged from true': + injectedProps.requireAdminApprovalAfterUserSignup = true; + break; + case 'unchanged from false': + injectedProps.requireAdminApprovalAfterUserSignup = false; + break; + case 'toggled off': + injectedProps.requireAdminApprovalAfterUserSignup = true; + break; + case 'toggled on': + injectedProps.requireAdminApprovalAfterUserSignup = false; + break; + default: + injectedProps.requireAdminApprovalAfterUserSignup = false; + break; + } + + formSubmitSpy = jest.spyOn(HTMLFormElement.prototype, 'submit').mockImplementation(); + + await mountComponent({ + injectedProps, + stubs: { GlButton, GlModal: stubComponent(GlModal) }, + }); + + findModal().vm.show = jest.fn(); + + if ( + requireAdminApprovalAction === 'toggled off' || + requireAdminApprovalAction === 'toggled on' + ) { + await findRequireAdminApprovalCheckbox().vm.$emit('input', false); + } + + switch (userCapAction) { + case 'increased': + await findUserCapInput().vm.$emit('input', USER_CAP_DEFAULT + 1); + break; + case 'decreased': + await findUserCapInput().vm.$emit('input', USER_CAP_DEFAULT - 1); + break; + case 'changed from limited to unlimited': + await findUserCapInput().vm.$emit('input', ''); + break; + case 'changed from unlimited to limited': + await findUserCapInput().vm.$emit('input', USER_CAP_DEFAULT); + break; + default: + break; + } + + await findFormSubmitButton().trigger('click'); + + if (isFormSubmittedWhenClickingFormSubmitButton) { + expect(formSubmitSpy).toHaveBeenCalled(); + expect(findModal().vm.show).not.toHaveBeenCalled(); + } else { + expect(formSubmitSpy).not.toHaveBeenCalled(); + expect(findModal().vm.show).toHaveBeenCalled(); + } + }, + ); + + describe('modal actions', () => { + beforeEach(async () => { + const INITIAL_USER_CAP = 5; + + await mountComponent({ + injectedProps: { + newUserSignupsCap: INITIAL_USER_CAP, + }, + stubs: { GlButton, GlModal: stubComponent(GlModal) }, + }); + + await findUserCapInput().vm.$emit('input', INITIAL_USER_CAP + 1); + + await findFormSubmitButton().trigger('click'); + }); + + it('submits the form after clicking approve users button', async () => { + formSubmitSpy = jest.spyOn(HTMLFormElement.prototype, 'submit').mockImplementation(); + + await findModal().vm.$emit('primary'); + + expect(formSubmitSpy).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/spec/frontend/admin/signup_restrictions/mock_data.js b/spec/frontend/admin/signup_restrictions/mock_data.js new file mode 100644 index 00000000000..624a5614c9c --- /dev/null +++ b/spec/frontend/admin/signup_restrictions/mock_data.js @@ -0,0 +1,41 @@ +export const rawMockData = { + host: 'path/to/host', + settingsPath: 'path/to/settings', + signupEnabled: 'true', + requireAdminApprovalAfterUserSignup: 'true', + sendUserConfirmationEmail: 'true', + minimumPasswordLength: '8', + minimumPasswordLengthMin: '3', + minimumPasswordLengthMax: '10', + minimumPasswordLengthHelpLink: 'help/link', + domainAllowlistRaw: 'domain1.com, domain2.com', + newUserSignupsCap: '8', + domainDenylistEnabled: 'true', + denylistTypeRawSelected: 'true', + domainDenylistRaw: 'domain2.com, domain3.com', + emailRestrictionsEnabled: 'true', + supportedSyntaxLinkUrl: '/supported/syntax/link', + emailRestrictions: 'user1@domain.com, user2@domain.com', + afterSignUpText: 'Congratulations on your successful sign-up!', +}; + +export const mockData = { + host: 'path/to/host', + settingsPath: 'path/to/settings', + signupEnabled: true, + requireAdminApprovalAfterUserSignup: true, + sendUserConfirmationEmail: true, + minimumPasswordLength: '8', + minimumPasswordLengthMin: '3', + minimumPasswordLengthMax: '10', + minimumPasswordLengthHelpLink: 'help/link', + domainAllowlistRaw: 'domain1.com, domain2.com', + newUserSignupsCap: '8', + domainDenylistEnabled: true, + denylistTypeRawSelected: true, + domainDenylistRaw: 'domain2.com, domain3.com', + emailRestrictionsEnabled: true, + supportedSyntaxLinkUrl: '/supported/syntax/link', + emailRestrictions: 'user1@domain.com, user2@domain.com', + afterSignUpText: 'Congratulations on your successful sign-up!', +}; diff --git a/spec/frontend/admin/signup_restrictions/utils.js b/spec/frontend/admin/signup_restrictions/utils.js new file mode 100644 index 00000000000..30a95467e09 --- /dev/null +++ b/spec/frontend/admin/signup_restrictions/utils.js @@ -0,0 +1,19 @@ +export const setDataAttributes = (data, element) => { + Object.keys(data).forEach((key) => { + const value = data[key]; + + // attribute should be: + // - valueless if value is 'true' + // - absent if value is 'false' + switch (value) { + case false: + break; + case true: + element.dataset[`${key}`] = ''; + break; + default: + element.dataset[`${key}`] = value; + break; + } + }); +}; diff --git a/spec/frontend/admin/signup_restrictions/utils_spec.js b/spec/frontend/admin/signup_restrictions/utils_spec.js new file mode 100644 index 00000000000..fd5c4c3317b --- /dev/null +++ b/spec/frontend/admin/signup_restrictions/utils_spec.js @@ -0,0 +1,22 @@ +import { getParsedDataset } from '~/pages/admin/application_settings/utils'; +import { rawMockData, mockData } from './mock_data'; + +describe('utils', () => { + describe('getParsedDataset', () => { + it('returns correct results', () => { + expect( + getParsedDataset({ + dataset: rawMockData, + booleanAttributes: [ + 'signupEnabled', + 'requireAdminApprovalAfterUserSignup', + 'sendUserConfirmationEmail', + 'domainDenylistEnabled', + 'denylistTypeRawSelected', + 'emailRestrictionsEnabled', + ], + }), + ).toEqual(mockData); + }); + }); +}); diff --git a/spec/frontend/admin/users/components/user_date_spec.js b/spec/frontend/admin/users/components/user_date_spec.js index 6428b10059b..1a2f2938db5 100644 --- a/spec/frontend/admin/users/components/user_date_spec.js +++ b/spec/frontend/admin/users/components/user_date_spec.js @@ -1,6 +1,6 @@ import { shallowMount } from '@vue/test-utils'; -import UserDate from '~/admin/users/components/user_date.vue'; +import UserDate from '~/vue_shared/components/user_date.vue'; import { users } from '../mock_data'; const mockDate = users[0].createdAt; diff --git a/spec/frontend/admin/users/components/users_table_spec.js b/spec/frontend/admin/users/components/users_table_spec.js index f1fcc20fb65..424b0deebd3 100644 --- a/spec/frontend/admin/users/components/users_table_spec.js +++ b/spec/frontend/admin/users/components/users_table_spec.js @@ -3,8 +3,8 @@ import { mount } from '@vue/test-utils'; import AdminUserActions from '~/admin/users/components/user_actions.vue'; import AdminUserAvatar from '~/admin/users/components/user_avatar.vue'; -import AdminUserDate from '~/admin/users/components/user_date.vue'; import AdminUsersTable from '~/admin/users/components/users_table.vue'; +import AdminUserDate from '~/vue_shared/components/user_date.vue'; import { users, paths } from '../mock_data'; diff --git a/spec/frontend/admin/users/new_spec.js b/spec/frontend/admin/users/new_spec.js new file mode 100644 index 00000000000..692c583dca8 --- /dev/null +++ b/spec/frontend/admin/users/new_spec.js @@ -0,0 +1,76 @@ +import { + setupInternalUserRegexHandler, + ID_USER_EMAIL, + ID_USER_EXTERNAL, + ID_WARNING, +} from '~/admin/users/new'; + +describe('admin/users/new', () => { + const FIXTURE = 'admin/users/new_with_internal_user_regex.html'; + + let elExternal; + let elUserEmail; + let elWarningMessage; + + beforeEach(() => { + loadFixtures(FIXTURE); + setupInternalUserRegexHandler(); + + elExternal = document.getElementById(ID_USER_EXTERNAL); + elUserEmail = document.getElementById(ID_USER_EMAIL); + elWarningMessage = document.getElementById(ID_WARNING); + + elExternal.checked = true; + }); + + const changeEmail = (val) => { + elUserEmail.value = val; + elUserEmail.dispatchEvent(new Event('input')); + }; + + const hasHiddenWarning = () => elWarningMessage.classList.contains('hidden'); + + describe('Behaviour of userExternal checkbox', () => { + it('hides warning by default', () => { + expect(hasHiddenWarning()).toBe(true); + }); + + describe('when matches email as internal', () => { + beforeEach(() => { + changeEmail('test@'); + }); + + it('has external unchecked', () => { + expect(elExternal.checked).toBe(false); + }); + + it('shows warning', () => { + expect(hasHiddenWarning()).toBe(false); + }); + + describe('when external is checked again', () => { + beforeEach(() => { + elExternal.dispatchEvent(new Event('change')); + }); + + it('hides warning', () => { + expect(hasHiddenWarning()).toBe(true); + }); + }); + }); + + describe('when matches emails as external', () => { + beforeEach(() => { + changeEmail('test.ext@'); + }); + + it('has external checked', () => { + expect(elExternal.checked).toBe(true); + }); + + it('hides warning', () => { + expect(hasHiddenWarning()).toBe(true); + }); + }); + }); +}); diff --git a/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap b/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap deleted file mode 100644 index 1f8429af7dd..00000000000 --- a/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap +++ /dev/null @@ -1,524 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AlertsSettingsForm with default values renders the initial template 1`] = ` -
-
- - -
- -
-
- -
- - - - - - -
-
- -
- - - - - - - -
- -
- - - -
-
-
- - - - - - -